From d642b05dcbb4825470cf5afb4de2d36cafedf7b1 Mon Sep 17 00:00:00 2001 From: ZXShady <153229951+ZXShady@users.noreply.github.com> Date: Wed, 11 Jun 2025 20:00:01 +0300 Subject: [PATCH] Add adl_ranges (#413) Co-authored-by: lsemprini <17140216+lsemprini@users.noreply.github.com> --- doc/limitations.md | 52 +++++++++++++++++++++++++++++++ doc/reference.md | 26 ++++++++++++++++ include/magic_enum/magic_enum.hpp | 48 ++++++++++++++++++++++++++-- test/test.cpp | 40 ++++++++++++++++++++++++ test/test_flags.cpp | 22 ++++++------- 5 files changed, 174 insertions(+), 14 deletions(-) diff --git a/doc/limitations.md b/doc/limitations.md index 7b57ef1..6e9bf3d 100644 --- a/doc/limitations.md +++ b/doc/limitations.md @@ -23,6 +23,32 @@ * 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); + } + ``` + +* 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); + } + ``` + ## Enum Range * Enum values must be in the range `[MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX]`. @@ -52,6 +78,32 @@ }; ``` +* 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 6eb4a3c..7d64103 100644 --- a/doc/reference.md +++ b/doc/reference.md @@ -512,6 +512,32 @@ 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); + } + ``` + +* 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); + } + ``` + ## `is_unscoped_enum` ```cpp diff --git a/include/magic_enum/magic_enum.hpp b/include/magic_enum/magic_enum.hpp index 7491fae..b6cd61e 100644 --- a/include/magic_enum/magic_enum.hpp +++ b/include/magic_enum/magic_enum.hpp @@ -163,15 +163,57 @@ static_assert([] { return true; } (), "magic_enum::customize wchar_t is not compatible with ASCII."); +namespace detail { + template + constexpr inline bool has_is_flags_adl = false; + + 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); +} + + + 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 { + static constexpr bool is_flags = IsFlags; +}; + // 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; + 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."); }; static_assert(MAGIC_ENUM_RANGE_MAX > MAGIC_ENUM_RANGE_MIN, "MAGIC_ENUM_RANGE_MAX must be greater than MAGIC_ENUM_RANGE_MIN."); diff --git a/test/test.cpp b/test/test.cpp index 9d26eab..a775f6a 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -99,6 +99,29 @@ struct magic_enum::customize::enum_range { static constexpr int max = 64; }; +namespace We::Need::To::Go::Deeper { + enum class Dimension : short { Overworld = 1000, Nether, TheEnd = Overworld + 128 }; + enum class Flaggy : std::uint64_t { Flag0 = 1 << 0, Flag32 = std::uint64_t(1) << 32 }; + + auto adl_magic_enum_define_range(Dimension) + { + enum { + min = 1000, + max = 1000 + 128 + } e{}; + return e; + } + + struct FlaggyData { + static constexpr bool is_flags = true; + }; + + // not defined! + FlaggyData adl_magic_enum_define_range(Flaggy); +} +using We::Need::To::Go::Deeper::Dimension; +using We::Need::To::Go::Deeper::Flaggy; + enum class BoolTest : bool { Yay, Nay }; using namespace magic_enum; @@ -113,6 +136,12 @@ TEST_CASE("enum_cast") { REQUIRE(enum_cast("blue", [](char lhs, char rhs) { return std::tolower(lhs) == std::tolower(rhs); }).value() == Color::BLUE); REQUIRE_FALSE(enum_cast("None").has_value()); + constexpr auto dim = enum_cast("Nether"); + REQUIRE(dim.value() == Dimension::Nether); + REQUIRE(enum_cast("Nether").value() == Dimension::Nether); + 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 no = enum_cast("one"); REQUIRE(no.value() == Numbers::one); REQUIRE(enum_cast("two").value() == Numbers::two); @@ -427,6 +456,13 @@ TEST_CASE("enum_values") { constexpr auto& s6 = enum_values(); REQUIRE(s6 == std::array{{MaxUsedAsInvalid::ONE, MaxUsedAsInvalid::TWO}}); + + constexpr auto& s7 = enum_values(); + REQUIRE(s7 == std::array{{Dimension::Overworld, Dimension::Nether, Dimension::TheEnd}}); + + constexpr auto& s8 = enum_values(); + REQUIRE(s8 == std::array{{Flaggy::Flag0, Flaggy::Flag32}}); + } TEST_CASE("enum_count") { @@ -932,6 +968,10 @@ TEST_CASE("extrema") { REQUIRE(magic_enum::detail::reflected_min>() == 0); REQUIRE(magic_enum::detail::min_v> == 0); + REQUIRE(magic_enum::customize::enum_range::min == 1000); + REQUIRE(magic_enum::customize::enum_range::max == 1000 + 128); + REQUIRE_FALSE(magic_enum::customize::enum_range::is_flags); + REQUIRE(magic_enum::customize::enum_range::min == MAGIC_ENUM_RANGE_MIN); REQUIRE(magic_enum::detail::reflected_min>() == MAGIC_ENUM_RANGE_MIN); REQUIRE(magic_enum::detail::min_v> == -12); diff --git a/test/test_flags.cpp b/test/test_flags.cpp index d7f01ec..6d889b1 100644 --- a/test/test_flags.cpp +++ b/test/test_flags.cpp @@ -49,17 +49,17 @@ struct magic_enum::customize::enum_range { static constexpr bool is_flags = true; }; -enum class Numbers : int { - none = 0, - one = 1 << 1, - two = 1 << 2, - three = 1 << 3, - many = 1 << 30, -}; -template <> -struct magic_enum::customize::enum_range { - static constexpr bool is_flags = true; -}; +namespace Namespace { + enum class Numbers : int { + none = 0, + one = 1 << 1, + two = 1 << 2, + three = 1 << 3, + many = 1 << 30, + }; + magic_enum::customize::adl_info adl_magic_enum_define_range(Numbers); +} +using Namespace::Numbers; enum Directions : std::uint64_t { NoDirection = 0,