Skip to content

Commit d642b05

Browse files
ZXShadylsemprini
andauthored
Add adl_ranges (#413)
Co-authored-by: lsemprini <17140216+lsemprini@users.noreply.github.com>
1 parent 513e606 commit d642b05

File tree

5 files changed

+174
-14
lines changed

5 files changed

+174
-14
lines changed

doc/limitations.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,32 @@
2323
2424
* If an enum is declared as a flag enum, its zero value will not be reflected.
2525
26+
* 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<my_enum_type>`
27+
```cpp
28+
namespace Deeply::Nested::Namespace {
29+
enum class my_enum_type { ... };
30+
struct my_adl_info_struct {
31+
static constexpr bool is_flags = true;
32+
// you can also set min and max here (see Enum Range below)
33+
// static constexpr int min = ...;
34+
// static constexpr int max = ...;
35+
};
36+
// - magic_enum will find this function by ADL
37+
// - no need to ever define this function
38+
my_adl_info_struct adl_magic_enum_define_range(my_enum_type);
39+
}
40+
```
41+
42+
* 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<is_flags_bool>` to avoid having to define `my_adl_info_struct` in your code:
43+
```cpp
44+
namespace Deeply::Nested::Namespace {
45+
enum class my_enum_type { ... };
46+
// - magic_enum will find this function by ADL
47+
// - no need to ever define this function
48+
magic_enum::customize::adl_info<true> adl_magic_enum_define_range(my_enum_type);
49+
}
50+
```
51+
2652
## Enum Range
2753
2854
* Enum values must be in the range `[MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX]`.
@@ -52,6 +78,32 @@
5278
};
5379
```
5480

81+
* 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<my_enum_type>`
82+
```cpp
83+
namespace Deeply::Nested::Namespace {
84+
enum class my_enum_type { ... };
85+
struct my_adl_info_struct {
86+
static constexpr int min = 100;
87+
static constexpr int max = 300;
88+
// you can also set is_flags here
89+
// static constexpr bool is_flags = true;
90+
};
91+
// - magic_enum will find this function by ADL
92+
// - no need to ever define this function
93+
my_adl_info_struct adl_magic_enum_define_range(my_enum_type);
94+
}
95+
```
96+
97+
* 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<min_int, max_int>` to avoid having to define `my_adl_info_struct` in your code:
98+
```cpp
99+
namespace Deeply::Nested::Namespace {
100+
enum class my_enum_type { ... };
101+
// - magic_enum will find this function by ADL
102+
// - no need to ever define this function
103+
magic_enum::customize::adl_info<100 /*min*/, 300 /*max*/> adl_magic_enum_define_range(my_enum_type);
104+
}
105+
```
106+
55107
## Aliasing
56108

57109
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.

doc/reference.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,32 @@ constexpr bool enum_flags_contains(string_view value, BinaryPredicate p) noexcep
512512
magic_enum::enum_flags_test_any(Left|Down|Right, Down|Right); // -> "true"
513513
```
514514
515+
* 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<my_enum_type>`
516+
```cpp
517+
namespace Deeply::Nested::Namespace {
518+
enum class my_enum_type { ... };
519+
struct my_adl_info_struct {
520+
static constexpr bool is_flags = true;
521+
// you can also set min and max here (see Limitations document)
522+
// static constexpr int min = ...;
523+
// static constexpr int max = ...;
524+
};
525+
// - magic_enum will find this function by ADL
526+
// - no need to ever define this function
527+
my_adl_info_struct adl_magic_enum_define_range(my_enum_type);
528+
}
529+
```
530+
531+
* 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<is_flags_bool>` to avoid having to define `my_adl_info_struct` in your code:
532+
```cpp
533+
namespace Deeply::Nested::Namespace {
534+
enum class my_enum_type { ... };
535+
// - magic_enum will find this function by ADL
536+
// - no need to ever define this function
537+
magic_enum::customize::adl_info<true> adl_magic_enum_define_range(my_enum_type);
538+
}
539+
```
540+
515541
## `is_unscoped_enum`
516542
517543
```cpp

include/magic_enum/magic_enum.hpp

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,15 +163,57 @@ static_assert([] {
163163
return true;
164164
} (), "magic_enum::customize wchar_t is not compatible with ASCII.");
165165

166+
namespace detail {
167+
template<typename E, typename = void>
168+
constexpr inline bool has_is_flags_adl = false;
169+
170+
template<typename E>
171+
constexpr inline bool has_is_flags_adl < E, std::void_t<decltype(decltype(adl_magic_enum_define_range(E{}))::is_flags) > > = decltype(adl_magic_enum_define_range(E{}))::is_flags;
172+
173+
template<typename E, typename = void>
174+
constexpr inline auto has_minmax_adl = std::pair<int, int>(MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX);
175+
176+
template<typename E>
177+
constexpr inline auto has_minmax_adl < E, std::void_t<decltype(decltype(adl_magic_enum_define_range(E{}))::max), decltype(decltype(adl_magic_enum_define_range(E{}))::max) >> =
178+
std::pair<int, int>(decltype(adl_magic_enum_define_range(E{}))::min, decltype(adl_magic_enum_define_range(E{}))::max);
179+
}
180+
181+
182+
166183
namespace customize {
167184

185+
template<auto... Vs>
186+
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"); };
187+
188+
template<int Min, int Max>
189+
struct adl_info<Min, Max> {
190+
static constexpr int min = Min;
191+
static constexpr int max = Max;
192+
};
193+
194+
template<bool IsFlags>
195+
struct adl_info<IsFlags> {
196+
static constexpr bool is_flags = IsFlags;
197+
};
198+
168199
// 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.
169200
// If need another range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MIN and MAGIC_ENUM_RANGE_MAX.
170201
// If need another range for specific enum type, add specialization enum_range for necessary enum type.
171-
template <typename E>
202+
template <typename E, typename = void>
172203
struct enum_range {
173-
static constexpr int min = MAGIC_ENUM_RANGE_MIN;
174-
static constexpr int max = MAGIC_ENUM_RANGE_MAX;
204+
static constexpr int min = MAGIC_ENUM_RANGE_MIN;
205+
static constexpr int max = MAGIC_ENUM_RANGE_MAX;
206+
};
207+
208+
template <typename E>
209+
struct enum_range < E, decltype(void(adl_magic_enum_define_range(E{}))) > {
210+
static constexpr int min = detail::has_minmax_adl<E>.first;
211+
static constexpr int max = detail::has_minmax_adl<E>.second;
212+
static constexpr bool is_flags = detail::has_is_flags_adl<E>;
213+
214+
static_assert(is_flags || min != MAGIC_ENUM_RANGE_MIN || max != MAGIC_ENUM_RANGE_MAX,
215+
"adl_magic_enum_define_range is declared for this enum but does not define any constants.\n"
216+
"be sure that the member names are static and are not mispelled.");
175217
};
176218

177219
static_assert(MAGIC_ENUM_RANGE_MAX > MAGIC_ENUM_RANGE_MIN, "MAGIC_ENUM_RANGE_MAX must be greater than MAGIC_ENUM_RANGE_MIN.");

test/test.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,29 @@ struct magic_enum::customize::enum_range<Binary> {
9999
static constexpr int max = 64;
100100
};
101101

102+
namespace We::Need::To::Go::Deeper {
103+
enum class Dimension : short { Overworld = 1000, Nether, TheEnd = Overworld + 128 };
104+
enum class Flaggy : std::uint64_t { Flag0 = 1 << 0, Flag32 = std::uint64_t(1) << 32 };
105+
106+
auto adl_magic_enum_define_range(Dimension)
107+
{
108+
enum {
109+
min = 1000,
110+
max = 1000 + 128
111+
} e{};
112+
return e;
113+
}
114+
115+
struct FlaggyData {
116+
static constexpr bool is_flags = true;
117+
};
118+
119+
// not defined!
120+
FlaggyData adl_magic_enum_define_range(Flaggy);
121+
}
122+
using We::Need::To::Go::Deeper::Dimension;
123+
using We::Need::To::Go::Deeper::Flaggy;
124+
102125
enum class BoolTest : bool { Yay, Nay };
103126

104127
using namespace magic_enum;
@@ -113,6 +136,12 @@ TEST_CASE("enum_cast") {
113136
REQUIRE(enum_cast<Color>("blue", [](char lhs, char rhs) { return std::tolower(lhs) == std::tolower(rhs); }).value() == Color::BLUE);
114137
REQUIRE_FALSE(enum_cast<Color>("None").has_value());
115138

139+
constexpr auto dim = enum_cast<Dimension>("Nether");
140+
REQUIRE(dim.value() == Dimension::Nether);
141+
REQUIRE(enum_cast<Dimension&>("Nether").value() == Dimension::Nether);
142+
REQUIRE(enum_cast<Dimension>("theend", [](char lhs, char rhs) { return std::tolower(lhs) == std::tolower(rhs); }).value() == Dimension::TheEnd);
143+
REQUIRE_FALSE(enum_cast<Dimension>("Aether").has_value());
144+
116145
constexpr auto no = enum_cast<Numbers>("one");
117146
REQUIRE(no.value() == Numbers::one);
118147
REQUIRE(enum_cast<Numbers>("two").value() == Numbers::two);
@@ -427,6 +456,13 @@ TEST_CASE("enum_values") {
427456

428457
constexpr auto& s6 = enum_values<MaxUsedAsInvalid>();
429458
REQUIRE(s6 == std::array<MaxUsedAsInvalid, 2>{{MaxUsedAsInvalid::ONE, MaxUsedAsInvalid::TWO}});
459+
460+
constexpr auto& s7 = enum_values<Dimension>();
461+
REQUIRE(s7 == std::array<Dimension, 3>{{Dimension::Overworld, Dimension::Nether, Dimension::TheEnd}});
462+
463+
constexpr auto& s8 = enum_values<Flaggy>();
464+
REQUIRE(s8 == std::array<Flaggy, 2>{{Flaggy::Flag0, Flaggy::Flag32}});
465+
430466
}
431467

432468
TEST_CASE("enum_count") {
@@ -932,6 +968,10 @@ TEST_CASE("extrema") {
932968
REQUIRE(magic_enum::detail::reflected_min<BadColor, as_common<>>() == 0);
933969
REQUIRE(magic_enum::detail::min_v<BadColor, as_common<>> == 0);
934970

971+
REQUIRE(magic_enum::customize::enum_range<Dimension>::min == 1000);
972+
REQUIRE(magic_enum::customize::enum_range<Dimension>::max == 1000 + 128);
973+
REQUIRE_FALSE(magic_enum::customize::enum_range<Dimension>::is_flags);
974+
935975
REQUIRE(magic_enum::customize::enum_range<Color>::min == MAGIC_ENUM_RANGE_MIN);
936976
REQUIRE(magic_enum::detail::reflected_min<Color, as_common<>>() == MAGIC_ENUM_RANGE_MIN);
937977
REQUIRE(magic_enum::detail::min_v<Color, as_common<>> == -12);

test/test_flags.cpp

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,17 @@ struct magic_enum::customize::enum_range<Color> {
4949
static constexpr bool is_flags = true;
5050
};
5151

52-
enum class Numbers : int {
53-
none = 0,
54-
one = 1 << 1,
55-
two = 1 << 2,
56-
three = 1 << 3,
57-
many = 1 << 30,
58-
};
59-
template <>
60-
struct magic_enum::customize::enum_range<Numbers> {
61-
static constexpr bool is_flags = true;
62-
};
52+
namespace Namespace {
53+
enum class Numbers : int {
54+
none = 0,
55+
one = 1 << 1,
56+
two = 1 << 2,
57+
three = 1 << 3,
58+
many = 1 << 30,
59+
};
60+
magic_enum::customize::adl_info<true> adl_magic_enum_define_range(Numbers);
61+
}
62+
using Namespace::Numbers;
6363

6464
enum Directions : std::uint64_t {
6565
NoDirection = 0,

0 commit comments

Comments
 (0)