There's no way to check the declaration order. Best you can do is check that the constants are contiguous (i.e. there are no gaps in the numbering).
First you start with __PRETTY_FUNCTION__
/__FUNSIG__
. This lets you check if the enum has a constant with a specific value:
template <auto X>constexpr const auto &PrettyFuncForValue(){ #ifdef _MSC_VER return __FUNCSIG__; #else return __PRETTY_FUNCTION__; #endif}
enum class E{ a = 10, b = 20, c = 21, d = 22,};// Those outputs here are from Clang, other compilers have something similar.std::cout << PrettyFuncForValue<E::a>() << '\n'; // const auto &PrettyFuncForValue() [X = E::a]std::cout << PrettyFuncForValue<E(10)>() << '\n'; // const auto &PrettyFuncForValue() [X = E::a]std::cout << PrettyFuncForValue<E(11)>() << '\n'; // const auto &PrettyFuncForValue() [X = (E)11]
As you can see, values with associated names result in a different string pattern.
You can then search for this pattern:
template <auto X>inline constexpr bool enum_constant_is_valid = std::string_view(PrettyFuncForValue<X>()).rfind( #ifdef _MSC_VER"<(enum " #else"X = (" #endif ) == std::string_view::npos;
std::cout << enum_constant_is_valid<E::a> << '\n'; // 1std::cout << enum_constant_is_valid<E(10)> << '\n'; // 1std::cout << enum_constant_is_valid<E(11)> << '\n'; // 0
Lastly you slap std::integer_sequence
onto it to check a range of values:
template <auto A, auto B>requires std::same_as<decltype(A), decltype(B)>inline constexpr bool enum_range_is_valid = []<std::underlying_type_t<decltype(A)> ...I>(std::integer_sequence<std::underlying_type_t<decltype(A)>, I...>){ return (enum_constant_is_valid<decltype(A)(std::to_underlying(A) + I)> && ...); }(std::make_integer_sequence<std::underlying_type_t<decltype(A)>, std::to_underlying(B) - std::to_underlying(A) + 1>{});std::cout << enum_range_is_valid<E::a, E::a> << '\n'; // 1std::cout << enum_range_is_valid<E::a, E::b> << '\n'; // 0std::cout << enum_range_is_valid<E::b, E::d> << '\n'; // 1