diff --git a/doc/limitations.md b/doc/limitations.md index 6e9bf3d..b4e5b5e 100644 --- a/doc/limitations.md +++ b/doc/limitations.md @@ -23,29 +23,23 @@ * If an enum is declared as a flag enum, its zero value will not be reflected. -* Or, for enum types that are deeply nested in classes and/or namespaces, declare a function called `my_adl_info_struct adl_magic_enum_define_range(my_enum_type)` in the same namespace as `my_enum_type`, which magic_enum will find by ADL (because the function is in the same class/namespace as `my_enum_type`), and whose return type is a struct with `static constexpr` data members containing the same parameters as `magic_enum::customize::enum_range` - ```cpp - namespace Deeply::Nested::Namespace { - enum class my_enum_type { ... }; - struct my_adl_info_struct { - static constexpr bool is_flags = true; - // you can also set min and max here (see Enum Range below) - // static constexpr int min = ...; - // static constexpr int max = ...; - }; - // - magic_enum will find this function by ADL - // - no need to ever define this function - my_adl_info_struct adl_magic_enum_define_range(my_enum_type); - } - ``` +* Or, for enum types that are deeply nested in classes and/or namespaces, declare a function called `adl_magic_enum_define_range(my_enum_type)` in the same namespace as `my_enum_type`, which magic_enum will find by ADL (because the function is in the same class/namespace as `my_enum_type`), and whose return type is a `magic_enum::customize::adl_info`. -* As a shorthand, if you only want to set `is_flags` and not `min` or `max`, you can also use `magic_enum::customize::adl_info` to avoid having to define `my_adl_info_struct` in your code: ```cpp namespace Deeply::Nested::Namespace { - enum class my_enum_type { ... }; + enum class my_enum_type { my_enum_value1,my_enum_value2 }; + // - magic_enum will find this function by ADL - // - no need to ever define this function - magic_enum::customize::adl_info adl_magic_enum_define_range(my_enum_type); + // - uses builder pattern + // - use auto to not have to name the type yourself + auto adl_magic_enum_define_range(my_enum_type) + { + return magic_enum::customize::adl_info() + .minmax<10,10>() // the min max search range + .flag() // whether it is a flag enum + .prefix(); // how many characters to trim from the start of each enum entry. + } + } ``` @@ -78,32 +72,6 @@ }; ``` -* Or, for enum types that are deeply nested in classes and/or namespaces, declare a function called `my_adl_info_struct adl_magic_enum_define_range(my_enum_type)` in the same namespace as `my_enum_type`, which magic_enum will find by ADL (because the function is in the same class/namespace as `my_enum_type`), and whose return type is a struct with `static constexpr` data members containing the same parameters as `magic_enum::customize::enum_range` - ```cpp - namespace Deeply::Nested::Namespace { - enum class my_enum_type { ... }; - struct my_adl_info_struct { - static constexpr int min = 100; - static constexpr int max = 300; - // you can also set is_flags here - // static constexpr bool is_flags = true; - }; - // - magic_enum will find this function by ADL - // - no need to ever define this function - my_adl_info_struct adl_magic_enum_define_range(my_enum_type); - } - ``` - -* As a shorthand, if you only want to set `min` and `max` and not `is_flags`, you can also use `magic_enum::customize::adl_info` to avoid having to define `my_adl_info_struct` in your code: - ```cpp - namespace Deeply::Nested::Namespace { - enum class my_enum_type { ... }; - // - magic_enum will find this function by ADL - // - no need to ever define this function - magic_enum::customize::adl_info<100 /*min*/, 300 /*max*/> adl_magic_enum_define_range(my_enum_type); - } - ``` - ## Aliasing magic_enum [won't work if a value is aliased](https://github.com/Neargye/magic_enum/issues/68). How magic_enum works with aliases is compiler-implementation-defined. diff --git a/doc/reference.md b/doc/reference.md index 7d64103..3a03986 100644 --- a/doc/reference.md +++ b/doc/reference.md @@ -8,6 +8,7 @@ * [`enum_name` returns name from enum value.](#enum_name) * [`enum_names` obtains string enum name sequence.](#enum_names) * [`enum_entries` obtains pair (value enum, string enum name) sequence.](#enum_entries) +* [`customize::enum_range`](#customizeenum_range) * [`enum_index` obtains index in enum value sequence from enum value.](#enum_index) * [`enum_contains` checks whether enum contains enumerator with such value.](#enum_contains) * [`enum_reflected` returns true if the enum value is in the range of values that can be reflected..](#enum_reflected) @@ -63,6 +64,7 @@ #define MAGIC_ENUM_RANGE_MAX 255 ``` + ## `enum_cast` ```cpp @@ -276,6 +278,62 @@ constexpr array, N> enum_entries() noexcept; // color_entries[0].second -> "RED" ``` +## `customize::enum_range` + +```cpp +namespace customize { +template +struct enum_range { + constexpr static std::size_t prefix_length = 0; + constexpr static bool is_flags = false; + constexpr static int min = MAGIC_ENUM_MIN_RANGE; + constexpr static int max = MAGIC_ENUM_MAX_RANGE; +}; +} + +``` + +* Defined in header `` + +* A customization point for controlling `magic_enum` defaults + +* It has a defaulted second `void` typename template parameter for SFINAE. + +* `is_flags` tells `magic_enum` whether this enum should be considered to be a bitflag enum. It is not required to be defined if not defined it will be assumed to be `false` + +* `prefix_length` tells `magic_enum` how many characters to remove from the start of the names for all string functions. It is not required to be defined if not defined it will be assumed to be `0` + +* `min` and `max` are not required to be defined if `is_flags` is defined because they are ignored for enum flags. + otherwise they are required. + +* Examples + + * Controlling prefix length + + ```cpp + enum CStyleAnimals { + CStyleAnimals_Giraffe, + CStyleAnimals_Elephant, + CStyleAnimals_Lion, + }; + + template<> + struct magic_enum::customize::enum_range { + // sizeof counts null terminator subtract 1 to get length + constexpr static auto prefix_length = sizeof("CStyleAnimals_")-1; + constexpr static int min = CStyleAnimals_Giraffe; // required + constexpr static int max = CStyleAnimals_Lion; // required + }; + + CStyleAnimals animal = CStyleAnimals_Giraffe; + auto animal_name = magic_enum::enum_name(animal); + // animal_name => "Giraffe" + auto animal_from_string = magic_enum::enum_cast(animal_name); + // animal_from_string.value() == CStyleAnimals_Giraffe + ``` + + + ## `enum_index` ```cpp @@ -512,31 +570,24 @@ constexpr bool enum_flags_contains(string_view value, BinaryPredicate p) noexcep magic_enum::enum_flags_test_any(Left|Down|Right, Down|Right); // -> "true" ``` -* Or, for enum types that are deeply nested in classes and/or namespaces, declare a function called `my_adl_info_struct adl_magic_enum_define_range(my_enum_type)` in the same namespace as `my_enum_type`, which magic_enum will find by ADL (because the function is in the same class/namespace as `my_enum_type`), and whose return type is a struct with `static constexpr` data members containing the same parameters as `magic_enum::customize::enum_range` - ```cpp - namespace Deeply::Nested::Namespace { - enum class my_enum_type { ... }; - struct my_adl_info_struct { - static constexpr bool is_flags = true; - // you can also set min and max here (see Limitations document) - // static constexpr int min = ...; - // static constexpr int max = ...; - }; - // - magic_enum will find this function by ADL - // - no need to ever define this function - my_adl_info_struct adl_magic_enum_define_range(my_enum_type); - } - ``` +* Or, for enum types that are deeply nested in classes and/or namespaces, declare a function called `adl_magic_enum_define_range(my_enum_type)` in the same namespace as `my_enum_type`, which magic_enum will find by ADL (because the function is in the same class/namespace as `my_enum_type`), and whose return type is a `magic_enum::customize::adl_info`. + +```cpp +namespace Deeply::Nested::Namespace { + enum class my_enum_type { my_enum_value1,my_enum_value2 }; -* As a shorthand, if you only want to set `is_flags` and not `min` or `max`, you can also use `magic_enum::customize::adl_info` to avoid having to define `my_adl_info_struct` in your code: - ```cpp - namespace Deeply::Nested::Namespace { - enum class my_enum_type { ... }; // - magic_enum will find this function by ADL - // - no need to ever define this function - magic_enum::customize::adl_info adl_magic_enum_define_range(my_enum_type); + // - uses builder pattern + // - use auto to not have to name the type yourself + auto adl_magic_enum_define_range(my_enum_type) + { + return magic_enum::customize::adl_info() + .minmax<10,10>() // the min max search range + .flag() // whether it is a flag enum + .prefix(); // how many characters to trim from the start of each enum entry. } - ``` +} +``` ## `is_unscoped_enum` diff --git a/include/magic_enum/magic_enum.hpp b/include/magic_enum/magic_enum.hpp index cbddc50..3ffc16f 100644 --- a/include/magic_enum/magic_enum.hpp +++ b/include/magic_enum/magic_enum.hpp @@ -171,58 +171,55 @@ static_assert([] { return true; } (), "magic_enum::customize wchar_t is not compatible with ASCII."); +namespace customize { + template + struct enum_range; +} + namespace detail { - template - constexpr inline bool has_is_flags_adl = false; + template + constexpr inline std::size_t prefix_length_or_zero = 0; template - constexpr inline bool has_is_flags_adl < E, std::void_t > = decltype(adl_magic_enum_define_range(E{}))::is_flags; - - template - constexpr inline auto has_minmax_adl = std::pair(MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX); - - template - constexpr inline auto has_minmax_adl < E, std::void_t> = - std::pair(decltype(adl_magic_enum_define_range(E{}))::min, decltype(adl_magic_enum_define_range(E{}))::max); + constexpr inline auto prefix_length_or_zero::prefix_length)>> = std::size_t{ customize::enum_range::prefix_length }; } namespace customize { -template -struct adl_info { static_assert(sizeof...(Vs) && !sizeof...(Vs), "adl_info parameter types must be either 2 ints exactly or 1 bool for the is_flgas"); }; - -template -struct adl_info { - static constexpr int min = Min; - static constexpr int max = Max; +template +struct adl_info_holder { + constexpr static int max = Max; + constexpr static int min = Min; + constexpr static bool is_flags =IsFlags; + constexpr static std::size_t prefix_length = PrefixLength; + + template + constexpr static adl_info_holder minmax() { return {};} + template + constexpr static adl_info_holder flag() { return {};} + template + constexpr static adl_info_holder prefix() { return {};} }; -template -struct adl_info { - static constexpr bool is_flags = IsFlags; -}; +adl_info_holder<> adl_info() +{ + return {}; +} // Enum value must be in range [MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX]. By default MAGIC_ENUM_RANGE_MIN = -128, MAGIC_ENUM_RANGE_MAX = 127. // If need another range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MIN and MAGIC_ENUM_RANGE_MAX. // If need another range for specific enum type, add specialization enum_range for necessary enum type. -template +template struct enum_range { static constexpr int min = MAGIC_ENUM_RANGE_MIN; static constexpr int max = MAGIC_ENUM_RANGE_MAX; }; template -struct enum_range < E, decltype(void(adl_magic_enum_define_range(E{}))) > { - static constexpr int min = detail::has_minmax_adl.first; - static constexpr int max = detail::has_minmax_adl.second; - static constexpr bool is_flags = detail::has_is_flags_adl; - - static_assert(is_flags || min != MAGIC_ENUM_RANGE_MIN || max != MAGIC_ENUM_RANGE_MAX, - "adl_magic_enum_define_range is declared for this enum but does not define any constants.\n" - "be sure that the member names are static and are not mispelled."); -}; +struct enum_range +: decltype(adl_magic_enum_define_range(E{})) {}; static_assert(MAGIC_ENUM_RANGE_MAX > MAGIC_ENUM_RANGE_MIN, "MAGIC_ENUM_RANGE_MAX must be greater than MAGIC_ENUM_RANGE_MIN."); @@ -310,6 +307,9 @@ class static_str { MAGIC_ENUM_ASSERT(str.size_ == N); } + constexpr explicit static_str(const char* const str) noexcept : static_str{ str, std::make_integer_sequence{} } { + } + constexpr explicit static_str(string_view str) noexcept : static_str{str.data(), std::make_integer_sequence{}} { MAGIC_ENUM_ASSERT(str.size() == N); } @@ -658,7 +658,7 @@ constexpr auto enum_name() noexcept { #else constexpr auto name = n(); #endif - return static_str{name}; + return static_str>{name.str_ + prefix_length_or_zero}; } else { static_assert(always_false_v, "magic_enum::customize invalid."); } diff --git a/test/test.cpp b/test/test.cpp index a775f6a..de1a73a 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -105,23 +105,37 @@ namespace We::Need::To::Go::Deeper { auto adl_magic_enum_define_range(Dimension) { - enum { - min = 1000, - max = 1000 + 128 - } e{}; - return e; + return magic_enum::customize::adl_info().minmax<1000,1000+128>(); } - struct FlaggyData { - static constexpr bool is_flags = true; - }; - // not defined! - FlaggyData adl_magic_enum_define_range(Flaggy); + auto adl_magic_enum_define_range(Flaggy) + { + return magic_enum::customize::adl_info().flag(); + } } using We::Need::To::Go::Deeper::Dimension; using We::Need::To::Go::Deeper::Flaggy; +enum CStyleEnum { + CStyleEnum_A = -36, + CStyleEnum_B, + CStyleEnum_C, + CStyleEnum_D, + CStyleEnum_F, + CStyleEnum_G, + CStyleEnum_H = 36 +}; + +template <> +struct magic_enum::customize::enum_range { + static constexpr auto prefix_length = sizeof("CStyleEnum_") - 1; + static constexpr int min = -100; + static constexpr int max = 100; +}; + + + enum class BoolTest : bool { Yay, Nay }; using namespace magic_enum; @@ -142,6 +156,13 @@ TEST_CASE("enum_cast") { REQUIRE(enum_cast("theend", [](char lhs, char rhs) { return std::tolower(lhs) == std::tolower(rhs); }).value() == Dimension::TheEnd); REQUIRE_FALSE(enum_cast("Aether").has_value()); + constexpr auto cstyle = enum_cast("A"); + REQUIRE(cstyle.value() == CStyleEnum_A); + REQUIRE(enum_cast("H").value() == CStyleEnum_H); + REQUIRE_FALSE(enum_cast("CStyleEnum_H").has_value()); + REQUIRE(enum_cast("d", [](char lhs, char rhs) { return std::tolower(lhs) == std::tolower(rhs); }) == CStyleEnum_D); + REQUIRE_FALSE(enum_cast("Q").has_value()); + constexpr auto no = enum_cast("one"); REQUIRE(no.value() == Numbers::one); REQUIRE(enum_cast("two").value() == Numbers::two); diff --git a/test/test_flags.cpp b/test/test_flags.cpp index 6d889b1..964bfe2 100644 --- a/test/test_flags.cpp +++ b/test/test_flags.cpp @@ -57,7 +57,10 @@ namespace Namespace { three = 1 << 3, many = 1 << 30, }; - magic_enum::customize::adl_info adl_magic_enum_define_range(Numbers); + auto adl_magic_enum_define_range(Numbers) + { + return magic_enum::customize::adl_info().flag(); + } } using Namespace::Numbers; @@ -91,6 +94,24 @@ struct magic_enum::customize::enum_range { static constexpr bool is_flags = true; }; +enum CStyleFlags { + CStyleFlags_A = 1 << 0, + CStyleFlags_B = 1 << 1, + CStyleFlags_C = 1 << 2, + CStyleFlags_D = 1 << 3, + CStyleFlags_E = 1 << 4, + CStyleFlags_F = 1 << 5, + CStyleFlags_G = 1 << 6, + CStyleFlags_H = 1 << 7, + CStyleFlags_I = 1 << 8, +}; + +template <> +struct magic_enum::customize::enum_range { + static constexpr bool is_flags = true; + static constexpr auto prefix_length = sizeof("CStyleFlags_")-1; +}; + #include #include @@ -117,6 +138,13 @@ TEST_CASE("enum_cast") { REQUIRE_FALSE(enum_flags_cast("GREEN|RED|None").has_value()); REQUIRE_FALSE(enum_flags_cast("None").has_value()); + REQUIRE(enum_flags_cast("A|B|C|D").value() == (CStyleFlags_A | CStyleFlags_B | CStyleFlags_C | CStyleFlags_D)); + REQUIRE(enum_flags_cast("a|e|f", [](char lhs, char rhs) { return std::tolower(lhs) == std::tolower(rhs); }).value() == (CStyleFlags_A | CStyleFlags_E | CStyleFlags_F)); + REQUIRE_FALSE(enum_flags_cast("blue|E|F|C", [](char lhs, char rhs) { return std::tolower(lhs) == std::tolower(rhs); }).has_value()); + REQUIRE(enum_flags_cast("H|I|F|F|F").value() == (CStyleFlags_H | CStyleFlags_I | CStyleFlags_F)); + REQUIRE(enum_flags_cast("E|B|C|A").value() == (CStyleFlags_A | CStyleFlags_B | CStyleFlags_C | CStyleFlags_E)); + + constexpr auto no = enum_cast("one"); REQUIRE(no.value() == Numbers::one); REQUIRE(enum_cast("two").value() == Numbers::two); @@ -504,6 +532,8 @@ TEST_CASE("enum_flags_name") { REQUIRE(enum_flags_name(number::four) == "four"); REQUIRE(nto_name == "one|three"); REQUIRE(enum_flags_name(static_cast(0)).empty()); + + REQUIRE(enum_flags_name(CStyleFlags_A | CStyleFlags_B | CStyleFlags_C | CStyleFlags_D) == "A|B|C|D"); } TEST_CASE("enum_names") {