diff --git a/README.md b/README.md index a83f999..d49021d 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ Header-only C++17 library provides static reflection for enums, work with any en * `enum_entries` obtains pair (value enum, string enum name) sequence. * `enum_index` obtains index in enum value sequence from enum value. * `enum_contains` checks whether enum contains enumerator with such value. +* `enum_type_name` returns string name of enum type. * `is_unscoped_enum` checks whether type is an [Unscoped enumeration](https://en.cppreference.com/w/cpp/language/enum#Unscoped_enumeration). * `is_scoped_enum` checks whether type is an [Scoped enumeration](https://en.cppreference.com/w/cpp/language/enum#Scoped_enumerations). * `underlying_type` improved UB-free "SFINAE-friendly" [std::underlying_type](https://en.cppreference.com/w/cpp/types/underlying_type). diff --git a/doc/limitations.md b/doc/limitations.md index 78ac3a5..abe3e63 100644 --- a/doc/limitations.md +++ b/doc/limitations.md @@ -93,5 +93,3 @@ * GCC `-fconstexpr-depth=N`, `-fconstexpr-loop-limit=N`, `-fconstexpr-ops-limit=N` * Intellisense Visual Studio may have some problems analyzing `magic_enum`. - * [Intellisense hangs forever when using `magic_enum` since 0.6.1 on x86 projects.](https://github.com/Neargye/magic_enum/issues/34) - * [Intellisense incorrect analysis constexpr.](https://github.com/Neargye/magic_enum/issues/26) diff --git a/doc/reference.md b/doc/reference.md index 33213b8..cf253e6 100644 --- a/doc/reference.md +++ b/doc/reference.md @@ -10,6 +10,7 @@ * [`enum_entries` obtains pair (value enum, string enum name) sequence.](#enum_entries) * [`enum_index` obtains index in enum value sequence from enum value.](#enum_index) * [`enum_contains` checks whether enum contains enumerator with such value.](#enum_contains) +* [`enum_type_name` returns string name of enum type.](#enum_type_name) * [`is_unscoped_enum` checks whether type is an Unscoped enumeration.](#is_unscoped_enum) * [`is_scoped_enum` checks whether type is an Scoped enumeration.](#is_scoped_enum) * [`underlying_type` improved UB-free "SFINAE-friendly" std::underlying_type.](#underlying_type) @@ -241,6 +242,23 @@ constexpr bool enum_contains(string_view value) noexcept; magic_enum::enum_contains("fda"); // -> false ``` +## `enum_type_name` + +```cpp +template +constexpr string_view enum_type_name() noexcept; +``` + +* Returns `std::string_view` with null-terminated string name of enum type. + +* Examples + + ```cpp + Color color = Color::RED; + auto type_name = magic_enum::enum_type_name(); + // color_name -> "Color" + ``` + ## `is_unscoped_enum` ```cpp diff --git a/include/magic_enum.hpp b/include/magic_enum.hpp index 7b43da4..d5db725 100644 --- a/include/magic_enum.hpp +++ b/include/magic_enum.hpp @@ -46,7 +46,6 @@ #if defined(_MSC_VER) # pragma warning(push) # pragma warning(disable : 26495) // Variable 'magic_enum::detail::static_string::chars' is uninitialized. -# pragma warning(disable : 26451) // Arithmetic overflow: 'indexes[static_cast(value) - min_v]' using operator '-' on a 4 byte value and then casting the result to a 8 byte value. #endif // Checks magic_enum compiler compatibility. @@ -308,6 +307,9 @@ constexpr auto indexes(std::integer_sequence) noexcept { return std::array, sizeof...(I)>{{((n(I + min_v)>().size() != 0) ? i++ : invalid_index_v)...}}; } +template +inline constexpr auto indexes_v = indexes(std::make_integer_sequence>{}); + template constexpr auto names(std::index_sequence) noexcept { static_assert(is_enum_v, "magic_enum::detail::names requires enum type."); @@ -315,6 +317,9 @@ constexpr auto names(std::index_sequence) noexcept { return std::array{{name_v[I]>...}}; } +template +inline constexpr auto names_v = names(std::make_index_sequence>{}); + template constexpr auto entries(std::index_sequence) noexcept { static_assert(is_enum_v, "magic_enum::detail::entries requires enum type."); @@ -322,6 +327,36 @@ constexpr auto entries(std::index_sequence) noexcept { return std::array, sizeof...(I)>{{{values_v[I], name_v[I]>}...}}; } +template +inline constexpr auto entries_v = entries(std::make_index_sequence>{}); + +template +inline static constexpr bool is_sparse = range_size_v != count_v; + +template > +constexpr int undex(U value) noexcept { + static_assert(is_enum_v, "magic_enum::detail::undex requires enum type."); + + if (const auto i = static_cast(value) - min_v; value >= static_cast(min_v) && value <= static_cast(max_v)) { + if constexpr (is_sparse) { + if (const auto idx = indexes_v[i]; idx != invalid_index_v) { + return idx; + } + } else { + return i; + } + } + + return -1; // Value out of range. +} + +template +constexpr int endex(E value) noexcept { + static_assert(is_enum_v, "magic_enum::detail::endex requires enum type."); + + return undex(static_cast>(value)); +} + template using enable_if_enum_t = std::enable_if_t>, R>; @@ -346,76 +381,6 @@ struct underlying_type {}; template struct underlying_type : std::underlying_type> {}; -template > -struct enum_traits {}; - -template -struct enum_traits { - using type = E; - using underlying_type = typename detail::underlying_type::type; - - inline static constexpr std::string_view type_name = detail::type_name_v; - - inline static constexpr bool is_unscoped = detail::is_unscoped_enum::value; - inline static constexpr bool is_scoped = detail::is_scoped_enum::value; - inline static constexpr bool is_dense = detail::range_size_v == detail::count_v; - inline static constexpr bool is_sparse = detail::range_size_v != detail::count_v; - - inline static constexpr std::size_t count = detail::count_v; - inline static constexpr std::array values = detail::values_v; - inline static constexpr std::array names = detail::names(std::make_index_sequence>{}); - inline static constexpr std::array, count> entries = detail::entries(std::make_index_sequence>{}); - - [[nodiscard]] static constexpr bool reflected(E value) noexcept { - return reflected(static_cast(value)); - } - - [[nodiscard]] static constexpr int index(E value) noexcept { - return index(static_cast(value)); - } - - [[nodiscard]] static constexpr E value(std::size_t index) noexcept { - if constexpr (is_sparse) { - return assert(index < count), values[index]; - } else { - return assert(index < count), static_cast(index + min_v); - } - } - - [[nodiscard]] static constexpr std::string_view name(E value) noexcept { - if (const auto i = index(value); i != -1) { - return names[i]; - } - - return {}; // Value out of range. - } - - private: - static_assert(is_enum_v, "magic_enum::enum_traits requires enum type."); - static_assert(supported::value, "magic_enum unsupported compiler (https://github.com/Neargye/magic_enum#compiler-compatibility)."); - static_assert(count > 0, "magic_enum::enum_range requires enum implementation and valid max and min."); - using U = underlying_type; - inline static constexpr auto indexes = detail::indexes(std::make_integer_sequence>{}); - - static constexpr bool reflected(U value) noexcept { - return value >= static_cast(reflected_min_v) && value <= static_cast(reflected_max_v); - } - - static constexpr int index(U value) noexcept { - if (value >= static_cast(min_v) && value <= static_cast(max_v)) { - if constexpr (is_sparse) { - if (const auto i = indexes[value - min_v]; i != invalid_index_v) { - return i; - } - } else { - return value - min_v; - } - } - - return -1; // Value out of range. - } -}; - } // namespace magic_enum::detail // Checks is magic_enum supported compiler. @@ -448,105 +413,47 @@ struct underlying_type : detail::underlying_type {}; template using underlying_type_t = typename underlying_type::type; -// Enum traits defines a compile-time template-based interface to query the properties of enum. +// Returns string name of enum type. template -using enum_traits = detail::enum_traits>; +[[nodiscard]] constexpr auto enum_type_name() noexcept -> detail::enable_if_enum_t { + constexpr std::string_view name = detail::type_name_v>; + static_assert(name.size() > 0, "Enum type does not have a name."); -// Obtains enum value from enum string name. -// Returns std::optional with enum value. -template -[[nodiscard]] constexpr auto enum_cast(std::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; - - if constexpr (detail::range_size_v > detail::count_v * 2) { - for (std::size_t i = 0; i < enum_traits::count; ++i) { - if (detail::cmp_equal(value, enum_traits::names[i], p)) { - return enum_traits::values[i]; - } - } - } else { - for (auto i = detail::min_v; i <= detail::max_v; ++i) { - if (detail::cmp_equal(value, enum_traits::name(static_cast(i)), p)) { - return static_cast(i); - } - } - } - - return std::nullopt; // Invalid value or out of range. + return name; } +// Returns number of enum values. template -[[nodiscard]] constexpr auto enum_cast(std::string_view value) noexcept -> detail::enable_if_enum_t>> { - return enum_cast(value, detail::char_equal{}); -} +[[nodiscard]] constexpr auto enum_count() noexcept -> detail::enable_if_enum_t { + static_assert(detail::supported::value, "magic_enum unsupported compiler (https://github.com/Neargye/magic_enum#compiler-compatibility)."); + static_assert(detail::count_v> > 0, "magic_enum requires enum implementation and valid max and min."); -// Obtains enum value from integer value. -// Returns std::optional with enum value. -template -[[nodiscard]] constexpr auto enum_cast(underlying_type_t value) noexcept -> detail::enable_if_enum_t>> { - using D = std::decay_t; - - if (enum_traits::index(static_cast(value)) != -1) { - return static_cast(value); - } - - return std::nullopt; // Invalid value or out of range. -} - -// Returns integer value from enum value. -template -[[nodiscard]] constexpr auto enum_integer(E value) noexcept -> detail::enable_if_enum_t> { - return static_cast>(value); -} - -// Obtains index in enum value sequence from enum value. -// Returns std::optional with index. -template -[[nodiscard]] constexpr auto enum_index(E value) noexcept -> detail::enable_if_enum_t> { - if (const auto i = enum_traits::index(value); i != -1) { - return i; - } - - return std::nullopt; // Value out of range. -} - -// Checks whether enum contains enumerator with such value. -template -[[nodiscard]] constexpr auto enum_contains(E value) noexcept -> detail::enable_if_enum_t { - return enum_traits::index(value) != -1; -} - -// 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 { - return enum_cast(value).has_value(); -} - -// Checks whether enum contains enumerator with such string enum name. -template -[[nodiscard]] constexpr auto enum_contains(std::string_view value) noexcept -> detail::enable_if_enum_t { - return enum_cast(value).has_value(); + return detail::count_v>; } // Returns enum value at specified index. // No bounds checking is performed: the behavior is undefined if index >= number of enum values. template [[nodiscard]] constexpr auto enum_value(std::size_t index) noexcept -> detail::enable_if_enum_t> { - return enum_traits::value(index); + static_assert(detail::supported::value, "magic_enum unsupported compiler (https://github.com/Neargye/magic_enum#compiler-compatibility)."); + static_assert(detail::count_v> > 0, "magic_enum requires enum implementation and valid max and min."); + using D = std::decay_t; + + if constexpr (detail::is_sparse) { + return assert(index < detail::count_v), detail::values_v[index]; + } else { + return assert(index < detail::count_v), static_cast(index + detail::min_v); + } } // Obtains value enum sequence. // Returns std::array with enum values, sorted by enum value. template -[[nodiscard]] constexpr auto enum_values() noexcept -> detail::enable_if_enum_t::values)&> { - return enum_traits::values; -} +[[nodiscard]] constexpr auto enum_values() noexcept -> detail::enable_if_enum_t>)&> { + static_assert(detail::supported::value, "magic_enum unsupported compiler (https://github.com/Neargye/magic_enum#compiler-compatibility)."); + static_assert(detail::count_v> > 0, "magic_enum requires enum implementation and valid max and min."); -// Returns number of enum values. -template -[[nodiscard]] constexpr auto enum_count() noexcept -> detail::enable_if_enum_t { - return enum_traits::count; + return detail::values_v>; } // Returns string enum name from static storage enum variable. @@ -563,21 +470,131 @@ template // If enum value does not have name or value out of range, returns empty string. template [[nodiscard]] constexpr auto enum_name(E value) noexcept -> detail::enable_if_enum_t { - return enum_traits::name(value); + static_assert(detail::supported::value, "magic_enum unsupported compiler (https://github.com/Neargye/magic_enum#compiler-compatibility)."); + static_assert(detail::count_v> > 0, "magic_enum requires enum implementation and valid max and min."); + using D = std::decay_t; + + if (const auto i = detail::endex(value); i != -1) { + return detail::names_v[i]; + } + + return {}; // Value out of range. } // Obtains string enum name sequence. // Returns std::array with string enum names, sorted by enum value. template -[[nodiscard]] constexpr auto enum_names() noexcept -> detail::enable_if_enum_t::names)&> { - return enum_traits::names; +[[nodiscard]] constexpr auto enum_names() noexcept -> detail::enable_if_enum_t>)&> { + static_assert(detail::supported::value, "magic_enum unsupported compiler (https://github.com/Neargye/magic_enum#compiler-compatibility)."); + static_assert(detail::count_v> > 0, "magic_enum requires enum implementation and valid max and min."); + + return detail::names_v>; } // Obtains pair (value enum, string enum name) sequence. // Returns std::array with std::pair (value enum, string enum name), sorted by enum value. template -[[nodiscard]] constexpr auto enum_entries() noexcept -> detail::enable_if_enum_t::entries)&> { - return enum_traits::entries; +[[nodiscard]] constexpr auto enum_entries() noexcept -> detail::enable_if_enum_t>)&> { + static_assert(detail::supported::value, "magic_enum unsupported compiler (https://github.com/Neargye/magic_enum#compiler-compatibility)."); + static_assert(detail::count_v> > 0, "magic_enum requires enum implementation and valid max and min."); + + return detail::entries_v>; +} + +// Obtains enum value from enum string name. +// Returns std::optional with enum value. +template +[[nodiscard]] constexpr auto enum_cast(std::string_view value, BinaryPredicate p) noexcept(std::is_nothrow_invocable_r_v) -> detail::enable_if_enum_t>> { + static_assert(detail::supported::value, "magic_enum unsupported compiler (https://github.com/Neargye/magic_enum#compiler-compatibility)."); + static_assert(detail::count_v> > 0, "magic_enum requires enum implementation and valid max and min."); + static_assert(std::is_invocable_r_v, "magic_enum::enum_cast requires bool(char, char) invocable predicate."); + using D = std::decay_t; + + if constexpr (detail::range_size_v > detail::count_v * 2) { + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (detail::cmp_equal(value, detail::names_v[i], p)) { + return detail::values_v[i]; + } + } + } else { + for (auto i = detail::min_v; i <= detail::max_v; ++i) { + if (detail::cmp_equal(value, enum_name(static_cast(i)), p)) { + return static_cast(i); + } + } + } + + return std::nullopt; // Invalid value or out of range. +} + +template +[[nodiscard]] constexpr auto enum_cast(std::string_view value) noexcept -> detail::enable_if_enum_t>> { + static_assert(detail::supported::value, "magic_enum unsupported compiler (https://github.com/Neargye/magic_enum#compiler-compatibility)."); + static_assert(detail::count_v> > 0, "magic_enum requires enum implementation and valid max and min."); + + return enum_cast(value, detail::char_equal{}); +} + +// Obtains enum value from integer value. +// Returns std::optional with enum value. +template +[[nodiscard]] constexpr auto enum_cast(underlying_type_t value) noexcept -> detail::enable_if_enum_t>> { + static_assert(detail::supported::value, "magic_enum unsupported compiler (https://github.com/Neargye/magic_enum#compiler-compatibility)."); + static_assert(detail::count_v> > 0, "magic_enum requires enum implementation and valid max and min."); + using D = std::decay_t; + + if (detail::undex(value) != -1) { + return static_cast(value); + } + + return std::nullopt; // Invalid value or out of range. +} + +// Returns integer value from enum value. +template +[[nodiscard]] constexpr auto enum_integer(E value) noexcept -> detail::enable_if_enum_t> { + return static_cast>(value); +} + +// Obtains index in enum value sequence from enum value. +// Returns std::optional with index. +template +[[nodiscard]] constexpr auto enum_index(E value) noexcept -> detail::enable_if_enum_t> { + static_assert(detail::supported::value, "magic_enum unsupported compiler (https://github.com/Neargye/magic_enum#compiler-compatibility)."); + static_assert(detail::count_v> > 0, "magic_enum requires enum implementation and valid max and min."); + + if (const auto i = detail::endex>(value); i != -1) { + return i; + } + + return std::nullopt; // Value out of range. +} + +// Checks whether enum contains enumerator with such value. +template +[[nodiscard]] constexpr auto enum_contains(E value) noexcept -> detail::enable_if_enum_t { + static_assert(detail::supported::value, "magic_enum unsupported compiler (https://github.com/Neargye/magic_enum#compiler-compatibility)."); + static_assert(detail::count_v> > 0, "magic_enum requires enum implementation and valid max and min."); + + return detail::endex>(value) != -1; +} + +// 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 { + static_assert(detail::supported::value, "magic_enum unsupported compiler (https://github.com/Neargye/magic_enum#compiler-compatibility)."); + static_assert(detail::count_v> > 0, "magic_enum requires enum implementation and valid max and min."); + + return detail::undex>(value) != -1; +} + +// Checks whether enum contains enumerator with such string enum name. +template +[[nodiscard]] constexpr auto enum_contains(std::string_view value) noexcept -> detail::enable_if_enum_t { + static_assert(detail::supported::value, "magic_enum unsupported compiler (https://github.com/Neargye/magic_enum#compiler-compatibility)."); + static_assert(detail::count_v> > 0, "magic_enum requires enum implementation and valid max and min."); + + return enum_cast(value).has_value(); } namespace ostream_operators { diff --git a/test/test.cpp b/test/test.cpp index 9629e88..ad3b2ac 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -567,31 +567,12 @@ TEST_CASE("type_traits") { REQUIRE_FALSE(is_scoped_enum_v); } -TEST_CASE("enum_traits") { - REQUIRE(enum_traits::type_name == "Color"); - REQUIRE(enum_traits::type_name == "Numbers"); - REQUIRE(enum_traits::type_name == "Directions"); - REQUIRE(enum_traits::type_name == "number"); - REQUIRE_FALSE(enum_traits::is_unscoped); - REQUIRE_FALSE(enum_traits::is_unscoped); - REQUIRE(enum_traits::is_unscoped); - REQUIRE(enum_traits::is_unscoped); - - REQUIRE(enum_traits::is_scoped); - REQUIRE(enum_traits::is_scoped); - REQUIRE_FALSE(enum_traits::is_scoped); - REQUIRE_FALSE(enum_traits::is_scoped); - - REQUIRE_FALSE(enum_traits::is_dense); - REQUIRE(enum_traits::is_dense); - REQUIRE_FALSE(enum_traits::is_dense); - REQUIRE_FALSE(enum_traits::is_dense); - - REQUIRE(enum_traits::is_sparse); - REQUIRE_FALSE(enum_traits::is_sparse); - REQUIRE(enum_traits::is_sparse); - REQUIRE(enum_traits::is_sparse); +TEST_CASE("enum_type_name") { + REQUIRE(enum_type_name() == "Color"); + REQUIRE(enum_type_name() == "Numbers"); + REQUIRE(enum_type_name() == "Directions"); + REQUIRE(enum_type_name() == "number"); } #if defined(_MSC_VER) && _MSC_VER >= 1920 @@ -628,6 +609,11 @@ TEST_CASE("extrema") { NONE = std::numeric_limits::max() }; + REQUIRE(magic_enum::enum_name(BadColor::NONE).empty()); + REQUIRE_FALSE(magic_enum::enum_cast(std::numeric_limits::max()).has_value()); + REQUIRE_FALSE(magic_enum::enum_contains(std::numeric_limits::max())); + REQUIRE_FALSE(magic_enum::enum_contains(BadColor::NONE)); + SECTION("min") { REQUIRE(magic_enum::enum_range::min == MAGIC_ENUM_RANGE_MIN); REQUIRE(magic_enum::detail::reflected_min_v == 0);