Below example is taken from P2996 verbatim:
#include <experimental/meta>
#include <string>
#include <type_traits>
#include <print>
template<typename E, bool Enumerable = std::meta::is_enumerable_type(^^E)>
requires std::is_enum_v<E>
constexpr std::string_view enum_to_string(E value) {
if constexpr (Enumerable) {
template for (constexpr auto e : define_static_array(enumerators_of(^^E)))
if (value == [:e:])
return identifier_of(e);
}
return "<unnamed>";
}
int main() {
enum Color : int;
static_assert(enum_to_string(Color(0)) == "<unnamed>");
std::println("Color 0: {}", enum_to_string(Color(0))); // prints '<unnamed>'
enum Color : int { red, green, blue };
static_assert(enum_to_string(Color::red) == "red");
static_assert(enum_to_string(Color(42)) == "<unnamed>");
std::println("Color 0: {}", enum_to_string(Color(0))); // prints 'red'
}
I didn't understand why it is necessary to declare Enumerable
as a template constant instead of directly calling is_enumerable_type
within constexpr.
I tried modifying this part:
template<typename E, bool Enumerable = std::meta::is_enumerable_type(^^E)>
requires std::is_enum_v<E>
constexpr std::string_view enum_to_string(E value) {
if constexpr (Enumerable) {
as this:
template<typename E>
requires std::is_enum_v<E>
constexpr std::string_view enum_to_string(E value) {
if constexpr (std::meta::is_enumerable_type(^^E)) {
but it didn't compile in the experimental P2996 clang implementation:
Output of x86-64 clang (experimental P2996) (Compiler #1)
<source>:24:17: error: static assertion failed due to requirement 'enum_to_string(Color::red) == "red"'
24 | static_assert(enum_to_string(Color::red) == "red");
|
https://godbolt.org/z/38enPGjoY
Why doesn't the second example work?
Is this a limitation of the prototype implementation or is it due to some requirements by specs?
Paring the main function to:
enum Color : int;
static_assert(enum_to_string(Color(0)) == "<unnamed>");
enum Color : int { red, green, blue };
static_assert(enum_to_string(Color::red) == "red");
With the default template argument, these two calls become enum_to_string<Color, false>(Color(0))
and enum_to_string<Color, true>(Color::red)
.
Without it, they are both enum_to_string<Color>(Color(0))
(or Color::red
which is the same value). This leads to an ODR violation because std::meta::is_enumerable_type(^^E)
would return a different value.
If two different points of instantiation give a template specialization different meanings according to the one-definition rule, the program is ill-formed, no diagnostic required.
- In each such definition, corresponding manifestly constant-evaluated expressions that are not value-dependent shall have the same value ([expr.const], [temp.dep.constexpr]).
This is not a problem with the default argument version because enum_to_string<Color, false>
and enum_to_string<Color, true>
are different functions (and the value is always the same in enum_to_string<Color, false>
and enum_to_string<Color, true>
)
It is similar to if you had done this:
template<typename E, bool B = requires { E::red; }>
constexpr bool has_red_good(E) { return B; }
template<typename E>
constexpr bool has_red_bad(E) { return requires { E::red; }; }
int main() {
enum Color : int;
static_assert(!has_red_good(Color(0)));
static_assert(!has_red_bad(Color(0)));
enum Color : int { red, green, blue };
static_assert(has_red_good(Color(0)));
static_assert(!has_red_bad(Color(0))); // Ill-formed NDR
}