Skip to content

Commit 76481d3

Browse files
authored
Merge pull request #24 from rsl-org/feature/enum
enum utilities
2 parents a1ae4b2 + 6eeaa0c commit 76481d3

6 files changed

Lines changed: 466 additions & 1 deletion

File tree

include/rsl/enum

Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
#pragma once
2+
#include <meta>
3+
#include <ranges>
4+
#include <type_traits>
5+
#include <rsl/meta_traits>
6+
7+
namespace rsl {
8+
inline namespace annotations {
9+
struct FlagEnumTag {};
10+
constexpr inline FlagEnumTag flag_enum;
11+
12+
} // namespace annotations
13+
14+
template <typename T>
15+
concept is_flag_enum =
16+
std::is_scoped_enum_v<T> && meta::has_annotation(^^T, ^^annotations::FlagEnumTag);
17+
18+
template <typename T>
19+
concept fixed_enum = std::is_enum_v<T> and requires { T{0}; };
20+
21+
consteval bool has_fixed_underlying_type(std::meta::info r) {
22+
return is_type(r) and is_enum_type(r) and extract<bool>(substitute(^^fixed_enum, {r}));
23+
}
24+
25+
template <is_flag_enum E>
26+
constexpr bool has_flag(E flags, E needle) {
27+
using U = std::underlying_type_t<E>;
28+
return (static_cast<U>(flags) & static_cast<U>(needle)) == static_cast<U>(needle);
29+
}
30+
31+
template <is_flag_enum E>
32+
constexpr bool has_flag(E flags, std::underlying_type_t<E> needle) {
33+
using U = std::underlying_type_t<E>;
34+
return (static_cast<U>(flags) & needle) == needle;
35+
}
36+
37+
template <typename T>
38+
requires std::is_enum_v<T>
39+
constexpr bool is_named_enumerator(T value) {
40+
constexpr static auto named_enumerators =
41+
define_static_array(enumerators_of(^^T) | std::views::transform([](std::meta::info r) {
42+
return extract<T>(constant_of(r));
43+
}));
44+
return std::ranges::contains(named_enumerators, value);
45+
}
46+
47+
namespace _impl {
48+
template <typename E>
49+
struct enum_range {
50+
using underlying = std::underlying_type_t<E>;
51+
struct Range {
52+
underlying min;
53+
underlying max;
54+
};
55+
56+
private:
57+
static consteval Range get_enumerator_range() {
58+
auto enumerators = enumerators_of(^^E);
59+
if (enumerators.empty()) {
60+
return {0, 0};
61+
} else {
62+
auto transformed = enumerators | std::views::transform(std::meta::extract<E>);
63+
auto [min_it, max_it] = std::ranges::minmax_element(transformed);
64+
return {static_cast<underlying>(*min_it), static_cast<underlying>(*max_it)};
65+
}
66+
}
67+
68+
static constexpr int get_bit_width() {
69+
using unsigned_underlying = std::make_unsigned_t<underlying>;
70+
71+
if (range.min < 0) {
72+
auto min_val = static_cast<unsigned_underlying>(~range.min);
73+
auto max_val = static_cast<unsigned_underlying>(range.max < 0 ? ~range.max : range.max);
74+
return 1 + std::max(std::bit_width(min_val), std::bit_width(max_val));
75+
}
76+
return std::max(1, std::bit_width(unsigned_underlying(range.max)));
77+
}
78+
79+
public:
80+
static constexpr auto range = get_enumerator_range();
81+
static constexpr auto bit_width = get_bit_width();
82+
static constexpr auto min_value = range.min < 0 ? -(1ULL << (bit_width - 1)) : 0;
83+
static constexpr auto max_value = range.min < 0 ? -(min_value + 1)
84+
: std::numeric_limits<uint64_t>::max() >>
85+
(bit_width >= 64 ? 0 : 64 - bit_width);
86+
};
87+
} // namespace _impl
88+
89+
template <typename T>
90+
struct numeric_limits;
91+
92+
template <typename T>
93+
requires std::numeric_limits<T>::is_specialized
94+
struct numeric_limits<T> : std::numeric_limits<T> {};
95+
96+
template <typename E>
97+
requires std::is_enum_v<E> && (not fixed_enum<E>)
98+
struct numeric_limits<E> {
99+
using type = E;
100+
static constexpr const bool is_specialized = false;
101+
[[nodiscard]] static constexpr type min() noexcept {
102+
return type(_impl::enum_range<E>::min_value);
103+
}
104+
[[nodiscard]] static constexpr type max() noexcept {
105+
return type(_impl::enum_range<E>::max_value);
106+
}
107+
[[nodiscard]] static constexpr type lowest() noexcept { return min(); }
108+
109+
static constexpr const int digits = _impl::enum_range<E>::bit_width;
110+
static constexpr const int digits10 = digits * 3 / 10;
111+
static constexpr const int max_digits10 = 0;
112+
static constexpr const bool is_signed = std::is_signed_v<std::underlying_type_t<E>>;
113+
static constexpr const bool is_integer = true;
114+
static constexpr const bool is_exact = true;
115+
static constexpr const int radix = 2;
116+
[[nodiscard]] static constexpr type epsilon() noexcept { return type(); }
117+
[[nodiscard]] static constexpr type round_error() noexcept { return type(); }
118+
119+
static constexpr const int min_exponent = 0;
120+
static constexpr const int min_exponent10 = 0;
121+
static constexpr const int max_exponent = 0;
122+
static constexpr const int max_exponent10 = 0;
123+
124+
static constexpr const bool has_infinity = false;
125+
static constexpr const bool has_quiet_NaN = false;
126+
static constexpr const bool has_signaling_NaN = false;
127+
[[deprecated]] static constexpr const std::float_denorm_style has_denorm = std::denorm_absent;
128+
[[deprecated]] static constexpr const bool has_denorm_loss = false;
129+
[[nodiscard]] static constexpr type infinity() noexcept { return type(); }
130+
[[nodiscard]] static constexpr type quiet_NaN() noexcept { return type(); }
131+
[[nodiscard]] static constexpr type signaling_NaN() noexcept { return type(); }
132+
[[nodiscard]] static constexpr type denorm_min() noexcept { return type(); }
133+
134+
static constexpr const bool is_iec559 = false;
135+
static constexpr const bool is_bounded = true;
136+
static constexpr const bool is_modulo = false;
137+
138+
static constexpr const bool traps = std::numeric_limits<std::underlying_type_t<E>>::traps;
139+
static constexpr const bool tinyness_before = false;
140+
static constexpr const std::float_round_style round_style = std::round_toward_zero;
141+
};
142+
143+
template <fixed_enum E>
144+
struct numeric_limits<E> : std::numeric_limits<std::underlying_type_t<E>> {};
145+
} // namespace rsl
146+
147+
#ifdef RSL_POISON_STD
148+
template <typename E>
149+
requires std::is_enum_v<E>
150+
struct std::numeric_limits<E> : rsl::numeric_limits<E> {};
151+
#endif
152+
153+
template <rsl::is_flag_enum E>
154+
constexpr E operator~(E v) noexcept {
155+
using U = std::underlying_type_t<E>;
156+
return static_cast<E>(~static_cast<U>(v));
157+
}
158+
159+
template <rsl::is_flag_enum E, std::convertible_to<std::underlying_type_t<E>> T>
160+
constexpr bool operator==(E lhs, T rhs) noexcept {
161+
using U = std::underlying_type_t<E>;
162+
return static_cast<U>(lhs) == static_cast<U>(rhs);
163+
}
164+
165+
template <rsl::is_flag_enum E, std::convertible_to<std::underlying_type_t<E>> T>
166+
constexpr bool operator==(T lhs, E rhs) noexcept {
167+
using U = std::underlying_type_t<E>;
168+
return static_cast<U>(lhs) == static_cast<U>(rhs);
169+
}
170+
171+
template <rsl::is_flag_enum E, std::convertible_to<std::underlying_type_t<E>> T>
172+
constexpr bool operator!=(E lhs, T rhs) noexcept {
173+
using U = std::underlying_type_t<E>;
174+
return static_cast<U>(lhs) != static_cast<U>(rhs);
175+
}
176+
177+
template <rsl::is_flag_enum E, std::convertible_to<std::underlying_type_t<E>> T>
178+
constexpr bool operator!=(T lhs, E rhs) noexcept {
179+
using U = std::underlying_type_t<E>;
180+
return static_cast<U>(lhs) != static_cast<U>(rhs);
181+
}
182+
183+
template <rsl::is_flag_enum E>
184+
constexpr E operator|(E lhs, E rhs) noexcept {
185+
using U = std::underlying_type_t<E>;
186+
return static_cast<E>(static_cast<U>(lhs) | static_cast<U>(rhs));
187+
}
188+
189+
template <rsl::is_flag_enum E>
190+
constexpr E operator&(E lhs, E rhs) noexcept {
191+
using U = std::underlying_type_t<E>;
192+
return static_cast<E>(static_cast<U>(lhs) & static_cast<U>(rhs));
193+
}
194+
195+
template <rsl::is_flag_enum E>
196+
constexpr E operator^(E lhs, E rhs) noexcept {
197+
using U = std::underlying_type_t<E>;
198+
return static_cast<E>(static_cast<U>(lhs) ^ static_cast<U>(rhs));
199+
}
200+
template <rsl::is_flag_enum E, std::convertible_to<std::underlying_type_t<E>> T>
201+
constexpr E operator|(E lhs, T rhs) noexcept {
202+
using U = std::underlying_type_t<E>;
203+
return static_cast<E>(static_cast<U>(lhs) | static_cast<U>(rhs));
204+
}
205+
206+
template <rsl::is_flag_enum E, std::convertible_to<std::underlying_type_t<E>> T>
207+
constexpr E operator&(E lhs, T rhs) noexcept {
208+
using U = std::underlying_type_t<E>;
209+
return static_cast<E>(static_cast<U>(lhs) & static_cast<U>(rhs));
210+
}
211+
212+
template <rsl::is_flag_enum E, std::convertible_to<std::underlying_type_t<E>> T>
213+
constexpr E operator^(E lhs, T rhs) noexcept {
214+
using U = std::underlying_type_t<E>;
215+
return static_cast<E>(static_cast<U>(lhs) ^ static_cast<U>(rhs));
216+
}
217+
218+
template <rsl::is_flag_enum E, std::convertible_to<std::underlying_type_t<E>> T>
219+
constexpr E operator|(T lhs, E rhs) noexcept {
220+
using U = std::underlying_type_t<E>;
221+
return static_cast<E>(static_cast<U>(lhs) | static_cast<U>(rhs));
222+
}
223+
224+
template <rsl::is_flag_enum E, std::convertible_to<std::underlying_type_t<E>> T>
225+
constexpr E operator&(T lhs, E rhs) noexcept {
226+
using U = std::underlying_type_t<E>;
227+
return static_cast<E>(static_cast<U>(lhs) & static_cast<U>(rhs));
228+
}
229+
230+
template <rsl::is_flag_enum E, std::convertible_to<std::underlying_type_t<E>> T>
231+
constexpr E operator^(T lhs, E rhs) noexcept {
232+
using U = std::underlying_type_t<E>;
233+
return static_cast<E>(static_cast<U>(lhs) ^ static_cast<U>(rhs));
234+
}
235+
236+
template <rsl::is_flag_enum E>
237+
constexpr E& operator|=(E& lhs, E rhs) noexcept {
238+
return lhs = lhs | rhs;
239+
}
240+
241+
template <rsl::is_flag_enum E>
242+
constexpr E& operator&=(E& lhs, E rhs) noexcept {
243+
return lhs = lhs & rhs;
244+
}
245+
246+
template <rsl::is_flag_enum E>
247+
constexpr E& operator^=(E& lhs, E rhs) noexcept {
248+
return lhs = lhs ^ rhs;
249+
}
250+
251+
template <rsl::is_flag_enum E, std::convertible_to<std::underlying_type_t<E>> T>
252+
constexpr E& operator|=(E& lhs, T rhs) noexcept {
253+
return lhs = lhs | rhs;
254+
}
255+
256+
template <rsl::is_flag_enum E, std::convertible_to<std::underlying_type_t<E>> T>
257+
constexpr E& operator&=(E& lhs, T rhs) noexcept {
258+
return lhs = lhs & rhs;
259+
}
260+
261+
template <rsl::is_flag_enum E, std::convertible_to<std::underlying_type_t<E>> T>
262+
constexpr E& operator^=(E& lhs, T rhs) noexcept {
263+
return lhs = lhs ^ rhs;
264+
}
265+
266+
template <rsl::is_flag_enum E, std::convertible_to<std::underlying_type_t<E>> T>
267+
constexpr T& operator|=(T& lhs, E rhs) noexcept {
268+
return lhs = static_cast<uint8_t>(lhs | rhs);
269+
}
270+
271+
template <rsl::is_flag_enum E, std::convertible_to<std::underlying_type_t<E>> T>
272+
constexpr T& operator&=(T& lhs, E rhs) noexcept {
273+
return lhs = static_cast<uint8_t>(lhs & rhs);
274+
}
275+
276+
template <rsl::is_flag_enum E, std::convertible_to<std::underlying_type_t<E>> T>
277+
constexpr T& operator^=(T& lhs, E rhs) noexcept {
278+
return lhs = static_cast<uint8_t>(lhs ^ rhs);
279+
}

