1
0
Fork 0
mirror of https://github.com/Neargye/magic_enum.git synced 2026-01-09 23:34:23 +00:00

More reliable value validness detection towards CWG1766 (#398)

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<E, static_cast<E>(V)>` is a valid type, which is
equivalent to whether `static_cast<E>(V)` is a constant expression. When
the answer is `false`, value `V` can't be an enumerator of `E`.
This commit is contained in:
A. Jiang 2025-02-20 16:34:09 +08:00 committed by GitHub
parent 1a1824df7a
commit 2ec43969d8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -621,26 +621,37 @@ constexpr auto enum_name() noexcept {
template <typename E, E V>
inline constexpr auto enum_name_v = enum_name<E, V>();
// 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
template <typename E, auto V, typename = void>
inline constexpr bool is_enum_constexpr_static_cast_valid = false;
template <typename E, auto V>
inline constexpr bool is_enum_constexpr_static_cast_valid<E, V, std::void_t<std::integral_constant<E, static_cast<E>(V)>>> = true;
#else
template <typename, auto>
inline constexpr bool is_enum_constexpr_static_cast_valid = true;
#endif
template <typename E, auto V>
constexpr bool is_valid() noexcept {
#if defined(__clang__) && __clang_major__ >= 16
// https://reviews.llvm.org/D130058, https://reviews.llvm.org/D131307
constexpr E v = __builtin_bit_cast(E, V);
#else
constexpr E v = static_cast<E>(V);
#endif
[[maybe_unused]] constexpr auto custom = customize::enum_name<E>(v);
static_assert(std::is_same_v<std::decay_t<decltype(custom)>, 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 constexpr (is_enum_constexpr_static_cast_valid<E, V>) {
constexpr E v = static_cast<E>(V);
[[maybe_unused]] constexpr auto custom = customize::enum_name<E>(v);
static_assert(std::is_same_v<std::decay_t<decltype(custom)>, 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<E, v>().size_ != 0;
return n<E, v>().size_ != 0;
#else
return n<v>().size_ != 0;
return n<v>().size_ != 0;
#endif
} else {
return false;
}
} else {
return false;
}