diff --git a/include/magic_enum.hpp b/include/magic_enum.hpp index 0226883..61f639a 100644 --- a/include/magic_enum.hpp +++ b/include/magic_enum.hpp @@ -141,6 +141,34 @@ constexpr std::string_view pretty_name(std::string_view name) noexcept { return {}; // Invalid name. } +template +constexpr bool mixed_sign_less(L left, R right) noexcept { + static_assert(std::is_integral_v, "L must be an integral value"); + static_assert(std::is_integral_v, "R must be an integral value"); + if constexpr(std::is_signed_v == std::is_signed_v) { + // If same signedness (both signed or both unsigned) + return left < right; + } + else if constexpr(std::is_signed_v) { + // if 'left' is negative, then result is 'true', otherwise cast & compare + return left < 0 || static_cast>(left) < right; + } + else { // std::is_signed_v + // if 'right' is negative, then result is 'false', otherwise cast & compare + return right >= 0 && left < static_cast>(right); + } +} + +template +constexpr int mixed_sign_min_as_int(L left, R right) noexcept { + return mixed_sign_less(left, right) ? static_cast(left) : static_cast(right); +} + +template +constexpr int mixed_sign_max_as_int(L left, R right) noexcept { + return mixed_sign_less(left, right) ? static_cast(right) : static_cast(left); +} + template constexpr auto n() noexcept { static_assert(is_enum_v, "magic_enum::detail::n requires enum type."); @@ -182,14 +210,10 @@ template inline constexpr auto name_v = n(); template -inline constexpr int reflected_min_v = static_cast(enum_range::min > (std::numeric_limits>::min)() - ? enum_range::min - : (std::numeric_limits>::min)()); +inline constexpr int reflected_min_v = mixed_sign_max_as_int(enum_range::min, std::numeric_limits>::min()); template -inline constexpr int reflected_max_v = static_cast(enum_range::max < (std::numeric_limits>::max)() - ? enum_range::max - : (std::numeric_limits>::max)()); +inline constexpr int reflected_max_v = mixed_sign_min_as_int(enum_range::max, std::numeric_limits>::max()); template constexpr std::size_t reflected_size() { diff --git a/test/test.cpp b/test/test.cpp index 71eddfe..81bd3a1 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -469,3 +469,87 @@ TEST_CASE("enum_traits") { REQUIRE(enum_traits::type_name == "number"); } } + +TEST_CASE("extrema") { + enum class BadColor : uint64_t + { + RED, + GREEN, + YELLOW, + // The value NONE is ignored (out of range). + // However, it affects the value of min_v. When reflected_min_v was incorrect, the + // presence of NONE caused miv_v to be equal to -1, which was then cast to unsigned, + // leading to a value of 18446744073709551615 (numeric_limit_max of uint64_t). + NONE = std::numeric_limits::max(), + }; + SECTION("min") { + REQUIRE(magic_enum::enum_range::min == MAGIC_ENUM_RANGE_MIN); + REQUIRE(magic_enum::detail::reflected_min_v == 0); + REQUIRE(magic_enum::detail::min_v == 0); + } + SECTION("max") { + REQUIRE(magic_enum::enum_range::max == MAGIC_ENUM_RANGE_MAX); + REQUIRE(magic_enum::detail::reflected_max_v == MAGIC_ENUM_RANGE_MAX); + REQUIRE(magic_enum::detail::max_v == 2); + } +} + +TEST_CASE("mixed_sign_less") { + using magic_enum::detail::mixed_sign_less; + + constexpr uint64_t uint64_t_min = std::numeric_limits::min(); + constexpr uint32_t uint32_t_min = std::numeric_limits::min(); + constexpr uint32_t uint32_t_max = std::numeric_limits::max(); + constexpr uint64_t uint64_t_max = std::numeric_limits::max(); + + constexpr int64_t int64_t_min = std::numeric_limits::min(); + constexpr int32_t int32_t_min = std::numeric_limits::min(); + constexpr int32_t int32_t_max = std::numeric_limits::max(); + constexpr int64_t int64_t_max = std::numeric_limits::max(); + + // Also testing with offset to avoid corner cases + // Two variables to avoid hidden casts + constexpr int64_t offset_int64_t = 17; + constexpr int32_t offset_int32_t = 17; + + SECTION("same signedness") { + REQUIRE(mixed_sign_less(-5, -3)); + REQUIRE(mixed_sign_less(27U, 49U)); + } + SECTION("same signedness, different width") { + REQUIRE(mixed_sign_less(uint32_t_max, uint64_t_max)); + REQUIRE(!mixed_sign_less(uint64_t_max, uint32_t_max)); + REQUIRE(mixed_sign_less(int64_t_min, int32_t_min)); + REQUIRE(!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(!mixed_sign_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)); + } + SECTION("left signed, right unsigned, different width") { + REQUIRE(mixed_sign_less(int32_t_max, uint64_t_max)); + REQUIRE(!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(!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)); + } + SECTION("left unsigned, right signed") { + REQUIRE(!mixed_sign_less(3U, -5)); + REQUIRE(mixed_sign_less(3U, 5)); + } + SECTION("left unsigned, right signed, different width") { + REQUIRE(mixed_sign_less(uint32_t_max, int64_t_max)); + REQUIRE(!mixed_sign_less(uint64_t_max, int32_t_max)); + REQUIRE(!mixed_sign_less(uint32_t_min, int64_t_min)); + REQUIRE(!mixed_sign_less(uint64_t_min, int32_t_min)); + REQUIRE(mixed_sign_less(uint32_t_max, int64_t_max - offset_int32_t)); + REQUIRE(!mixed_sign_less(uint64_t_max, int32_t_max - offset_int64_t)); + REQUIRE(!mixed_sign_less(uint32_t_min, int64_t_min + offset_int32_t)); + REQUIRE(!mixed_sign_less(uint64_t_min, int32_t_min + offset_int64_t)); + } +} \ No newline at end of file