test/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ add_subdirectory(serializer)
99
add_subdirectory(string_view)
1010
add_subdirectory(span)
1111
add_subdirectory(format)
12-
add_subdirectory(kwargs)
12+
add_subdirectory(kwargs)
13+
add_subdirectory(enum)

test/enum/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
target_sources(rsl-util-test PRIVATE
2+
operator.cpp
3+
utilities.cpp
4+
numeric_limits.cpp
5+
)

test/enum/numeric_limits.cpp

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#include <rsl/enum>
2+
#include <gtest/gtest.h>
3+
4+
namespace {}
5+
6+
TEST(NumericLimits, FixedEnum) {
7+
enum T1 : int { A1 = -1 };
8+
ASSERT_TRUE(rsl::has_fixed_underlying_type(^^T1));
9+
ASSERT_EQ(rsl::numeric_limits<T1>::min(), std::numeric_limits<int>::min());
10+
ASSERT_EQ(rsl::numeric_limits<T1>::max(), std::numeric_limits<int>::max());
11+
12+
enum class T2 { A2 = -1 };
13+
ASSERT_TRUE(rsl::has_fixed_underlying_type(^^T2));
14+
ASSERT_EQ(rsl::numeric_limits<T2>::min(), std::numeric_limits<int>::min());
15+
ASSERT_EQ(rsl::numeric_limits<T2>::max(), std::numeric_limits<int>::max());
16+
17+
enum class T3 {};
18+
ASSERT_TRUE(rsl::has_fixed_underlying_type(^^T3));
19+
ASSERT_EQ(rsl::numeric_limits<T3>::min(), std::numeric_limits<int>::min());
20+
ASSERT_EQ(rsl::numeric_limits<T3>::max(), std::numeric_limits<int>::max());
21+
22+
enum T4 : int {};
23+
ASSERT_TRUE(rsl::has_fixed_underlying_type(^^T4));
24+
ASSERT_EQ(rsl::numeric_limits<T4>::min(), std::numeric_limits<int>::min());
25+
ASSERT_EQ(rsl::numeric_limits<T4>::max(), std::numeric_limits<int>::max());
26+
27+
enum T5 : short {};
28+
ASSERT_TRUE(rsl::has_fixed_underlying_type(^^T5));
29+
ASSERT_EQ(rsl::numeric_limits<T5>::min(), std::numeric_limits<short>::min());
30+
ASSERT_EQ(rsl::numeric_limits<T5>::max(), std::numeric_limits<short>::max());
31+
32+
enum class T6 : short {};
33+
ASSERT_TRUE(rsl::has_fixed_underlying_type(^^T6));
34+
ASSERT_EQ(rsl::numeric_limits<T6>::min(), std::numeric_limits<short>::min());
35+
ASSERT_EQ(rsl::numeric_limits<T6>::max(), std::numeric_limits<short>::max());
36+
}
37+
38+
TEST(NumericLimits, MinWidthEnum) {
39+
enum T1 {};
40+
ASSERT_FALSE(rsl::has_fixed_underlying_type(^^T1));
41+
ASSERT_EQ(rsl::numeric_limits<T1>::min(), 0);
42+
ASSERT_EQ(rsl::numeric_limits<T1>::max(), 1);
43+
44+
enum T2 { A1 = 5 };
45+
ASSERT_FALSE(rsl::has_fixed_underlying_type(^^T2));
46+
ASSERT_EQ(rsl::numeric_limits<T2>::min(), 0);
47+
ASSERT_EQ(rsl::numeric_limits<T2>::max(), 7);
48+
49+
enum T3 { A = -1, B = 1 };
50+
ASSERT_FALSE(rsl::has_fixed_underlying_type(^^T3));
51+
ASSERT_EQ(rsl::numeric_limits<T3>::min(), -2);
52+
ASSERT_EQ(rsl::numeric_limits<T3>::max(), 1);
53+
54+
enum T4 { A4 = -5 };
55+
ASSERT_FALSE(rsl::has_fixed_underlying_type(^^T4));
56+
ASSERT_EQ(rsl::numeric_limits<T4>::min(), -8);
57+
ASSERT_EQ(rsl::numeric_limits<T4>::max(), 7);
58+
59+
enum T5 { A5 = -9223372036854775807LL };
60+
ASSERT_FALSE(rsl::has_fixed_underlying_type(^^T5));
61+
ASSERT_EQ(rsl::numeric_limits<T5>::min(), -9223372036854775807LL - 1);
62+
ASSERT_EQ(rsl::numeric_limits<T5>::max(), 9223372036854775807LL);
63+
64+
enum T6 { A6 = -1 };
65+
ASSERT_FALSE(rsl::has_fixed_underlying_type(^^T6));
66+
ASSERT_EQ(rsl::numeric_limits<T6>::min(), -1);
67+
ASSERT_EQ(rsl::numeric_limits<T6>::max(), 0);
68+
69+
enum T7 { A7 = -1, B7 = -5 };
70+
ASSERT_FALSE(rsl::has_fixed_underlying_type(^^T7));
71+
ASSERT_EQ(rsl::numeric_limits<T7>::min(), -8);
72+
ASSERT_EQ(rsl::numeric_limits<T7>::max(), 7);
73+
}

0 commit comments

Comments
 (0)