From b96c47f8c7d6f4cee63220f66e0fd5ff00b4d529 Mon Sep 17 00:00:00 2001 From: "A. Jiang" Date: Fri, 24 Jan 2025 17:00:49 +0800 Subject: [PATCH] More reliable value validness detection towards CWG1766 CWG1766 made out-of-range cast to enumeration without fixed underlying type raise undefined behavior. Such UB arguably also applies to `bit_cast`, although it's not required that UB in `bit_cast` makes the expression non-constant. Currently, only Clang has implemented CWG1766, while Clang's underlying `__builtin_bit_cast` happens to be a workaround. However, it's more reliable to me to rely on the guarantee that core language UB causes constant evaluation failure. The approach in this patch effectively detects whether `std::integral_constant(V)>` is a valid type, which is equivalent to whether `static_cast(V)` is a constant expression. When the answer is `false`, value `V` can't be an enumerator of `E`. --- include/magic_enum/magic_enum.hpp | 39 ++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/include/magic_enum/magic_enum.hpp b/include/magic_enum/magic_enum.hpp index 158207ee5..7491fae45 100644 --- a/include/magic_enum/magic_enum.hpp +++ b/include/magic_enum/magic_enum.hpp @@ -621,26 +621,37 @@ constexpr auto enum_name() noexcept { template inline constexpr auto enum_name_v = enum_name(); -template -constexpr bool is_valid() noexcept { +// CWG1766: Values outside the range of the values of an enumeration +// https://reviews.llvm.org/D130058, https://reviews.llvm.org/D131307 #if defined(__clang__) && __clang_major__ >= 16 - // https://reviews.llvm.org/D130058, https://reviews.llvm.org/D131307 - constexpr E v = __builtin_bit_cast(E, V); +template +inline constexpr bool is_enum_constexpr_static_cast_valid = false; +template +inline constexpr bool is_enum_constexpr_static_cast_valid(V)>>> = true; #else - constexpr E v = static_cast(V); +template +inline constexpr bool is_enum_constexpr_static_cast_valid = true; #endif - [[maybe_unused]] constexpr auto custom = customize::enum_name(v); - static_assert(std::is_same_v, customize::customize_t>, "magic_enum::customize requires customize_t type."); - if constexpr (custom.first == customize::detail::customize_tag::custom_tag) { - constexpr auto name = custom.second; - static_assert(!name.empty(), "magic_enum::customize requires not empty string."); - return name.size() != 0; - } else if constexpr (custom.first == customize::detail::customize_tag::default_tag) { + +template +constexpr bool is_valid() noexcept { + if constexpr (is_enum_constexpr_static_cast_valid) { + constexpr E v = static_cast(V); + [[maybe_unused]] constexpr auto custom = customize::enum_name(v); + static_assert(std::is_same_v, customize::customize_t>, "magic_enum::customize requires customize_t type."); + if constexpr (custom.first == customize::detail::customize_tag::custom_tag) { + constexpr auto name = custom.second; + static_assert(!name.empty(), "magic_enum::customize requires not empty string."); + return name.size() != 0; + } else if constexpr (custom.first == customize::detail::customize_tag::default_tag) { #if defined(MAGIC_ENUM_VS_2017_WORKAROUND) - return n().size_ != 0; + return n().size_ != 0; #else - return n().size_ != 0; + return n().size_ != 0; #endif + } else { + return false; + } } else { return false; }