1
0
Fork 0
mirror of https://github.com/Neargye/magic_enum.git synced 2026-01-09 23:34:23 +00:00

New enum-flags API (#120)

This commit is contained in:
Daniil Goncharov 2022-01-08 17:30:44 +02:00 committed by GitHub
parent 6a1db3b8b6
commit aecf0db795
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 336 additions and 477 deletions

View file

@ -197,8 +197,6 @@ enum class Color { RED = 2, BLUE = 4, GREEN = 8 };
* Before use, read the [limitations](doc/limitations.md) of functionality.
* For the small enum use the API from the namespace `magic_enum`, and for enum-flags use the API from the namespace `magic_enum::flags`.
## Integration
* You should add the required file [magic_enum.hpp](include/magic_enum.hpp).

View file

@ -7,6 +7,13 @@
* Enum can't reflect if the enum is a forward declaration.
* For enum-flags add specialization `is_flags_enum` for necessary enum type. Specialization of `is_flags_enum` must be injected in `namespace magic_enum::customize`.
```cpp
enum class Directions { Up = 1 << 1, Down = 1 << 2, Right = 1 << 3, Left = 1 << 4 };
template <>
struct magic_enum::customize::is_flags_enum<Directions> : std::true_type {};
```
* 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 = 128`.
@ -28,14 +35,12 @@
enum class number { one = 100, two = 200, three = 300 };
namespace magic_enum::customize {
template <>
struct enum_range<number> {
struct magic_enum::customize::enum_range<number> {
static constexpr int min = 100;
static constexpr int max = 300;
// (max - min) must be less than UINT16_MIN.
};
} // namespace magic_enum
```
* `magic_enum` [won't work if a value is aliased](https://github.com/Neargye/magic_enum/issues/68). Work with enum-aliases is compiler-implementation-defined.

View file

@ -24,8 +24,6 @@
* To check is magic_enum supported compiler use macro `MAGIC_ENUM_SUPPORTED` or constexpr constant `magic_enum::is_magic_enum_supported`.</br>
If magic_enum used on unsupported compiler, occurs the compilation error. To suppress error define macro `MAGIC_ENUM_NO_CHECK_SUPPORT`.
* For the small enum use the API from the namespace `magic_enum`, and for enum-flags use the API from the namespace `magic_enum::flags`.
* To add custom enum or type names see the [example](../example/example_custom_name.cpp).
* To change the type of strings or ortional, use special macros:

View file

@ -26,15 +26,18 @@
#include <magic_enum.hpp>
enum class AnimalFlags : std::uint64_t { HasClaws = 1 << 10, CanFly = 1 << 20, EatsFish = 1 << 30, Endangered = std::uint64_t{1} << 40 };
// Add specialization `is_flags_enum` to force define that enum are flags.
// template <>
// struct magic_enum::customize::is_flags_enum<AnimalFlags> : std::true_type {};
int main() {
// Enum-flags variable to string name.
AnimalFlags f1 = AnimalFlags::Endangered;
auto f1_name = magic_enum::flags::enum_name(f1);
auto f1_name = magic_enum::enum_name(f1);
std::cout << f1_name << std::endl; // Endangered
// String enum-flags name sequence.
constexpr auto names = magic_enum::flags::enum_names<AnimalFlags>();
constexpr auto names = magic_enum::enum_names<AnimalFlags>();
std::cout << "AnimalFlags names:";
for (const auto& n : names) {
std::cout << " " << n;
@ -43,33 +46,33 @@ int main() {
// AnimalFlags names: HasClaws CanFly EatsFish Endangered
// String name to enum-flags value.
auto f2 = magic_enum::flags::enum_cast<AnimalFlags>("EatsFish|CanFly");
auto f2 = magic_enum::enum_cast<AnimalFlags>("EatsFish|CanFly");
if (f2.has_value()) {
std::cout << "EatsFish = " << magic_enum::flags::enum_integer(f2.value()) << std::endl; // CanFly|EatsFish = 1074790400
std::cout << "EatsFish|CanFly = " << magic_enum::enum_integer(f2.value()) << std::endl; // CanFly|EatsFish = 1074790400
}
// Integer value to enum-flags value.
auto f3 = magic_enum::flags::enum_cast<AnimalFlags>(1073742848);
auto f3 = magic_enum::enum_cast<AnimalFlags>(1073742848);
if (f3.has_value()) {
std::cout << magic_enum::flags::enum_name(f3.value()) << " = " << magic_enum::flags::enum_integer(f3.value()) << std::endl; // HasClaws|EatsFish = 1073742848
std::cout << magic_enum::enum_flags_name(f3.value()) << " = " << magic_enum::enum_integer(f3.value()) << std::endl; // HasClaws|EatsFish = 1073742848
}
// Enum-flags value to integer value.
auto f4_integer = magic_enum::flags::enum_integer(AnimalFlags::HasClaws);
auto f4_integer = magic_enum::enum_integer(AnimalFlags::HasClaws);
std::cout << "HasClaws = " << f4_integer << std::endl; // HasClaws = 1024
using namespace magic_enum::flags::ostream_operators; // out-of-the-box ostream operator for enum-flags.
using namespace magic_enum::ostream_operators; // out-of-the-box ostream operator for enum-flags.
// Ostream operator for enum-flags.
std::cout << f1 << " " << f2 << " " << f3 << std::endl; // Endangered CanFly|EatsFish HasClaws|EatsFish
// Number of enum-flags values.
std::cout << "AnimalFlags enum size: " << magic_enum::flags::enum_count<AnimalFlags>() << std::endl; // AnimalFlags enum size: 4
std::cout << "AnimalFlags enum size: " << magic_enum::enum_count<AnimalFlags>() << std::endl; // AnimalFlags enum size: 4
// Indexed access to enum-flags value.
std::cout << "AnimalFlags[0] = " << magic_enum::flags::enum_value<AnimalFlags>(0) << std::endl; // AnimalFlags[0] = HasClaws
std::cout << "AnimalFlags[0] = " << magic_enum::enum_value<AnimalFlags>(0) << std::endl; // AnimalFlags[0] = HasClaws
// Enum-flags value sequence.
constexpr auto values = magic_enum::flags::enum_values<AnimalFlags>();
constexpr auto values = magic_enum::enum_values<AnimalFlags>();
std::cout << "AnimalFlags values:";
for (const auto f : values) {
std::cout << " " << f; // Ostream operator for enum-flags.
@ -77,16 +80,16 @@ int main() {
std::cout << std::endl;
// AnimalFlags sequence: HasClaws CanFly EatsFish Endangered
using namespace magic_enum::flags::bitwise_operators; // out-of-the-box bitwise operators for all enums.
using namespace magic_enum::bitwise_operators; // out-of-the-box bitwise operators for all enums.
// Support operators: ~, |, &, ^, |=, &=, ^=.
AnimalFlags flag = AnimalFlags::HasClaws | AnimalFlags::CanFly;
std::cout << flag << std::endl; // HasClaws|CanFly
// Enum-flags pair (value, string name) sequence.
constexpr auto entries = magic_enum::flags::enum_entries<AnimalFlags>();
constexpr auto entries = magic_enum::enum_entries<AnimalFlags>();
std::cout << "AnimalFlags entries:";
for (const auto& e : entries) {
std::cout << " " << e.second << " = " << magic_enum::flags::enum_integer(e.first);
std::cout << " " << e.second << " = " << magic_enum::enum_integer(e.first);
}
std::cout << std::endl;
// AnimalFlags entries: AnimalFlags entries: HasClaws = 1024 CanFly = 1048576 EatsFish = 1073741824 Endangered = 1099511627776

View file

@ -142,6 +142,9 @@ constexpr string_view enum_name(E) noexcept {
return {};
}
template <typename E>
struct is_flags_enum : std::false_type {};
} // namespace magic_enum::customize
namespace detail {
@ -283,9 +286,9 @@ constexpr bool cmp_less(L lhs, R rhs) noexcept {
if constexpr (std::is_signed_v<L> == std::is_signed_v<R>) {
// If same signedness (both signed or both unsigned).
return lhs < rhs;
} else if constexpr (std::is_same_v<L, bool>) { // bool special case due to msvc's C4804, C4018
} else if constexpr (std::is_same_v<L, bool>) { // bool special case
return static_cast<R>(lhs) < rhs;
} else if constexpr (std::is_same_v<R, bool>) { // bool special case due to msvc's C4804, C4018
} else if constexpr (std::is_same_v<R, bool>) { // bool special case
return lhs < static_cast<L>(rhs);
} else if constexpr (std::is_signed_v<R>) {
// If 'right' is negative, then result is 'false', otherwise cast & compare.
@ -306,13 +309,6 @@ constexpr I log2(I value) noexcept {
return ret;
}
template <typename I>
constexpr bool is_pow2(I x) noexcept {
static_assert(std::is_integral_v<I>, "magic_enum::detail::is_pow2 requires integral type.");
return x != 0 && (x & (x - 1)) == 0;
}
template <typename T>
inline constexpr bool is_enum_v = std::is_enum_v<T> && std::is_same_v<T, std::decay_t<T>>;
@ -368,11 +364,15 @@ constexpr bool is_valid() noexcept {
return n<E, static_cast<E>(V)>().size() != 0;
}
template <typename E, int O, bool IsFlags = false, typename U = std::underlying_type_t<E>>
template <typename E, int O, bool IsFlags, typename U = std::underlying_type_t<E>>
constexpr E value(std::size_t i) noexcept {
static_assert(is_enum_v<E>, "magic_enum::detail::value requires enum type.");
if constexpr (IsFlags) {
if constexpr (std::is_same_v<U, bool>) { // bool special case
static_assert(O == 0, "magic_enum::detail::value requires valid offset.");
return static_cast<E>(i);
} else if constexpr (IsFlags) {
return static_cast<E>(U{1} << static_cast<U>(static_cast<int>(i) + O));
} else {
return static_cast<E>(static_cast<int>(i) + O);
@ -417,10 +417,10 @@ constexpr int reflected_max() noexcept {
}
}
template <typename E, bool IsFlags = false>
template <typename E, bool IsFlags>
inline constexpr auto reflected_min_v = reflected_min<E, IsFlags>();
template <typename E, bool IsFlags = false>
template <typename E, bool IsFlags>
inline constexpr auto reflected_max_v = reflected_max<E, IsFlags>();
template <std::size_t N>
@ -467,124 +467,101 @@ constexpr auto values() noexcept {
return values<E, IsFlags, reflected_min_v<E, IsFlags>>(std::make_index_sequence<range_size>{});
}
template <typename E, bool IsFlags = false>
inline constexpr auto values_v = values<E, IsFlags>();
template <typename E, typename U = std::underlying_type_t<E>>
constexpr bool is_flags_enum() noexcept {
static_assert(is_enum_v<E>, "magic_enum::detail::is_flags_enum requires enum type.");
template <typename E, bool IsFlags = false, typename D = std::decay_t<E>>
using values_t = decltype((values_v<D, IsFlags>));
template <typename E, bool IsFlags = false>
inline constexpr auto count_v = values_v<E, IsFlags>.size();
template <typename E, bool IsFlags = false, typename U = std::underlying_type_t<E>>
inline constexpr auto min_v = (count_v<E, IsFlags> > 0) ? static_cast<U>(values_v<E, IsFlags>.front()) : U{0};
template <typename E, bool IsFlags = false, typename U = std::underlying_type_t<E>>
inline constexpr auto max_v = (count_v<E, IsFlags> > 0) ? static_cast<U>(values_v<E, IsFlags>.back()) : U{0};
template <typename E, bool IsFlags, typename U = std::underlying_type_t<E>>
constexpr std::size_t range_size() noexcept {
static_assert(is_enum_v<E>, "magic_enum::detail::range_size requires enum type.");
constexpr auto max = IsFlags ? log2(max_v<E, IsFlags>) : max_v<E, IsFlags>;
constexpr auto min = IsFlags ? log2(min_v<E, IsFlags>) : min_v<E, IsFlags>;
constexpr auto range_size = max - min + 1;
static_assert(range_size > 0, "magic_enum::enum_range requires valid size.");
static_assert(range_size < (std::numeric_limits<std::uint16_t>::max)(), "magic_enum::enum_range requires valid size.");
return static_cast<std::size_t>(range_size);
#if defined(MAGIC_ENUM_NO_CHECK_FLAGS)
return customize::is_flags_enum<E>::value;
#else
if constexpr (std::is_same_v<U, bool>) { // bool special case
return false;
} else if constexpr (customize::is_flags_enum<E>::value) {
return true;
} else {
constexpr auto flags_values = values<E, true>();
constexpr auto default_values = values<E, false>();
if (flags_values.size() == 0 || default_values.size() > flags_values.size()) {
return false;
}
for (std::size_t i = 0; i < default_values.size(); ++i) {
const auto v = static_cast<U>(default_values[i]);
if (v != 0 && (v & (v - 1)) != 0) {
return false;
}
}
return flags_values.size() > 0;
}
#endif
}
template <typename E, bool IsFlags = false>
inline constexpr auto range_size_v = range_size<E, IsFlags>();
template <typename E>
inline constexpr bool is_flags_v = is_flags_enum<E>();
template <typename E, bool IsFlags = false>
using index_t = std::conditional_t<range_size_v<E, IsFlags> < (std::numeric_limits<std::uint8_t>::max)(), std::uint8_t, std::uint16_t>;
template <typename E>
inline constexpr auto values_v = values<E, is_flags_v<E>>();
template <typename E, bool IsFlags = false>
inline constexpr auto invalid_index_v = (std::numeric_limits<index_t<E, IsFlags>>::max)();
template <typename E, typename D = std::decay_t<E>>
using values_t = decltype((values_v<D>));
template <typename E, bool IsFlags, std::size_t... I>
constexpr auto indexes(std::index_sequence<I...>) noexcept {
static_assert(is_enum_v<E>, "magic_enum::detail::indexes requires enum type.");
constexpr auto min = IsFlags ? log2(min_v<E, IsFlags>) : min_v<E, IsFlags>;
[[maybe_unused]] auto i = index_t<E, IsFlags>{0};
template <typename E>
inline constexpr auto count_v = values_v<E>.size();
return std::array<decltype(i), sizeof...(I)>{{(is_valid<E, value<E, min, IsFlags>(I)>() ? i++ : invalid_index_v<E, IsFlags>)...}};
}
template <typename E, typename U = std::underlying_type_t<E>>
inline constexpr auto min_v = (count_v<E> > 0) ? static_cast<U>(values_v<E>.front()) : U{0};
template <typename E, bool IsFlags = false>
inline constexpr auto indexes_v = indexes<E, IsFlags>(std::make_index_sequence<range_size_v<E, IsFlags>>{});
template <typename E, typename U = std::underlying_type_t<E>>
inline constexpr auto max_v = (count_v<E> > 0) ? static_cast<U>(values_v<E>.back()) : U{0};
template <typename E, bool IsFlags, std::size_t... I>
template <typename E, std::size_t... I>
constexpr auto names(std::index_sequence<I...>) noexcept {
static_assert(is_enum_v<E>, "magic_enum::detail::names requires enum type.");
return std::array<string_view, sizeof...(I)>{{enum_name_v<E, values_v<E, IsFlags>[I]>...}};
return std::array<string_view, sizeof...(I)>{{enum_name_v<E, values_v<E>[I]>...}};
}
template <typename E, bool IsFlags = false>
inline constexpr auto names_v = names<E, IsFlags>(std::make_index_sequence<count_v<E, IsFlags>>{});
template <typename E>
inline constexpr auto names_v = names<E>(std::make_index_sequence<count_v<E>>{});
template <typename E, bool IsFlags = false, typename D = std::decay_t<E>>
using names_t = decltype((names_v<D, IsFlags>));
template <typename E, typename D = std::decay_t<E>>
using names_t = decltype((names_v<D>));
template <typename E, bool IsFlags, std::size_t... I>
template <typename E, std::size_t... I>
constexpr auto entries(std::index_sequence<I...>) noexcept {
static_assert(is_enum_v<E>, "magic_enum::detail::entries requires enum type.");
return std::array<std::pair<E, string_view>, sizeof...(I)>{{{values_v<E, IsFlags>[I], enum_name_v<E, values_v<E, IsFlags>[I]>}...}};
return std::array<std::pair<E, string_view>, sizeof...(I)>{{{values_v<E>[I], enum_name_v<E, values_v<E>[I]>}...}};
}
template <typename E, bool IsFlags = false>
inline constexpr auto entries_v = entries<E, IsFlags>(std::make_index_sequence<count_v<E, IsFlags>>{});
template <typename E>
inline constexpr auto entries_v = entries<E>(std::make_index_sequence<count_v<E>>{});
template <typename E, bool IsFlags = false, typename D = std::decay_t<E>>
using entries_t = decltype((entries_v<D, IsFlags>));
template <typename E, typename D = std::decay_t<E>>
using entries_t = decltype((entries_v<D>));
template <typename E, bool IsFlags, typename U = std::underlying_type_t<E>>
template <typename E, typename U = std::underlying_type_t<E>>
constexpr bool is_sparse() noexcept {
static_assert(is_enum_v<E>, "magic_enum::detail::is_sparse requires enum type.");
constexpr auto max = is_flags_v<E> ? log2(max_v<E>) : max_v<E>;
constexpr auto min = is_flags_v<E> ? log2(min_v<E>) : min_v<E>;
constexpr auto range_size = max - min + 1;
return range_size_v<E, IsFlags> != count_v<E, IsFlags>;
return range_size != count_v<E>;
}
template <typename E, bool IsFlags = false>
inline constexpr bool is_sparse_v = is_sparse<E, IsFlags>();
template <typename E>
inline constexpr bool is_sparse_v = is_sparse<E>();
template <typename E, typename U = std::underlying_type_t<E>>
constexpr std::size_t undex(U value) noexcept {
static_assert(is_enum_v<E>, "magic_enum::detail::undex requires enum type.");
constexpr U values_ors() noexcept {
static_assert(is_enum_v<E>, "magic_enum::detail::values_ors requires enum type.");
if (const auto i = static_cast<std::size_t>(value - min_v<E>); value >= min_v<E> && value <= max_v<E>) {
if constexpr (is_sparse_v<E>) {
if (const auto idx = indexes_v<E>[i]; idx != invalid_index_v<E>) {
return idx;
}
} else {
return i;
}
auto ors = U{0};
for (std::size_t i = 0; i < count_v<E>; ++i) {
ors |= static_cast<U>(values_v<E>[i]);
}
return invalid_index_v<E>; // Value out of range.
}
template <typename E, typename U = std::underlying_type_t<E>>
constexpr std::size_t endex(E value) noexcept {
static_assert(is_enum_v<E>, "magic_enum::detail::endex requires enum type.");
return undex<E>(static_cast<U>(value));
}
template <typename E, typename U = std::underlying_type_t<E>>
constexpr U value_ors() noexcept {
static_assert(is_enum_v<E>, "magic_enum::detail::endex requires enum type.");
auto value = U{0};
for (std::size_t i = 0; i < count_v<E, true>; ++i) {
value |= static_cast<U>(values_v<E, true>[i]);
}
return value;
return ors;
}
template <bool, typename T, typename R>
@ -656,8 +633,7 @@ using underlying_type_t = typename underlying_type<T>::type;
// Returns type name of enum.
template <typename E>
[[nodiscard]] constexpr auto enum_type_name() noexcept -> detail::enable_if_enum_t<E, string_view> {
using D = std::decay_t<E>;
constexpr string_view name = detail::type_name_v<D>;
constexpr string_view name = detail::type_name_v<std::decay_t<E>>;
static_assert(name.size() > 0, "Enum type does not have a name.");
return name;
@ -666,9 +642,7 @@ template <typename E>
// Returns number of enum values.
template <typename E>
[[nodiscard]] constexpr auto enum_count() noexcept -> detail::enable_if_enum_t<E, std::size_t> {
using D = std::decay_t<E>;
return detail::count_v<D>;
return detail::count_v<std::decay_t<E>>;
}
// Returns enum value at specified index.
@ -676,40 +650,34 @@ template <typename E>
template <typename E>
[[nodiscard]] constexpr auto enum_value(std::size_t index) noexcept -> detail::enable_if_enum_t<E, std::decay_t<E>> {
using D = std::decay_t<E>;
static_assert(detail::count_v<D> > 0, "magic_enum requires enum implementation and valid max and min.");
if constexpr (detail::is_sparse_v<D>) {
return assert((index < detail::count_v<D>)), detail::values_v<D>[index];
} else {
return assert((index < detail::count_v<D>)), detail::value<D, detail::min_v<D>>(index);
constexpr bool is_flag = detail::is_flags_v<D>;
constexpr auto min = is_flag ? detail::log2(detail::min_v<D>) : detail::min_v<D>;
return assert((index < detail::count_v<D>)), detail::value<D, min, is_flag>(index);
}
}
// Returns enum value at specified index.
template <typename E, std::size_t I>
[[nodiscard]] constexpr auto enum_value() noexcept -> detail::enable_if_enum_t<E, std::decay_t<E>> {
using D = std::decay_t<E>;
static_assert(detail::count_v<D> > 0, "magic_enum requires enum implementation and valid max and min.");
static_assert(I < detail::count_v<D>, "magic_enum::enum_value out of range.");
return detail::values_v<D>[I];
return enum_value<std::decay_t<E>>(I);
}
// Returns std::array with enum values, sorted by enum value.
template <typename E>
[[nodiscard]] constexpr auto enum_values() noexcept -> detail::enable_if_enum_t<E, detail::values_t<E>> {
using D = std::decay_t<E>;
static_assert(detail::count_v<D> > 0, "magic_enum requires enum implementation and valid max and min.");
return detail::values_v<D>;
return detail::values_v<std::decay_t<E>>;
}
// Returns name from static storage enum variable.
// This version is much lighter on the compile times and is not restricted to the enum_range limitation.
template <auto V>
[[nodiscard]] constexpr auto enum_name() noexcept -> detail::enable_if_enum_t<decltype(V), string_view> {
using D = std::decay_t<decltype(V)>;
constexpr string_view name = detail::enum_name_v<D, V>;
constexpr string_view name = detail::enum_name_v<std::decay_t<decltype(V)>, V>;
static_assert(name.size() > 0, "Enum value does not have a name.");
return name;
@ -720,30 +688,65 @@ template <auto V>
template <typename E>
[[nodiscard]] constexpr auto enum_name(E value) noexcept -> detail::enable_if_enum_t<E, string_view> {
using D = std::decay_t<E>;
using U = underlying_type_t<D>;
if (const auto i = detail::endex<D>(value); i != detail::invalid_index_v<D>) {
if constexpr (detail::is_sparse_v<D> || detail::is_flags_v<D>) {
for (std::size_t i = 0; i < detail::count_v<D>; ++i) {
if (enum_value<D>(i) == value) {
return detail::names_v<D>[i];
}
}
} else {
const auto v = static_cast<U>(value);
if (v >= detail::min_v<D> && v <= detail::max_v<D>) {
return detail::names_v<D>[static_cast<std::size_t>(v - detail::min_v<E>)];
}
}
return {}; // Invalid value or out of range.
}
// Returns name from enum-flags value.
// If enum-flags value does not have name or value out of range, returns empty string.
template <typename E>
[[nodiscard]] auto enum_flags_name(E value) -> detail::enable_if_enum_t<E, string> {
using D = std::decay_t<E>;
using U = underlying_type_t<D>;
if constexpr (detail::is_flags_v<D>) {
string name;
auto check_value = U{0};
for (std::size_t i = 0; i < detail::count_v<D>; ++i) {
if (const auto v = static_cast<U>(enum_value<D>(i)); (static_cast<U>(value) & v) != 0) {
check_value |= v;
const auto n = detail::names_v<D>[i];
if (!name.empty()) {
name.append(1, '|');
}
name.append(n.data(), n.size());
}
}
if (check_value != 0 && check_value == static_cast<U>(value)) {
return name;
}
return {}; // Invalid value or out of range.
} else {
return string{enum_name(value)};
}
}
// Returns std::array with names, sorted by enum value.
template <typename E>
[[nodiscard]] constexpr auto enum_names() noexcept -> detail::enable_if_enum_t<E, detail::names_t<E>> {
using D = std::decay_t<E>;
static_assert(detail::count_v<D> > 0, "magic_enum requires enum implementation and valid max and min.");
return detail::names_v<D>;
return detail::names_v<std::decay_t<E>>;
}
// Returns std::array with pairs (value, name), sorted by enum value.
template <typename E>
[[nodiscard]] constexpr auto enum_entries() noexcept -> detail::enable_if_enum_t<E, detail::entries_t<E>> {
using D = std::decay_t<E>;
static_assert(detail::count_v<D> > 0, "magic_enum requires enum implementation and valid max and min.");
return detail::entries_v<D>;
return detail::entries_v<std::decay_t<E>>;
}
// Obtains enum value from integer value.
@ -751,10 +754,36 @@ template <typename E>
template <typename E>
[[nodiscard]] constexpr auto enum_cast(underlying_type_t<E> value) noexcept -> detail::enable_if_enum_t<E, optional<std::decay_t<E>>> {
using D = std::decay_t<E>;
using U = underlying_type_t<D>;
if (detail::undex<D>(value) != detail::invalid_index_v<D>) {
if constexpr (detail::is_sparse_v<D>) {
constexpr auto count = detail::count_v<D>;
if constexpr (detail::is_flags_v<D>) {
auto check_value = U{0};
for (std::size_t i = 0; i < count; ++i) {
if (const auto v = static_cast<U>(enum_value<D>(i)); (value & v) != 0) {
check_value |= v;
}
}
if (check_value != 0 && check_value == value) {
return static_cast<D>(value);
}
} else {
for (std::size_t i = 0; i < count; ++i) {
if (value == static_cast<U>(enum_value<D>(i))) {
return static_cast<D>(value);
}
}
}
} else {
constexpr auto min = detail::min_v<D>;
constexpr auto max = detail::is_flags_v<D> ? detail::values_ors<D>() : detail::max_v<D>;
if (value >= min && value <= max) {
return static_cast<D>(value);
}
}
return {}; // Invalid value or out of range.
}
@ -765,12 +794,37 @@ template <typename E, typename BinaryPredicate>
[[nodiscard]] constexpr auto enum_cast(string_view value, BinaryPredicate p) noexcept(std::is_nothrow_invocable_r_v<bool, BinaryPredicate, char, char>) -> detail::enable_if_enum_t<E, optional<std::decay_t<E>>> {
static_assert(std::is_invocable_r_v<bool, BinaryPredicate, char, char>, "magic_enum::enum_cast requires bool(char, char) invocable predicate.");
using D = std::decay_t<E>;
using U = underlying_type_t<D>;
if constexpr (detail::is_flags_v<D>) {
auto result = U{0};
while (!value.empty()) {
const auto d = detail::find(value, '|');
const auto s = (d == string_view::npos) ? value : value.substr(0, d);
auto f = U{0};
for (std::size_t i = 0; i < detail::count_v<D>; ++i) {
if (detail::cmp_equal(s, detail::names_v<D>[i], p)) {
f = static_cast<U>(enum_value<D>(i));
result |= f;
break;
}
}
if (f == U{0}) {
return {}; // Invalid value or out of range.
}
value.remove_prefix((d == string_view::npos) ? value.size() : d + 1);
}
if (result != U{0}) {
return static_cast<D>(result);
}
} else {
for (std::size_t i = 0; i < detail::count_v<D>; ++i) {
if (detail::cmp_equal(value, detail::names_v<D>[i], p)) {
return enum_value<D>(i);
}
}
}
return {}; // Invalid value or out of range.
}
@ -795,10 +849,20 @@ template <typename E>
template <typename E>
[[nodiscard]] constexpr auto enum_index(E value) noexcept -> detail::enable_if_enum_t<E, optional<std::size_t>> {
using D = std::decay_t<E>;
using U = underlying_type_t<D>;
if (const auto i = detail::endex<D>(value); i != detail::invalid_index_v<D>) {
if constexpr (detail::is_sparse_v<D> || detail::is_flags_v<D>) {
for (std::size_t i = 0; i < detail::count_v<D>; ++i) {
if (enum_value<D>(i) == value) {
return i;
}
}
} else {
const auto v = static_cast<U>(value);
if (v >= detail::min_v<D> && v <= detail::max_v<D>) {
return static_cast<std::size_t>(v - detail::min_v<E>);
}
}
return {}; // Invalid value or out of range.
}
@ -807,43 +871,39 @@ template <typename E>
template <typename E>
[[nodiscard]] constexpr auto enum_contains(E value) noexcept -> detail::enable_if_enum_t<E, bool> {
using D = std::decay_t<E>;
using U = underlying_type_t<D>;
return detail::endex<D>(value) != detail::invalid_index_v<D>;
return enum_cast<D>(static_cast<U>(value)).has_value();
}
// Checks whether enum contains enumerator with such integer value.
template <typename E>
[[nodiscard]] constexpr auto enum_contains(underlying_type_t<E> value) noexcept -> detail::enable_if_enum_t<E, bool> {
using D = std::decay_t<E>;
return detail::undex<D>(value) != detail::invalid_index_v<D>;
return enum_cast<std::decay_t<E>>(value).has_value();
}
// Checks whether enum contains enumerator with such name.
template <typename E, typename BinaryPredicate>
[[nodiscard]] constexpr auto enum_contains(string_view value, BinaryPredicate p) noexcept(std::is_nothrow_invocable_r_v<bool, BinaryPredicate, char, char>) -> detail::enable_if_enum_t<E, bool> {
static_assert(std::is_invocable_r_v<bool, BinaryPredicate, char, char>, "magic_enum::enum_contains requires bool(char, char) invocable predicate.");
using D = std::decay_t<E>;
return enum_cast<D>(value, std::move_if_noexcept(p)).has_value();
return enum_cast<std::decay_t<E>>(value, std::move_if_noexcept(p)).has_value();
}
// Checks whether enum contains enumerator with such name.
template <typename E>
[[nodiscard]] constexpr auto enum_contains(string_view value) noexcept -> detail::enable_if_enum_t<E, bool> {
using D = std::decay_t<E>;
return enum_cast<D>(value).has_value();
return enum_cast<std::decay_t<E>>(value).has_value();
}
namespace ostream_operators {
template <typename Char, typename Traits, typename E, std::enable_if_t<std::is_enum_v<E>, int> = 0>
template <typename Char, typename Traits, typename E, detail::enable_if_enum_t<E, int> = 0>
std::basic_ostream<Char, Traits>& operator<<(std::basic_ostream<Char, Traits>& os, E value) {
using D = std::decay_t<E>;
using U = underlying_type_t<D>;
#if defined(MAGIC_ENUM_SUPPORTED) && MAGIC_ENUM_SUPPORTED
if (const auto name = magic_enum::enum_name<D>(value); !name.empty()) {
if (const auto name = magic_enum::enum_flags_name<D>(value); !name.empty()) {
for (const auto c : name) {
os.put(c);
}
@ -853,7 +913,7 @@ std::basic_ostream<Char, Traits>& operator<<(std::basic_ostream<Char, Traits>& o
return (os << static_cast<U>(value));
}
template <typename Char, typename Traits, typename E, std::enable_if_t<std::is_enum_v<E>, int> = 0>
template <typename Char, typename Traits, typename E, detail::enable_if_enum_t<E, int> = 0>
std::basic_ostream<Char, Traits>& operator<<(std::basic_ostream<Char, Traits>& os, optional<E> value) {
return value.has_value() ? (os << value.value()) : os;
}
@ -862,295 +922,43 @@ std::basic_ostream<Char, Traits>& operator<<(std::basic_ostream<Char, Traits>& o
namespace bitwise_operators {
template <typename E, std::enable_if_t<std::is_enum_v<E>, int> = 0>
template <typename E, detail::enable_if_enum_t<E, int> = 0>
constexpr E operator~(E rhs) noexcept {
return static_cast<E>(~static_cast<underlying_type_t<E>>(rhs));
}
template <typename E, std::enable_if_t<std::is_enum_v<E>, int> = 0>
template <typename E, detail::enable_if_enum_t<E, int> = 0>
constexpr E operator|(E lhs, E rhs) noexcept {
return static_cast<E>(static_cast<underlying_type_t<E>>(lhs) | static_cast<underlying_type_t<E>>(rhs));
}
template <typename E, std::enable_if_t<std::is_enum_v<E>, int> = 0>
template <typename E, detail::enable_if_enum_t<E, int> = 0>
constexpr E operator&(E lhs, E rhs) noexcept {
return static_cast<E>(static_cast<underlying_type_t<E>>(lhs) & static_cast<underlying_type_t<E>>(rhs));
}
template <typename E, std::enable_if_t<std::is_enum_v<E>, int> = 0>
template <typename E, detail::enable_if_enum_t<E, int> = 0>
constexpr E operator^(E lhs, E rhs) noexcept {
return static_cast<E>(static_cast<underlying_type_t<E>>(lhs) ^ static_cast<underlying_type_t<E>>(rhs));
}
template <typename E, std::enable_if_t<std::is_enum_v<E>, int> = 0>
template <typename E, detail::enable_if_enum_t<E, int> = 0>
constexpr E& operator|=(E& lhs, E rhs) noexcept {
return lhs = (lhs | rhs);
}
template <typename E, std::enable_if_t<std::is_enum_v<E>, int> = 0>
template <typename E, detail::enable_if_enum_t<E, int> = 0>
constexpr E& operator&=(E& lhs, E rhs) noexcept {
return lhs = (lhs & rhs);
}
template <typename E, std::enable_if_t<std::is_enum_v<E>, int> = 0>
template <typename E, detail::enable_if_enum_t<E, int> = 0>
constexpr E& operator^=(E& lhs, E rhs) noexcept {
return lhs = (lhs ^ rhs);
}
} // namespace magic_enum::bitwise_operators
namespace flags {
// Returns type name of enum.
using magic_enum::enum_type_name;
// Returns number of enum-flags values.
template <typename E>
[[nodiscard]] constexpr auto enum_count() noexcept -> detail::enable_if_enum_t<E, std::size_t> {
using D = std::decay_t<E>;
return detail::count_v<D, true>;
}
// Returns enum-flags value at specified index.
// No bounds checking is performed: the behavior is undefined if index >= number of enum-flags values.
template <typename E>
[[nodiscard]] constexpr auto enum_value(std::size_t index) noexcept -> detail::enable_if_enum_t<E, std::decay_t<E>> {
using D = std::decay_t<E>;
static_assert(detail::count_v<D, true> > 0, "magic_enum::flags requires enum-flags implementation.");
if constexpr (detail::is_sparse_v<D, true>) {
return assert((index < detail::count_v<D, true>)), detail::values_v<D, true>[index];
} else {
constexpr auto min = detail::log2(detail::min_v<D, true>);
return assert((index < detail::count_v<D, true>)), detail::value<D, min, true>(index);
}
}
// Returns enum-flags value at specified index.
template <typename E, std::size_t I>
[[nodiscard]] constexpr auto enum_value() noexcept -> detail::enable_if_enum_t<E, std::decay_t<E>> {
using D = std::decay_t<E>;
static_assert(detail::count_v<D, true> > 0, "magic_enum::flags requires enum implementation and valid max and min.");
static_assert(I < detail::count_v<D, true>, "magic_enum::flags::enum_value out of range.");
return detail::values_v<D, true>[I];
}
// Returns std::array with enum-flags values, sorted by enum-flags value.
template <typename E>
[[nodiscard]] constexpr auto enum_values() noexcept -> detail::enable_if_enum_t<E, detail::values_t<E, true>> {
using D = std::decay_t<E>;
static_assert(detail::count_v<D, true> > 0, "magic_enum::flags requires enum-flags implementation.");
return detail::values_v<D, true>;
}
// Returns name from enum-flags value.
// If enum-flags value does not have name or value out of range, returns empty string.
template <typename E>
[[nodiscard]] auto enum_name(E value) -> detail::enable_if_enum_t<E, string> {
using D = std::decay_t<E>;
using U = underlying_type_t<D>;
string name;
auto check_value = U{0};
for (std::size_t i = 0; i < detail::count_v<D, true>; ++i) {
if (const auto v = static_cast<U>(enum_value<D>(i)); (static_cast<U>(value) & v) != 0) {
check_value |= v;
const auto n = detail::names_v<D, true>[i];
if (!name.empty()) {
name.append(1, '|');
}
name.append(n.data(), n.size());
}
}
if (check_value != 0 && check_value == static_cast<U>(value)) {
return name;
}
return {}; // Invalid value or out of range.
}
// Returns std::array with string names, sorted by enum-flags value.
template <typename E>
[[nodiscard]] constexpr auto enum_names() noexcept -> detail::enable_if_enum_t<E, detail::names_t<E, true>> {
using D = std::decay_t<E>;
static_assert(detail::count_v<D, true> > 0, "magic_enum::flags requires enum-flags implementation.");
return detail::names_v<D, true>;
}
// Returns std::array with pairs (value, name), sorted by enum-flags value.
template <typename E>
[[nodiscard]] constexpr auto enum_entries() noexcept -> detail::enable_if_enum_t<E, detail::entries_t<E, true>> {
using D = std::decay_t<E>;
static_assert(detail::count_v<D, true> > 0, "magic_enum::flags requires enum-flags implementation.");
return detail::entries_v<D, true>;
}
// Obtains enum-flags value from integer value.
// Returns optional with enum-flags value.
template <typename E>
[[nodiscard]] constexpr auto enum_cast(underlying_type_t<E> value) noexcept -> detail::enable_if_enum_t<E, optional<std::decay_t<E>>> {
using D = std::decay_t<E>;
using U = underlying_type_t<D>;
if constexpr (detail::is_sparse_v<D, true>) {
auto check_value = U{0};
for (std::size_t i = 0; i < detail::count_v<D, true>; ++i) {
if (const auto v = static_cast<U>(enum_value<D>(i)); (value & v) != 0) {
check_value |= v;
}
}
if (check_value != 0 && check_value == value) {
return static_cast<D>(value);
}
} else {
constexpr auto min = detail::min_v<D, true>;
constexpr auto max = detail::value_ors<D>();
if (value >= min && value <= max) {
return static_cast<D>(value);
}
}
return {}; // Invalid value or out of range.
}
// Obtains enum-flags value from name.
// Returns optional with enum-flags value.
template <typename E, typename BinaryPredicate>
[[nodiscard]] constexpr auto enum_cast(string_view value, BinaryPredicate p) noexcept(std::is_nothrow_invocable_r_v<bool, BinaryPredicate, char, char>) -> detail::enable_if_enum_t<E, optional<std::decay_t<E>>> {
static_assert(std::is_invocable_r_v<bool, BinaryPredicate, char, char>, "magic_enum::flags::enum_cast requires bool(char, char) invocable predicate.");
using D = std::decay_t<E>;
using U = underlying_type_t<D>;
auto result = U{0};
while (!value.empty()) {
const auto d = detail::find(value, '|');
const auto s = (d == string_view::npos) ? value : value.substr(0, d);
auto f = U{0};
for (std::size_t i = 0; i < detail::count_v<D, true>; ++i) {
if (detail::cmp_equal(s, detail::names_v<D, true>[i], p)) {
f = static_cast<U>(enum_value<D>(i));
result |= f;
break;
}
}
if (f == U{0}) {
return {}; // Invalid value or out of range.
}
value.remove_prefix((d == string_view::npos) ? value.size() : d + 1);
}
if (result == U{0}) {
return {}; // Invalid value or out of range.
} else {
return static_cast<D>(result);
}
}
// Obtains enum-flags value from name.
// Returns optional with enum-flags value.
template <typename E>
[[nodiscard]] constexpr auto enum_cast(string_view value) noexcept -> detail::enable_if_enum_t<E, optional<std::decay_t<E>>> {
using D = std::decay_t<E>;
return enum_cast<D>(value, detail::char_equal_to{});
}
// Returns integer value from enum value.
using magic_enum::enum_integer;
// Obtains index in enum-flags values from enum-flags value.
// Returns optional with index.
template <typename E>
[[nodiscard]] constexpr auto enum_index(E value) noexcept -> detail::enable_if_enum_t<E, optional<std::size_t>> {
using D = std::decay_t<E>;
using U = underlying_type_t<D>;
if (detail::is_pow2(static_cast<U>(value))) {
for (std::size_t i = 0; i < detail::count_v<D, true>; ++i) {
if (enum_value<D>(i) == value) {
return i;
}
}
}
return {}; // Invalid value or out of range.
}
// Checks whether enum-flags contains enumerator with such enum-flags value.
template <typename E>
[[nodiscard]] constexpr auto enum_contains(E value) noexcept -> detail::enable_if_enum_t<E, bool> {
using D = std::decay_t<E>;
using U = underlying_type_t<D>;
return enum_cast<D>(static_cast<U>(value)).has_value();
}
// Checks whether enum-flags contains enumerator with such integer value.
template <typename E>
[[nodiscard]] constexpr auto enum_contains(underlying_type_t<E> value) noexcept -> detail::enable_if_enum_t<E, bool> {
using D = std::decay_t<E>;
return enum_cast<D>(value).has_value();
}
// Checks whether enum-flags contains enumerator with such name.
template <typename E, typename BinaryPredicate>
[[nodiscard]] constexpr auto enum_contains(string_view value, BinaryPredicate p) noexcept(std::is_nothrow_invocable_r_v<bool, BinaryPredicate, char, char>) -> detail::enable_if_enum_t<E, bool> {
static_assert(std::is_invocable_r_v<bool, BinaryPredicate, char, char>, "magic_enum::flags::enum_contains requires bool(char, char) invocable predicate.");
using D = std::decay_t<E>;
return enum_cast<D>(value, std::move_if_noexcept(p)).has_value();
}
// Checks whether enum-flags contains enumerator with such name.
template <typename E>
[[nodiscard]] constexpr auto enum_contains(string_view value) noexcept -> detail::enable_if_enum_t<E, bool> {
using D = std::decay_t<E>;
return enum_cast<D>(value).has_value();
}
} // namespace magic_enum::flags
namespace flags::ostream_operators {
template <typename Char, typename Traits, typename E, std::enable_if_t<std::is_enum_v<E>, int> = 0>
std::basic_ostream<Char, Traits>& operator<<(std::basic_ostream<Char, Traits>& os, E value) {
using D = std::decay_t<E>;
using U = underlying_type_t<D>;
#if defined(MAGIC_ENUM_SUPPORTED) && MAGIC_ENUM_SUPPORTED
if (const auto name = magic_enum::flags::enum_name<D>(value); !name.empty()) {
for (const auto c : name) {
os.put(c);
}
return os;
}
#endif
return (os << static_cast<U>(value));
}
template <typename Char, typename Traits, typename E, std::enable_if_t<std::is_enum_v<E>, int> = 0>
std::basic_ostream<Char, Traits>& operator<<(std::basic_ostream<Char, Traits>& os, optional<E> value) {
return value.has_value() ? (os << value.value()) : os;
}
} // namespace magic_enum::flags::ostream_operators
namespace flags::bitwise_operators {
using namespace magic_enum::bitwise_operators;
} // namespace magic_enum::flags::bitwise_operators
} // namespace magic_enum
#if defined(__clang__)

View file

@ -57,38 +57,33 @@ enum number : unsigned long {
_4 = four
#endif
};
template <>
struct magic_enum::customize::enum_range<number> {
static constexpr int min = 100;
static constexpr int max = 300;
};
enum class MaxUsedAsInvalid : std::uint8_t {
ONE,
TWO,
INVALID = std::numeric_limits<std::uint8_t>::max()
};
template <>
struct magic_enum::customize::enum_range<MaxUsedAsInvalid> {
static constexpr int min = 0;
static constexpr int max = 64;
};
enum class Binary : bool {
ONE,
TWO
};
namespace magic_enum::customize {
template <>
struct enum_range<MaxUsedAsInvalid> {
struct magic_enum::customize::enum_range<Binary> {
static constexpr int min = 0;
static constexpr int max = 64;
};
template <>
struct enum_range<Binary> {
static constexpr int min = 0;
static constexpr int max = 64;
};
template <>
struct enum_range<number> {
static constexpr int min = 100;
static constexpr int max = 300;
};
} // namespace magic_enum
template <>
constexpr std::string_view magic_enum::customize::enum_name<Color>(Color value) noexcept {
switch (value) {
@ -847,69 +842,69 @@ TEST_CASE("extrema") {
SECTION("min") {
REQUIRE(magic_enum::customize::enum_range<BadColor>::min == MAGIC_ENUM_RANGE_MIN);
REQUIRE(magic_enum::detail::reflected_min_v<BadColor> == 0);
REQUIRE(magic_enum::detail::reflected_min_v<BadColor, false> == 0);
REQUIRE(magic_enum::detail::min_v<BadColor> == 0);
REQUIRE(magic_enum::customize::enum_range<Color>::min == MAGIC_ENUM_RANGE_MIN);
REQUIRE(magic_enum::detail::reflected_min_v<Color> == MAGIC_ENUM_RANGE_MIN);
REQUIRE(magic_enum::detail::reflected_min_v<Color, false> == MAGIC_ENUM_RANGE_MIN);
REQUIRE(magic_enum::detail::min_v<Color> == -12);
REQUIRE(magic_enum::customize::enum_range<Numbers>::min == MAGIC_ENUM_RANGE_MIN);
REQUIRE(magic_enum::detail::reflected_min_v<Numbers> == MAGIC_ENUM_RANGE_MIN);
REQUIRE(magic_enum::detail::reflected_min_v<Numbers, false> == MAGIC_ENUM_RANGE_MIN);
REQUIRE(magic_enum::detail::min_v<Numbers> == 1);
REQUIRE(magic_enum::customize::enum_range<Directions>::min == MAGIC_ENUM_RANGE_MIN);
REQUIRE(magic_enum::detail::reflected_min_v<Directions> == MAGIC_ENUM_RANGE_MIN);
REQUIRE(magic_enum::detail::reflected_min_v<Directions, false> == MAGIC_ENUM_RANGE_MIN);
REQUIRE(magic_enum::detail::min_v<Directions> == -120);
#if defined(MAGIC_ENUM_ENABLE_NONASCII)
REQUIRE(magic_enum::customize::enum_range<Language>::min == MAGIC_ENUM_RANGE_MIN);
REQUIRE(magic_enum::detail::reflected_min_v<Language> == MAGIC_ENUM_RANGE_MIN);
REQUIRE(magic_enum::detail::reflected_min_v<Language, false> == MAGIC_ENUM_RANGE_MIN);
REQUIRE(magic_enum::detail::min_v<Language> == 10);
#endif
REQUIRE(magic_enum::customize::enum_range<number>::min == 100);
REQUIRE(magic_enum::detail::reflected_min_v<number> == 100);
REQUIRE(magic_enum::detail::reflected_min_v<number, false> == 100);
REQUIRE(magic_enum::detail::min_v<number> == 100);
REQUIRE(magic_enum::detail::reflected_min_v<Binary> == 0);
REQUIRE(magic_enum::detail::reflected_min_v<Binary, false> == 0);
REQUIRE(magic_enum::detail::min_v<Binary> == false);
REQUIRE(magic_enum::detail::reflected_min_v<MaxUsedAsInvalid> == 0);
REQUIRE(magic_enum::detail::reflected_min_v<MaxUsedAsInvalid,false> == 0);
REQUIRE(magic_enum::detail::min_v<MaxUsedAsInvalid> == 0);
}
SECTION("max") {
REQUIRE(magic_enum::customize::enum_range<BadColor>::max == MAGIC_ENUM_RANGE_MAX);
REQUIRE(magic_enum::detail::reflected_max_v<BadColor> == MAGIC_ENUM_RANGE_MAX);
REQUIRE(magic_enum::detail::reflected_max_v<BadColor, false> == MAGIC_ENUM_RANGE_MAX);
REQUIRE(magic_enum::detail::max_v<BadColor> == 2);
REQUIRE(magic_enum::customize::enum_range<Color>::max == MAGIC_ENUM_RANGE_MAX);
REQUIRE(magic_enum::detail::reflected_max_v<Color> == MAGIC_ENUM_RANGE_MAX);
REQUIRE(magic_enum::detail::reflected_max_v<Color, false> == MAGIC_ENUM_RANGE_MAX);
REQUIRE(magic_enum::detail::max_v<Color> == 15);
REQUIRE(magic_enum::customize::enum_range<Numbers>::max == MAGIC_ENUM_RANGE_MAX);
REQUIRE(magic_enum::detail::reflected_max_v<Numbers> == MAGIC_ENUM_RANGE_MAX);
REQUIRE(magic_enum::detail::reflected_max_v<Numbers, false> == MAGIC_ENUM_RANGE_MAX);
REQUIRE(magic_enum::detail::max_v<Numbers> == 3);
REQUIRE(magic_enum::customize::enum_range<Directions>::max == MAGIC_ENUM_RANGE_MAX);
REQUIRE(magic_enum::detail::reflected_max_v<Directions> == MAGIC_ENUM_RANGE_MAX);
REQUIRE(magic_enum::detail::reflected_max_v<Directions, false> == MAGIC_ENUM_RANGE_MAX);
REQUIRE(magic_enum::detail::max_v<Directions> == 120);
#if defined(MAGIC_ENUM_ENABLE_NONASCII)
REQUIRE(magic_enum::customize::enum_range<Language>::max == MAGIC_ENUM_RANGE_MAX);
REQUIRE(magic_enum::detail::reflected_max_v<Language> == MAGIC_ENUM_RANGE_MAX);
REQUIRE(magic_enum::detail::reflected_max_v<Language, false> == MAGIC_ENUM_RANGE_MAX);
REQUIRE(magic_enum::detail::max_v<Language> == 40);
#endif
REQUIRE(magic_enum::customize::enum_range<number>::max == 300);
REQUIRE(magic_enum::detail::reflected_max_v<number> == 300);
REQUIRE(magic_enum::detail::reflected_max_v<number, false> == 300);
REQUIRE(magic_enum::detail::max_v<number> == 300);
REQUIRE(magic_enum::detail::reflected_max_v<Binary> == 1);
REQUIRE(magic_enum::detail::reflected_max_v<Binary, false> == 1);
REQUIRE(magic_enum::detail::max_v<Binary> == true);
REQUIRE(magic_enum::detail::reflected_max_v<MaxUsedAsInvalid> == 64);
REQUIRE(magic_enum::detail::reflected_max_v<MaxUsedAsInvalid, false> == 64);
REQUIRE(magic_enum::detail::max_v<MaxUsedAsInvalid> == 1);
}
}

View file

@ -40,6 +40,8 @@
#include <sstream>
enum class Color { RED = 1, GREEN = 2, BLUE = 4 };
template <>
struct magic_enum::customize::is_flags_enum<Color> : std::true_type {};
enum class Numbers : int {
one = 1 << 1,
@ -77,16 +79,13 @@ enum number : unsigned long {
_4 = four
#endif
};
namespace magic_enum::customize {
template <>
struct enum_range<number> {
struct magic_enum::customize::enum_range<number> {
static constexpr int min = 100;
static constexpr int max = 300;
};
} // namespace magic_enum
using namespace magic_enum::flags;
using namespace magic_enum;
using namespace magic_enum::bitwise_operators;
TEST_CASE("enum_cast") {
@ -453,39 +452,39 @@ TEST_CASE("enum_count") {
TEST_CASE("enum_name") {
SECTION("automatic storage") {
constexpr Color cr = Color::RED;
auto cr_name = enum_name(cr);
constexpr auto cr_name = enum_name(cr);
Color cm[3] = {Color::RED, Color::GREEN, Color::BLUE};
Color cb = Color::BLUE;
REQUIRE(cr_name == "RED");
REQUIRE(enum_name<Color&>(cb) == "BLUE");
REQUIRE(enum_name(cm[1]) == "GREEN");
REQUIRE(enum_name(Color::RED | Color{0}) == "RED");
REQUIRE(enum_name(Color::RED | Color::GREEN) == "RED|GREEN");
REQUIRE(enum_name(Color::RED | Color::GREEN).empty());
REQUIRE(enum_name(Color::RED | Color{8}).empty());
REQUIRE(enum_name(static_cast<Color>(0)).empty());
constexpr Numbers no = Numbers::one;
auto no_name = enum_name(no);
constexpr auto no_name = enum_name(no);
REQUIRE(no_name == "one");
REQUIRE(enum_name(Numbers::two) == "two");
REQUIRE(enum_name(Numbers::three) == "three");
REQUIRE(enum_name(Numbers::many) == "many");
REQUIRE(enum_name(Numbers::many | Numbers::two) == "two|many");
REQUIRE(enum_name(Numbers::many | Numbers::two).empty());
REQUIRE(enum_name(static_cast<Numbers>(0)).empty());
constexpr Directions dr = Directions::Right;
auto dr_name = enum_name(dr);
constexpr auto dr_name = enum_name(dr);
Directions du = Directions::Up;
REQUIRE(enum_name<Directions&>(du) == "Up");
REQUIRE(enum_name<const Directions>(Directions::Down) == "Down");
REQUIRE(dr_name == "Right");
REQUIRE(enum_name(Directions::Left) == "Left");
REQUIRE(enum_name(Directions::Right | Directions::Up | Directions::Left | Directions::Down) == "Left|Down|Up|Right");
REQUIRE(enum_name(Directions::Right | Directions::Up | Directions::Left | Directions::Down).empty());
REQUIRE(enum_name(static_cast<Directions>(0)).empty());
#if defined(MAGIC_ENUM_ENABLE_NONASCII)
constexpr Language lang = Language::;
auto lang_name = enum_name(lang);
constexpr auto lang_name = enum_name(lang);
Language lk = Language::;
REQUIRE(enum_name<Language&>(lk) == "한국어");
REQUIRE(enum_name<const Language>(Language::English) == "English");
@ -495,16 +494,69 @@ TEST_CASE("enum_name") {
#endif
constexpr number nto = number::three | number::one;
auto nto_name = enum_name(nto);
constexpr auto nto_name = enum_name(nto);
REQUIRE(enum_name(number::one) == "one");
REQUIRE(enum_name(number::two) == "two");
REQUIRE(enum_name(number::three) == "three");
REQUIRE(enum_name(number::four) == "four");
REQUIRE(nto_name == "one|three");
REQUIRE(nto_name.empty());
REQUIRE(enum_name(static_cast<number>(0)).empty());
}
}
TEST_CASE("enum_flags_name") {
constexpr Color cr = Color::RED;
auto cr_name = enum_flags_name(cr);
Color cm[3] = {Color::RED, Color::GREEN, Color::BLUE};
Color cb = Color::BLUE;
REQUIRE(cr_name == "RED");
REQUIRE(enum_flags_name<Color&>(cb) == "BLUE");
REQUIRE(enum_flags_name(cm[1]) == "GREEN");
REQUIRE(enum_flags_name(Color::RED | Color{0}) == "RED");
REQUIRE(enum_flags_name(Color::RED | Color::GREEN) == "RED|GREEN");
REQUIRE(enum_flags_name(Color::RED | Color{8}).empty());
REQUIRE(enum_flags_name(static_cast<Color>(0)).empty());
constexpr Numbers no = Numbers::one;
auto no_name = enum_flags_name(no);
REQUIRE(no_name == "one");
REQUIRE(enum_flags_name(Numbers::two) == "two");
REQUIRE(enum_flags_name(Numbers::three) == "three");
REQUIRE(enum_flags_name(Numbers::many) == "many");
REQUIRE(enum_flags_name(Numbers::many | Numbers::two) == "two|many");
REQUIRE(enum_flags_name(static_cast<Numbers>(0)).empty());
constexpr Directions dr = Directions::Right;
auto dr_name = enum_flags_name(dr);
Directions du = Directions::Up;
REQUIRE(enum_flags_name<Directions&>(du) == "Up");
REQUIRE(enum_flags_name<const Directions>(Directions::Down) == "Down");
REQUIRE(dr_name == "Right");
REQUIRE(enum_flags_name(Directions::Left) == "Left");
REQUIRE(enum_flags_name(Directions::Right | Directions::Up | Directions::Left | Directions::Down) == "Left|Down|Up|Right");
REQUIRE(enum_flags_name(static_cast<Directions>(0)).empty());
#if defined(MAGIC_ENUM_ENABLE_NONASCII)
constexpr Language lang = Language::;
auto lang_name = enum_flags_name(lang);
Language lk = Language::;
REQUIRE(enum_flags_name<Language&>(lk) == "한국어");
REQUIRE(enum_flags_name<const Language>(Language::English) == "English");
REQUIRE(lang_name == "日本語");
REQUIRE(enum_flags_name(Language::😃) == "😃");
REQUIRE(enum_flags_name(static_cast<Language>(0)).empty());
#endif
constexpr number nto = number::three | number::one;
auto nto_name = enum_flags_name(nto);
REQUIRE(enum_flags_name(number::one) == "one");
REQUIRE(enum_flags_name(number::two) == "two");
REQUIRE(enum_flags_name(number::three) == "three");
REQUIRE(enum_flags_name(number::four) == "four");
REQUIRE(nto_name == "one|three");
REQUIRE(enum_flags_name(static_cast<number>(0)).empty());
}
TEST_CASE("enum_names") {
REQUIRE(std::is_same_v<decltype(magic_enum::enum_names<Color>()), const std::array<std::string_view, 3>&>);
@ -549,7 +601,7 @@ TEST_CASE("enum_entries") {
TEST_CASE("ostream_operators") {
auto test_ostream = [](auto e, std::string_view name) {
using namespace magic_enum::flags::ostream_operators;
using namespace magic_enum::ostream_operators;
std::stringstream ss;
ss << e;
REQUIRE(ss.str() == name);