From aecf0db795f708a4397a2af591b4d387cf0dde7b Mon Sep 17 00:00:00 2001 From: Daniil Goncharov Date: Sat, 8 Jan 2022 17:30:44 +0200 Subject: [PATCH] New enum-flags API (#120) --- README.md | 2 - doc/limitations.md | 11 +- doc/reference.md | 2 - example/enum_flag_example.cpp | 31 +- include/magic_enum.hpp | 616 ++++++++++++---------------------- test/test.cpp | 69 ++-- test/test_flags.cpp | 82 ++++- 7 files changed, 336 insertions(+), 477 deletions(-) diff --git a/README.md b/README.md index 7e8a112..79fdfb2 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/doc/limitations.md b/doc/limitations.md index dc7fc57..6ba2435 100644 --- a/doc/limitations.md +++ b/doc/limitations.md @@ -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 : 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 { + struct magic_enum::customize::enum_range { 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. diff --git a/doc/reference.md b/doc/reference.md index b84ce8f..5f15c70 100644 --- a/doc/reference.md +++ b/doc/reference.md @@ -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`.
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: diff --git a/example/enum_flag_example.cpp b/example/enum_flag_example.cpp index 3db3a50..fc29336 100644 --- a/example/enum_flag_example.cpp +++ b/example/enum_flag_example.cpp @@ -26,15 +26,18 @@ #include 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 : 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(); + constexpr auto names = magic_enum::enum_names(); 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("EatsFish|CanFly"); + auto f2 = magic_enum::enum_cast("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(1073742848); + auto f3 = magic_enum::enum_cast(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() << std::endl; // AnimalFlags enum size: 4 + std::cout << "AnimalFlags enum size: " << magic_enum::enum_count() << std::endl; // AnimalFlags enum size: 4 // Indexed access to enum-flags value. - std::cout << "AnimalFlags[0] = " << magic_enum::flags::enum_value(0) << std::endl; // AnimalFlags[0] = HasClaws + std::cout << "AnimalFlags[0] = " << magic_enum::enum_value(0) << std::endl; // AnimalFlags[0] = HasClaws // Enum-flags value sequence. - constexpr auto values = magic_enum::flags::enum_values(); + constexpr auto values = magic_enum::enum_values(); 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(); + constexpr auto entries = magic_enum::enum_entries(); 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 diff --git a/include/magic_enum.hpp b/include/magic_enum.hpp index c907b6f..36b975a 100644 --- a/include/magic_enum.hpp +++ b/include/magic_enum.hpp @@ -142,6 +142,9 @@ constexpr string_view enum_name(E) noexcept { return {}; } +template +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 == std::is_signed_v) { // If same signedness (both signed or both unsigned). return lhs < rhs; - } else if constexpr (std::is_same_v) { // bool special case due to msvc's C4804, C4018 + } else if constexpr (std::is_same_v) { // bool special case return static_cast(lhs) < rhs; - } else if constexpr (std::is_same_v) { // bool special case due to msvc's C4804, C4018 + } else if constexpr (std::is_same_v) { // bool special case return lhs < static_cast(rhs); } else if constexpr (std::is_signed_v) { // If 'right' is negative, then result is 'false', otherwise cast & compare. @@ -306,13 +309,6 @@ constexpr I log2(I value) noexcept { return ret; } -template -constexpr bool is_pow2(I x) noexcept { - static_assert(std::is_integral_v, "magic_enum::detail::is_pow2 requires integral type."); - - return x != 0 && (x & (x - 1)) == 0; -} - template inline constexpr bool is_enum_v = std::is_enum_v && std::is_same_v>; @@ -368,11 +364,15 @@ constexpr bool is_valid() noexcept { return n(V)>().size() != 0; } -template > +template > constexpr E value(std::size_t i) noexcept { static_assert(is_enum_v, "magic_enum::detail::value requires enum type."); - if constexpr (IsFlags) { + if constexpr (std::is_same_v) { // bool special case + static_assert(O == 0, "magic_enum::detail::value requires valid offset."); + + return static_cast(i); + } else if constexpr (IsFlags) { return static_cast(U{1} << static_cast(static_cast(i) + O)); } else { return static_cast(static_cast(i) + O); @@ -417,10 +417,10 @@ constexpr int reflected_max() noexcept { } } -template +template inline constexpr auto reflected_min_v = reflected_min(); -template +template inline constexpr auto reflected_max_v = reflected_max(); template @@ -467,124 +467,101 @@ constexpr auto values() noexcept { return values>(std::make_index_sequence{}); } -template -inline constexpr auto values_v = values(); +template > +constexpr bool is_flags_enum() noexcept { + static_assert(is_enum_v, "magic_enum::detail::is_flags_enum requires enum type."); -template > -using values_t = decltype((values_v)); - -template -inline constexpr auto count_v = values_v.size(); - -template > -inline constexpr auto min_v = (count_v > 0) ? static_cast(values_v.front()) : U{0}; - -template > -inline constexpr auto max_v = (count_v > 0) ? static_cast(values_v.back()) : U{0}; - -template > -constexpr std::size_t range_size() noexcept { - static_assert(is_enum_v, "magic_enum::detail::range_size requires enum type."); - constexpr auto max = IsFlags ? log2(max_v) : max_v; - constexpr auto min = IsFlags ? log2(min_v) : min_v; - 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::max)(), "magic_enum::enum_range requires valid size."); - - return static_cast(range_size); +#if defined(MAGIC_ENUM_NO_CHECK_FLAGS) + return customize::is_flags_enum::value; +#else + if constexpr (std::is_same_v) { // bool special case + return false; + } else if constexpr (customize::is_flags_enum::value) { + return true; + } else { + constexpr auto flags_values = values(); + constexpr auto default_values = values(); + 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(default_values[i]); + if (v != 0 && (v & (v - 1)) != 0) { + return false; + } + } + return flags_values.size() > 0; + } +#endif } -template -inline constexpr auto range_size_v = range_size(); +template +inline constexpr bool is_flags_v = is_flags_enum(); -template -using index_t = std::conditional_t < (std::numeric_limits::max)(), std::uint8_t, std::uint16_t>; +template +inline constexpr auto values_v = values>(); -template -inline constexpr auto invalid_index_v = (std::numeric_limits>::max)(); +template > +using values_t = decltype((values_v)); -template -constexpr auto indexes(std::index_sequence) noexcept { - static_assert(is_enum_v, "magic_enum::detail::indexes requires enum type."); - constexpr auto min = IsFlags ? log2(min_v) : min_v; - [[maybe_unused]] auto i = index_t{0}; +template +inline constexpr auto count_v = values_v.size(); - return std::array{{(is_valid(I)>() ? i++ : invalid_index_v)...}}; -} +template > +inline constexpr auto min_v = (count_v > 0) ? static_cast(values_v.front()) : U{0}; -template -inline constexpr auto indexes_v = indexes(std::make_index_sequence>{}); +template > +inline constexpr auto max_v = (count_v > 0) ? static_cast(values_v.back()) : U{0}; -template +template constexpr auto names(std::index_sequence) noexcept { static_assert(is_enum_v, "magic_enum::detail::names requires enum type."); - return std::array{{enum_name_v[I]>...}}; + return std::array{{enum_name_v[I]>...}}; } -template -inline constexpr auto names_v = names(std::make_index_sequence>{}); +template +inline constexpr auto names_v = names(std::make_index_sequence>{}); -template > -using names_t = decltype((names_v)); +template > +using names_t = decltype((names_v)); -template +template constexpr auto entries(std::index_sequence) noexcept { static_assert(is_enum_v, "magic_enum::detail::entries requires enum type."); - return std::array, sizeof...(I)>{{{values_v[I], enum_name_v[I]>}...}}; + return std::array, sizeof...(I)>{{{values_v[I], enum_name_v[I]>}...}}; } -template -inline constexpr auto entries_v = entries(std::make_index_sequence>{}); +template +inline constexpr auto entries_v = entries(std::make_index_sequence>{}); -template > -using entries_t = decltype((entries_v)); +template > +using entries_t = decltype((entries_v)); -template > +template > constexpr bool is_sparse() noexcept { static_assert(is_enum_v, "magic_enum::detail::is_sparse requires enum type."); + constexpr auto max = is_flags_v ? log2(max_v) : max_v; + constexpr auto min = is_flags_v ? log2(min_v) : min_v; + constexpr auto range_size = max - min + 1; - return range_size_v != count_v; + return range_size != count_v; } -template -inline constexpr bool is_sparse_v = is_sparse(); +template +inline constexpr bool is_sparse_v = is_sparse(); template > -constexpr std::size_t undex(U value) noexcept { - static_assert(is_enum_v, "magic_enum::detail::undex requires enum type."); +constexpr U values_ors() noexcept { + static_assert(is_enum_v, "magic_enum::detail::values_ors requires enum type."); - if (const auto i = static_cast(value - min_v); value >= min_v && value <= max_v) { - if constexpr (is_sparse_v) { - if (const auto idx = indexes_v[i]; idx != invalid_index_v) { - return idx; - } - } else { - return i; - } + auto ors = U{0}; + for (std::size_t i = 0; i < count_v; ++i) { + ors |= static_cast(values_v[i]); } - return invalid_index_v; // Value out of range. -} - -template > -constexpr std::size_t endex(E value) noexcept { - static_assert(is_enum_v, "magic_enum::detail::endex requires enum type."); - - return undex(static_cast(value)); -} - -template > -constexpr U value_ors() noexcept { - static_assert(is_enum_v, "magic_enum::detail::endex requires enum type."); - - auto value = U{0}; - for (std::size_t i = 0; i < count_v; ++i) { - value |= static_cast(values_v[i]); - } - - return value; + return ors; } template @@ -656,8 +633,7 @@ using underlying_type_t = typename underlying_type::type; // Returns type name of enum. template [[nodiscard]] constexpr auto enum_type_name() noexcept -> detail::enable_if_enum_t { - using D = std::decay_t; - constexpr string_view name = detail::type_name_v; + constexpr string_view name = detail::type_name_v>; static_assert(name.size() > 0, "Enum type does not have a name."); return name; @@ -666,9 +642,7 @@ template // Returns number of enum values. template [[nodiscard]] constexpr auto enum_count() noexcept -> detail::enable_if_enum_t { - using D = std::decay_t; - - return detail::count_v; + return detail::count_v>; } // Returns enum value at specified index. @@ -676,40 +650,34 @@ template template [[nodiscard]] constexpr auto enum_value(std::size_t index) noexcept -> detail::enable_if_enum_t> { using D = std::decay_t; - static_assert(detail::count_v > 0, "magic_enum requires enum implementation and valid max and min."); if constexpr (detail::is_sparse_v) { return assert((index < detail::count_v)), detail::values_v[index]; } else { - return assert((index < detail::count_v)), detail::value>(index); + constexpr bool is_flag = detail::is_flags_v; + constexpr auto min = is_flag ? detail::log2(detail::min_v) : detail::min_v; + + return assert((index < detail::count_v)), detail::value(index); } } // Returns enum value at specified index. template [[nodiscard]] constexpr auto enum_value() noexcept -> detail::enable_if_enum_t> { - using D = std::decay_t; - static_assert(detail::count_v > 0, "magic_enum requires enum implementation and valid max and min."); - static_assert(I < detail::count_v, "magic_enum::enum_value out of range."); - - return detail::values_v[I]; + return enum_value>(I); } // Returns std::array with enum values, sorted by enum value. template [[nodiscard]] constexpr auto enum_values() noexcept -> detail::enable_if_enum_t> { - using D = std::decay_t; - static_assert(detail::count_v > 0, "magic_enum requires enum implementation and valid max and min."); - - return detail::values_v; + return detail::values_v>; } // 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 [[nodiscard]] constexpr auto enum_name() noexcept -> detail::enable_if_enum_t { - using D = std::decay_t; - constexpr string_view name = detail::enum_name_v; + constexpr string_view name = detail::enum_name_v, V>; static_assert(name.size() > 0, "Enum value does not have a name."); return name; @@ -720,30 +688,65 @@ template template [[nodiscard]] constexpr auto enum_name(E value) noexcept -> detail::enable_if_enum_t { using D = std::decay_t; + using U = underlying_type_t; - if (const auto i = detail::endex(value); i != detail::invalid_index_v) { - return detail::names_v[i]; + if constexpr (detail::is_sparse_v || detail::is_flags_v) { + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (enum_value(i) == value) { + return detail::names_v[i]; + } + } + } else { + const auto v = static_cast(value); + if (v >= detail::min_v && v <= detail::max_v) { + return detail::names_v[static_cast(v - detail::min_v)]; + } } 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 +[[nodiscard]] auto enum_flags_name(E value) -> detail::enable_if_enum_t { + using D = std::decay_t; + using U = underlying_type_t; + + if constexpr (detail::is_flags_v) { + string name; + auto check_value = U{0}; + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (const auto v = static_cast(enum_value(i)); (static_cast(value) & v) != 0) { + check_value |= v; + const auto n = detail::names_v[i]; + if (!name.empty()) { + name.append(1, '|'); + } + name.append(n.data(), n.size()); + } + } + + if (check_value != 0 && check_value == static_cast(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 [[nodiscard]] constexpr auto enum_names() noexcept -> detail::enable_if_enum_t> { - using D = std::decay_t; - static_assert(detail::count_v > 0, "magic_enum requires enum implementation and valid max and min."); - - return detail::names_v; + return detail::names_v>; } // Returns std::array with pairs (value, name), sorted by enum value. template [[nodiscard]] constexpr auto enum_entries() noexcept -> detail::enable_if_enum_t> { - using D = std::decay_t; - static_assert(detail::count_v > 0, "magic_enum requires enum implementation and valid max and min."); - - return detail::entries_v; + return detail::entries_v>; } // Obtains enum value from integer value. @@ -751,9 +754,35 @@ template template [[nodiscard]] constexpr auto enum_cast(underlying_type_t value) noexcept -> detail::enable_if_enum_t>> { using D = std::decay_t; + using U = underlying_type_t; - if (detail::undex(value) != detail::invalid_index_v) { - return static_cast(value); + if constexpr (detail::is_sparse_v) { + constexpr auto count = detail::count_v; + if constexpr (detail::is_flags_v) { + auto check_value = U{0}; + for (std::size_t i = 0; i < count; ++i) { + if (const auto v = static_cast(enum_value(i)); (value & v) != 0) { + check_value |= v; + } + } + + if (check_value != 0 && check_value == value) { + return static_cast(value); + } + } else { + for (std::size_t i = 0; i < count; ++i) { + if (value == static_cast(enum_value(i))) { + return static_cast(value); + } + } + } + } else { + constexpr auto min = detail::min_v; + constexpr auto max = detail::is_flags_v ? detail::values_ors() : detail::max_v; + + if (value >= min && value <= max) { + return static_cast(value); + } } return {}; // Invalid value or out of range. @@ -765,10 +794,35 @@ template [[nodiscard]] constexpr auto enum_cast(string_view value, BinaryPredicate p) noexcept(std::is_nothrow_invocable_r_v) -> detail::enable_if_enum_t>> { static_assert(std::is_invocable_r_v, "magic_enum::enum_cast requires bool(char, char) invocable predicate."); using D = std::decay_t; + using U = underlying_type_t; - for (std::size_t i = 0; i < detail::count_v; ++i) { - if (detail::cmp_equal(value, detail::names_v[i], p)) { - return enum_value(i); + if constexpr (detail::is_flags_v) { + 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; ++i) { + if (detail::cmp_equal(s, detail::names_v[i], p)) { + f = static_cast(enum_value(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(result); + } + } else { + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (detail::cmp_equal(value, detail::names_v[i], p)) { + return enum_value(i); + } } } @@ -795,9 +849,19 @@ template template [[nodiscard]] constexpr auto enum_index(E value) noexcept -> detail::enable_if_enum_t> { using D = std::decay_t; + using U = underlying_type_t; - if (const auto i = detail::endex(value); i != detail::invalid_index_v) { - return i; + if constexpr (detail::is_sparse_v || detail::is_flags_v) { + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (enum_value(i) == value) { + return i; + } + } + } else { + const auto v = static_cast(value); + if (v >= detail::min_v && v <= detail::max_v) { + return static_cast(v - detail::min_v); + } } return {}; // Invalid value or out of range. @@ -807,43 +871,39 @@ template template [[nodiscard]] constexpr auto enum_contains(E value) noexcept -> detail::enable_if_enum_t { using D = std::decay_t; + using U = underlying_type_t; - return detail::endex(value) != detail::invalid_index_v; + return enum_cast(static_cast(value)).has_value(); } // Checks whether enum contains enumerator with such integer value. template [[nodiscard]] constexpr auto enum_contains(underlying_type_t value) noexcept -> detail::enable_if_enum_t { - using D = std::decay_t; - - return detail::undex(value) != detail::invalid_index_v; + return enum_cast>(value).has_value(); } // Checks whether enum contains enumerator with such name. template [[nodiscard]] constexpr auto enum_contains(string_view value, BinaryPredicate p) noexcept(std::is_nothrow_invocable_r_v) -> detail::enable_if_enum_t { static_assert(std::is_invocable_r_v, "magic_enum::enum_contains requires bool(char, char) invocable predicate."); - using D = std::decay_t; - return enum_cast(value, std::move_if_noexcept(p)).has_value(); + return enum_cast>(value, std::move_if_noexcept(p)).has_value(); } // Checks whether enum contains enumerator with such name. template [[nodiscard]] constexpr auto enum_contains(string_view value) noexcept -> detail::enable_if_enum_t { - using D = std::decay_t; - - return enum_cast(value).has_value(); + return enum_cast>(value).has_value(); } namespace ostream_operators { -template , int> = 0> +template = 0> std::basic_ostream& operator<<(std::basic_ostream& os, E value) { using D = std::decay_t; using U = underlying_type_t; #if defined(MAGIC_ENUM_SUPPORTED) && MAGIC_ENUM_SUPPORTED - if (const auto name = magic_enum::enum_name(value); !name.empty()) { + if (const auto name = magic_enum::enum_flags_name(value); !name.empty()) { for (const auto c : name) { os.put(c); } @@ -853,7 +913,7 @@ std::basic_ostream& operator<<(std::basic_ostream& o return (os << static_cast(value)); } -template , int> = 0> +template = 0> std::basic_ostream& operator<<(std::basic_ostream& os, optional value) { return value.has_value() ? (os << value.value()) : os; } @@ -862,295 +922,43 @@ std::basic_ostream& operator<<(std::basic_ostream& o namespace bitwise_operators { -template , int> = 0> +template = 0> constexpr E operator~(E rhs) noexcept { return static_cast(~static_cast>(rhs)); } -template , int> = 0> +template = 0> constexpr E operator|(E lhs, E rhs) noexcept { return static_cast(static_cast>(lhs) | static_cast>(rhs)); } -template , int> = 0> +template = 0> constexpr E operator&(E lhs, E rhs) noexcept { return static_cast(static_cast>(lhs) & static_cast>(rhs)); } -template , int> = 0> +template = 0> constexpr E operator^(E lhs, E rhs) noexcept { return static_cast(static_cast>(lhs) ^ static_cast>(rhs)); } -template , int> = 0> +template = 0> constexpr E& operator|=(E& lhs, E rhs) noexcept { return lhs = (lhs | rhs); } -template , int> = 0> +template = 0> constexpr E& operator&=(E& lhs, E rhs) noexcept { return lhs = (lhs & rhs); } -template , int> = 0> +template = 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 -[[nodiscard]] constexpr auto enum_count() noexcept -> detail::enable_if_enum_t { - using D = std::decay_t; - - return detail::count_v; -} - -// Returns enum-flags value at specified index. -// No bounds checking is performed: the behavior is undefined if index >= number of enum-flags values. -template -[[nodiscard]] constexpr auto enum_value(std::size_t index) noexcept -> detail::enable_if_enum_t> { - using D = std::decay_t; - static_assert(detail::count_v > 0, "magic_enum::flags requires enum-flags implementation."); - - if constexpr (detail::is_sparse_v) { - return assert((index < detail::count_v)), detail::values_v[index]; - } else { - constexpr auto min = detail::log2(detail::min_v); - - return assert((index < detail::count_v)), detail::value(index); - } -} - -// Returns enum-flags value at specified index. -template -[[nodiscard]] constexpr auto enum_value() noexcept -> detail::enable_if_enum_t> { - using D = std::decay_t; - static_assert(detail::count_v > 0, "magic_enum::flags requires enum implementation and valid max and min."); - static_assert(I < detail::count_v, "magic_enum::flags::enum_value out of range."); - - return detail::values_v[I]; -} - -// Returns std::array with enum-flags values, sorted by enum-flags value. -template -[[nodiscard]] constexpr auto enum_values() noexcept -> detail::enable_if_enum_t> { - using D = std::decay_t; - static_assert(detail::count_v > 0, "magic_enum::flags requires enum-flags implementation."); - - return detail::values_v; -} - -// Returns name from enum-flags value. -// If enum-flags value does not have name or value out of range, returns empty string. -template -[[nodiscard]] auto enum_name(E value) -> detail::enable_if_enum_t { - using D = std::decay_t; - using U = underlying_type_t; - - string name; - auto check_value = U{0}; - for (std::size_t i = 0; i < detail::count_v; ++i) { - if (const auto v = static_cast(enum_value(i)); (static_cast(value) & v) != 0) { - check_value |= v; - const auto n = detail::names_v[i]; - if (!name.empty()) { - name.append(1, '|'); - } - name.append(n.data(), n.size()); - } - } - - if (check_value != 0 && check_value == static_cast(value)) { - return name; - } - - return {}; // Invalid value or out of range. -} - -// Returns std::array with string names, sorted by enum-flags value. -template -[[nodiscard]] constexpr auto enum_names() noexcept -> detail::enable_if_enum_t> { - using D = std::decay_t; - static_assert(detail::count_v > 0, "magic_enum::flags requires enum-flags implementation."); - - return detail::names_v; -} - -// Returns std::array with pairs (value, name), sorted by enum-flags value. -template -[[nodiscard]] constexpr auto enum_entries() noexcept -> detail::enable_if_enum_t> { - using D = std::decay_t; - static_assert(detail::count_v > 0, "magic_enum::flags requires enum-flags implementation."); - - return detail::entries_v; -} - -// Obtains enum-flags value from integer value. -// Returns optional with enum-flags value. -template -[[nodiscard]] constexpr auto enum_cast(underlying_type_t value) noexcept -> detail::enable_if_enum_t>> { - using D = std::decay_t; - using U = underlying_type_t; - - if constexpr (detail::is_sparse_v) { - auto check_value = U{0}; - for (std::size_t i = 0; i < detail::count_v; ++i) { - if (const auto v = static_cast(enum_value(i)); (value & v) != 0) { - check_value |= v; - } - } - - if (check_value != 0 && check_value == value) { - return static_cast(value); - } - } else { - constexpr auto min = detail::min_v; - constexpr auto max = detail::value_ors(); - - if (value >= min && value <= max) { - return static_cast(value); - } - } - - return {}; // Invalid value or out of range. -} - -// Obtains enum-flags value from name. -// Returns optional with enum-flags value. -template -[[nodiscard]] constexpr auto enum_cast(string_view value, BinaryPredicate p) noexcept(std::is_nothrow_invocable_r_v) -> detail::enable_if_enum_t>> { - static_assert(std::is_invocable_r_v, "magic_enum::flags::enum_cast requires bool(char, char) invocable predicate."); - using D = std::decay_t; - using U = underlying_type_t; - - 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; ++i) { - if (detail::cmp_equal(s, detail::names_v[i], p)) { - f = static_cast(enum_value(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(result); - } -} - -// Obtains enum-flags value from name. -// Returns optional with enum-flags value. -template -[[nodiscard]] constexpr auto enum_cast(string_view value) noexcept -> detail::enable_if_enum_t>> { - using D = std::decay_t; - - return enum_cast(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 -[[nodiscard]] constexpr auto enum_index(E value) noexcept -> detail::enable_if_enum_t> { - using D = std::decay_t; - using U = underlying_type_t; - - if (detail::is_pow2(static_cast(value))) { - for (std::size_t i = 0; i < detail::count_v; ++i) { - if (enum_value(i) == value) { - return i; - } - } - } - - return {}; // Invalid value or out of range. -} - -// Checks whether enum-flags contains enumerator with such enum-flags value. -template -[[nodiscard]] constexpr auto enum_contains(E value) noexcept -> detail::enable_if_enum_t { - using D = std::decay_t; - using U = underlying_type_t; - - return enum_cast(static_cast(value)).has_value(); -} - -// Checks whether enum-flags contains enumerator with such integer value. -template -[[nodiscard]] constexpr auto enum_contains(underlying_type_t value) noexcept -> detail::enable_if_enum_t { - using D = std::decay_t; - - return enum_cast(value).has_value(); -} - -// Checks whether enum-flags contains enumerator with such name. -template -[[nodiscard]] constexpr auto enum_contains(string_view value, BinaryPredicate p) noexcept(std::is_nothrow_invocable_r_v) -> detail::enable_if_enum_t { - static_assert(std::is_invocable_r_v, "magic_enum::flags::enum_contains requires bool(char, char) invocable predicate."); - using D = std::decay_t; - - return enum_cast(value, std::move_if_noexcept(p)).has_value(); -} - -// Checks whether enum-flags contains enumerator with such name. -template -[[nodiscard]] constexpr auto enum_contains(string_view value) noexcept -> detail::enable_if_enum_t { - using D = std::decay_t; - - return enum_cast(value).has_value(); -} - -} // namespace magic_enum::flags - -namespace flags::ostream_operators { - -template , int> = 0> -std::basic_ostream& operator<<(std::basic_ostream& os, E value) { - using D = std::decay_t; - using U = underlying_type_t; -#if defined(MAGIC_ENUM_SUPPORTED) && MAGIC_ENUM_SUPPORTED - if (const auto name = magic_enum::flags::enum_name(value); !name.empty()) { - for (const auto c : name) { - os.put(c); - } - return os; - } -#endif - return (os << static_cast(value)); -} - -template , int> = 0> -std::basic_ostream& operator<<(std::basic_ostream& os, optional 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__) diff --git a/test/test.cpp b/test/test.cpp index d0541d0..193c10b 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -57,38 +57,33 @@ enum number : unsigned long { _4 = four #endif }; +template <> +struct magic_enum::customize::enum_range { + static constexpr int min = 100; + static constexpr int max = 300; +}; enum class MaxUsedAsInvalid : std::uint8_t { - ONE, - TWO, - INVALID = std::numeric_limits::max() + ONE, + TWO, + INVALID = std::numeric_limits::max() +}; +template <> +struct magic_enum::customize::enum_range { + static constexpr int min = 0; + static constexpr int max = 64; }; enum class Binary : bool { ONE, TWO }; - -namespace magic_enum::customize { template <> -struct enum_range { - static constexpr int min = 0; - static constexpr int max = 64; +struct magic_enum::customize::enum_range { + static constexpr int min = 0; + static constexpr int max = 64; }; -template <> -struct enum_range { - static constexpr int min = 0; - static constexpr int max = 64; -}; - -template <> -struct enum_range { - static constexpr int min = 100; - static constexpr int max = 300; -}; -} // namespace magic_enum - template <> constexpr std::string_view magic_enum::customize::enum_name(Color value) noexcept { switch (value) { @@ -847,69 +842,69 @@ TEST_CASE("extrema") { SECTION("min") { REQUIRE(magic_enum::customize::enum_range::min == MAGIC_ENUM_RANGE_MIN); - REQUIRE(magic_enum::detail::reflected_min_v == 0); + REQUIRE(magic_enum::detail::reflected_min_v == 0); REQUIRE(magic_enum::detail::min_v == 0); REQUIRE(magic_enum::customize::enum_range::min == MAGIC_ENUM_RANGE_MIN); - REQUIRE(magic_enum::detail::reflected_min_v == MAGIC_ENUM_RANGE_MIN); + REQUIRE(magic_enum::detail::reflected_min_v == MAGIC_ENUM_RANGE_MIN); REQUIRE(magic_enum::detail::min_v == -12); REQUIRE(magic_enum::customize::enum_range::min == MAGIC_ENUM_RANGE_MIN); - REQUIRE(magic_enum::detail::reflected_min_v == MAGIC_ENUM_RANGE_MIN); + REQUIRE(magic_enum::detail::reflected_min_v == MAGIC_ENUM_RANGE_MIN); REQUIRE(magic_enum::detail::min_v == 1); REQUIRE(magic_enum::customize::enum_range::min == MAGIC_ENUM_RANGE_MIN); - REQUIRE(magic_enum::detail::reflected_min_v == MAGIC_ENUM_RANGE_MIN); + REQUIRE(magic_enum::detail::reflected_min_v == MAGIC_ENUM_RANGE_MIN); REQUIRE(magic_enum::detail::min_v == -120); #if defined(MAGIC_ENUM_ENABLE_NONASCII) REQUIRE(magic_enum::customize::enum_range::min == MAGIC_ENUM_RANGE_MIN); - REQUIRE(magic_enum::detail::reflected_min_v == MAGIC_ENUM_RANGE_MIN); + REQUIRE(magic_enum::detail::reflected_min_v == MAGIC_ENUM_RANGE_MIN); REQUIRE(magic_enum::detail::min_v == 10); #endif REQUIRE(magic_enum::customize::enum_range::min == 100); - REQUIRE(magic_enum::detail::reflected_min_v == 100); + REQUIRE(magic_enum::detail::reflected_min_v == 100); REQUIRE(magic_enum::detail::min_v == 100); - REQUIRE(magic_enum::detail::reflected_min_v == 0); + REQUIRE(magic_enum::detail::reflected_min_v == 0); REQUIRE(magic_enum::detail::min_v == false); - REQUIRE(magic_enum::detail::reflected_min_v == 0); + REQUIRE(magic_enum::detail::reflected_min_v == 0); REQUIRE(magic_enum::detail::min_v == 0); } SECTION("max") { REQUIRE(magic_enum::customize::enum_range::max == MAGIC_ENUM_RANGE_MAX); - REQUIRE(magic_enum::detail::reflected_max_v == MAGIC_ENUM_RANGE_MAX); + REQUIRE(magic_enum::detail::reflected_max_v == MAGIC_ENUM_RANGE_MAX); REQUIRE(magic_enum::detail::max_v == 2); REQUIRE(magic_enum::customize::enum_range::max == MAGIC_ENUM_RANGE_MAX); - REQUIRE(magic_enum::detail::reflected_max_v == MAGIC_ENUM_RANGE_MAX); + REQUIRE(magic_enum::detail::reflected_max_v == MAGIC_ENUM_RANGE_MAX); REQUIRE(magic_enum::detail::max_v == 15); REQUIRE(magic_enum::customize::enum_range::max == MAGIC_ENUM_RANGE_MAX); - REQUIRE(magic_enum::detail::reflected_max_v == MAGIC_ENUM_RANGE_MAX); + REQUIRE(magic_enum::detail::reflected_max_v == MAGIC_ENUM_RANGE_MAX); REQUIRE(magic_enum::detail::max_v == 3); REQUIRE(magic_enum::customize::enum_range::max == MAGIC_ENUM_RANGE_MAX); - REQUIRE(magic_enum::detail::reflected_max_v == MAGIC_ENUM_RANGE_MAX); + REQUIRE(magic_enum::detail::reflected_max_v == MAGIC_ENUM_RANGE_MAX); REQUIRE(magic_enum::detail::max_v == 120); #if defined(MAGIC_ENUM_ENABLE_NONASCII) REQUIRE(magic_enum::customize::enum_range::max == MAGIC_ENUM_RANGE_MAX); - REQUIRE(magic_enum::detail::reflected_max_v == MAGIC_ENUM_RANGE_MAX); + REQUIRE(magic_enum::detail::reflected_max_v == MAGIC_ENUM_RANGE_MAX); REQUIRE(magic_enum::detail::max_v == 40); #endif REQUIRE(magic_enum::customize::enum_range::max == 300); - REQUIRE(magic_enum::detail::reflected_max_v == 300); + REQUIRE(magic_enum::detail::reflected_max_v == 300); REQUIRE(magic_enum::detail::max_v == 300); - REQUIRE(magic_enum::detail::reflected_max_v == 1); + REQUIRE(magic_enum::detail::reflected_max_v == 1); REQUIRE(magic_enum::detail::max_v == true); - REQUIRE(magic_enum::detail::reflected_max_v == 64); + REQUIRE(magic_enum::detail::reflected_max_v == 64); REQUIRE(magic_enum::detail::max_v == 1); } } diff --git a/test/test_flags.cpp b/test/test_flags.cpp index 00bb0f6..8da3a48 100644 --- a/test/test_flags.cpp +++ b/test/test_flags.cpp @@ -40,6 +40,8 @@ #include enum class Color { RED = 1, GREEN = 2, BLUE = 4 }; +template <> +struct magic_enum::customize::is_flags_enum : 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 { +struct magic_enum::customize::enum_range { 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(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(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(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(du) == "Up"); REQUIRE(enum_name(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(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(lk) == "한국어"); REQUIRE(enum_name(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(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(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(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(0)).empty()); + + constexpr Directions dr = Directions::Right; + auto dr_name = enum_flags_name(dr); + Directions du = Directions::Up; + REQUIRE(enum_flags_name(du) == "Up"); + REQUIRE(enum_flags_name(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(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(lk) == "한국어"); + REQUIRE(enum_flags_name(Language::English) == "English"); + REQUIRE(lang_name == "日本語"); + REQUIRE(enum_flags_name(Language::😃) == "😃"); + REQUIRE(enum_flags_name(static_cast(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(0)).empty()); +} + TEST_CASE("enum_names") { REQUIRE(std::is_same_v()), const std::array&>); @@ -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);