diff --git a/include/magic_enum.hpp b/include/magic_enum.hpp index de05c83..78487c4 100644 --- a/include/magic_enum.hpp +++ b/include/magic_enum.hpp @@ -131,6 +131,12 @@ struct static_string<0> { constexpr operator std::string_view() const noexcept { return {}; } }; +struct char_equal { + constexpr bool operator()(char lhs, char rhs) const noexcept { + return lhs == rhs; + } +}; + constexpr std::string_view pretty_name(std::string_view name) noexcept { for (std::size_t i = name.size(); i > 0; --i) { if (!((name[i - 1] >= '0' && name[i - 1] <= '9') || @@ -151,19 +157,33 @@ constexpr std::string_view pretty_name(std::string_view name) noexcept { return {}; // Invalid name. } -template -constexpr bool mixed_sign_less(L lhs, R rhs) noexcept { - static_assert(std::is_integral_v && std::is_integral_v, "magic_enum::detail::mixed_sign_less requires integral type."); +template +constexpr bool cmp_equal(std::string_view lhs, std::string_view rhs, BinaryPredicate p) noexcept(std::is_nothrow_invocable_r_v) { + if (lhs.size() != rhs.size()) { + return false; + } + const auto size = lhs.size(); + for (std::size_t i = 0; i < size; ++i) { + if (!p(lhs[i], rhs[i])) { + return false; + } + } + return true; +} - if constexpr (std::is_signed_v && std::is_unsigned_v) { - // If 'left' is negative, then result is 'true', otherwise cast & compare. - return lhs < 0 || static_cast>(lhs) < rhs; - } else if constexpr (std::is_unsigned_v && std::is_signed_v) { - // If 'right' is negative, then result is 'false', otherwise cast & compare. - return rhs >= 0 && lhs < static_cast>(rhs); - } else { +template +constexpr bool cmp_less(L lhs, R rhs) noexcept { + static_assert(std::is_integral_v && std::is_integral_v, "magic_enum::detail::cmp_less requires integral type."); + + 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_signed_v) { + // If 'right' is negative, then result is 'false', otherwise cast & compare. + return rhs > 0 && lhs < static_cast>(rhs); + } else { + // If 'left' is negative, then result is 'true', otherwise cast & compare. + return lhs < 0 || static_cast>(lhs) < rhs; } } @@ -212,7 +232,7 @@ constexpr int reflected_min() noexcept { static_assert(lhs > (std::numeric_limits::min)(), "magic_enum::enum_range requires min must be greater than INT16_MIN."); constexpr auto rhs = (std::numeric_limits>::min)(); - return mixed_sign_less(lhs, rhs) ? rhs : lhs; + return cmp_less(lhs, rhs) ? rhs : lhs; } template @@ -222,7 +242,7 @@ constexpr int reflected_max() noexcept { static_assert(lhs < (std::numeric_limits::max)(), "magic_enum::enum_range requires max must be less than INT16_MAX."); constexpr auto rhs = (std::numeric_limits>::max)(); - return mixed_sign_less(lhs, rhs) ? lhs : rhs; + return cmp_less(lhs, rhs) ? lhs : rhs; } template @@ -443,19 +463,20 @@ using enum_traits = detail::enum_traits>; // Obtains enum value from enum string name. // Returns std::optional with enum value. -template -[[nodiscard]] constexpr auto enum_cast(std::string_view value) noexcept -> detail::enable_if_enum_t>> { +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 (value == enum_traits::names[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 (value == enum_traits::name(static_cast(i))) { + if (detail::cmp_equal(value, enum_traits::name(static_cast(i)), p)) { return static_cast(i); } } @@ -464,6 +485,11 @@ template 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>> { + return enum_cast(value, detail::char_equal{}); +} + // Obtains enum value from integer value. // Returns std::optional with enum value. template diff --git a/test/test.cpp b/test/test.cpp index d2e9c54..e53b154 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -28,6 +28,7 @@ #include #include +#include #include #include @@ -56,7 +57,7 @@ TEST_CASE("enum_cast") { constexpr auto cr = enum_cast("RED"); REQUIRE(cr.value() == Color::RED); REQUIRE(enum_cast("GREEN").value() == Color::GREEN); - REQUIRE(enum_cast("BLUE").value() == Color::BLUE); + REQUIRE(enum_cast("blue", [](char lhs, char rhs) { return std::tolower(lhs) == std::tolower(rhs); }).value() == Color::BLUE); REQUIRE_FALSE(enum_cast("None").has_value()); constexpr auto no = enum_cast("one"); @@ -626,8 +627,8 @@ TEST_CASE("extrema") { } } -TEST_CASE("mixed_sign_less") { - using magic_enum::detail::mixed_sign_less; +TEST_CASE("cmp_less") { + using magic_enum::detail::cmp_less; constexpr std::uint64_t uint64_t_min = std::numeric_limits::min(); constexpr std::uint32_t uint32_t_min = std::numeric_limits::min(); @@ -645,48 +646,48 @@ TEST_CASE("mixed_sign_less") { constexpr std::int32_t offset_int32_t = 17; SECTION("same signedness") { - REQUIRE(mixed_sign_less(-5, -3)); - REQUIRE(mixed_sign_less(27U, 49U)); + REQUIRE(cmp_less(-5, -3)); + REQUIRE(cmp_less(27U, 49U)); } SECTION("same signedness, different width") { - REQUIRE(mixed_sign_less(uint32_t_max, uint64_t_max)); - REQUIRE_FALSE(mixed_sign_less(uint64_t_max, uint32_t_max)); - REQUIRE(mixed_sign_less(int64_t_min, int32_t_min)); - REQUIRE_FALSE(mixed_sign_less(int32_t_min, int64_t_min)); - REQUIRE(mixed_sign_less(int64_t_min + offset_int64_t, int32_t_min + offset_int32_t)); - REQUIRE_FALSE(mixed_sign_less(int32_t_min + offset_int32_t, int64_t_min + offset_int64_t)); + REQUIRE(cmp_less(uint32_t_max, uint64_t_max)); + REQUIRE_FALSE(cmp_less(uint64_t_max, uint32_t_max)); + REQUIRE(cmp_less(int64_t_min, int32_t_min)); + REQUIRE_FALSE(cmp_less(int32_t_min, int64_t_min)); + REQUIRE(cmp_less(int64_t_min + offset_int64_t, int32_t_min + offset_int32_t)); + REQUIRE_FALSE(cmp_less(int32_t_min + offset_int32_t, int64_t_min + offset_int64_t)); } SECTION("left signed, right unsigned") { - REQUIRE(mixed_sign_less(-5, 3U)); - REQUIRE(mixed_sign_less(3, 5U)); + REQUIRE(cmp_less(-5, 3U)); + REQUIRE(cmp_less(3, 5U)); } SECTION("left signed, right unsigned, different width") { - REQUIRE(mixed_sign_less(int32_t_max, uint64_t_max)); - REQUIRE_FALSE(mixed_sign_less(int64_t_max, uint32_t_max)); - REQUIRE(mixed_sign_less(int32_t_min, uint64_t_min)); - REQUIRE(mixed_sign_less(int64_t_min, uint32_t_min)); - REQUIRE(mixed_sign_less(int32_t_max - offset_int32_t, uint64_t_max)); - REQUIRE_FALSE(mixed_sign_less(int64_t_max - offset_int64_t, uint32_t_max)); - REQUIRE(mixed_sign_less(int32_t_min + offset_int32_t, uint64_t_min)); - REQUIRE(mixed_sign_less(int64_t_min + offset_int64_t, uint32_t_min)); + REQUIRE(cmp_less(int32_t_max, uint64_t_max)); + REQUIRE_FALSE(cmp_less(int64_t_max, uint32_t_max)); + REQUIRE(cmp_less(int32_t_min, uint64_t_min)); + REQUIRE(cmp_less(int64_t_min, uint32_t_min)); + REQUIRE(cmp_less(int32_t_max - offset_int32_t, uint64_t_max)); + REQUIRE_FALSE(cmp_less(int64_t_max - offset_int64_t, uint32_t_max)); + REQUIRE(cmp_less(int32_t_min + offset_int32_t, uint64_t_min)); + REQUIRE(cmp_less(int64_t_min + offset_int64_t, uint32_t_min)); } SECTION("left unsigned, right signed") { - REQUIRE_FALSE(mixed_sign_less(3U, -5)); - REQUIRE(mixed_sign_less(3U, 5)); + REQUIRE_FALSE(cmp_less(3U, -5)); + REQUIRE(cmp_less(3U, 5)); } SECTION("left unsigned, right signed, different width") { - REQUIRE(mixed_sign_less(uint32_t_max, int64_t_max)); - REQUIRE_FALSE(mixed_sign_less(uint64_t_max, int32_t_max)); - REQUIRE_FALSE(mixed_sign_less(uint32_t_min, int64_t_min)); - REQUIRE_FALSE(mixed_sign_less(uint64_t_min, int32_t_min)); - REQUIRE(mixed_sign_less(uint32_t_max, int64_t_max - offset_int32_t)); - REQUIRE_FALSE(mixed_sign_less(uint64_t_max, int32_t_max - offset_int64_t)); - REQUIRE_FALSE(mixed_sign_less(uint32_t_min, int64_t_min + offset_int32_t)); - REQUIRE_FALSE(mixed_sign_less(uint64_t_min, int32_t_min + offset_int64_t)); + REQUIRE(cmp_less(uint32_t_max, int64_t_max)); + REQUIRE_FALSE(cmp_less(uint64_t_max, int32_t_max)); + REQUIRE_FALSE(cmp_less(uint32_t_min, int64_t_min)); + REQUIRE_FALSE(cmp_less(uint64_t_min, int32_t_min)); + REQUIRE(cmp_less(uint32_t_max, int64_t_max - offset_int32_t)); + REQUIRE_FALSE(cmp_less(uint64_t_max, int32_t_max - offset_int64_t)); + REQUIRE_FALSE(cmp_less(uint32_t_min, int64_t_min + offset_int32_t)); + REQUIRE_FALSE(cmp_less(uint64_t_min, int32_t_min + offset_int64_t)); } }