I'm experimenting with the new C++ compile-time reflection features (as described in P2996R0) and I testing a simple enum_to_string()
utility using template for
:
template <typename E>
requires std::is_enum_v<E>
constexpr std::string enum_to_string(E value) {
template for (constexpr auto e : std::meta::enumerators_of(^^E)) {
if (value == [:e:]) {
return std::string(std::meta::identifier_of(e));
}
}
return "<unnamed>";
}
As I understand it, this is not a regular loop but rather a compile-time unrolling of repeated code with different template substitutions.
Is this mechanism compatible with the begin()
/end()
functions used by range-based for
loops?
My concrete questions:
What is the actual type returned by std::meta::enumerators_of(^^E)
?
What enables it to be iterated using template for
? Does template for
rely on begin()
/end()
?
Are there any formal requirements for the right-hand side of a template for
? Does it need to model a specific concept or structure?
Can a user-defined function return something that's usable in a template for
loop?
Is there a way to define our own object that behaves like members_of
or enumerators_of
?
As I understand it, this is not a regular loop but rather a compile-time unrolling of repeated code with different template substitutions.
That's correct. It stamps out the body of the loop once for each iteration.
- What is the actual type returned by
std::meta::enumerators_of(^^E)
?
std::vector<std::meta::info>
.
- What enables it to be iterated using
template for
? Doestemplate for
rely onbegin()
/end()
?- Are there any formal requirements for the right-hand side of a template for? Does it need to model a specific concept or structure?
There are three forms to expansion statements:
{e...}
. This just is one iteration per expression.in that order.
I think the wording is actually quite clear (here so far, will link to the actual draft standard once it's merged in a few days), would recommend just seeing what it does.
- Can a user-defined function return something that's usable in a
template for
loop?- Is there a way to define our own object that behaves like
members_of
orenumerators_of
?
Of course. Any random-access range or destructurable type works.
Note also that the example as written doesn't work. This is because internally we create a static constexpr
variable to hold the range, which we cannot do for a vector
due to lack of non-transient constexpr allocation.
Instead you'll have to first promote the vector
to something that you can create a static constexpr
variable of. We have a workaround for this called define_static_array
, which takes the contents of an arbitrary range, promotes it to a static storage duration array, and returns a span
to it. The full example would become:
template <typename E>
requires std::is_enum_v<E>
constexpr std::string enum_to_string(E value) {
template for (constexpr auto e : define_static_array(enumerators_of(^^E))) {
if (value == [:e:]) {
return std::string(identifier_of(e));
}
}
return "<unnamed>";
}