As far as I know, static reflection is currently on the roadmap for C++26.
The reflection TS proposes a type-based syntax, but a value-based syntax has also been proposed in the meantime. In P2560 Matúš Chochlı́k presented a comparison of the both approaches.
Has a decision already been made on which approach is likely to be standardized on?
The C++ committee met in Kona last week (November 2023), and this was discussed in SG7.
The direction for Reflection for C++26 is outlined in P2996 (currently R0, R1 will be published next week), which is a value-based reflection. At a high level:
^e
is a reflection ofe
(which could be a type, template, namespace, expression, etc.) and is of typestd::meta::info
[: i :]
splicesi
(and is basically the inverse of reflection). So[: ^int :]
is the typeint
std::meta
namespace that takeinfo
orspan<info const>
and returninfo
orvector<info
>SG7 unanimous approved this design, there was no interest in discussing further pursuit of the Reflection TS (i.e. type-based reflection).
In terms of some of the P2560 commentary, I disagree with some of the points made - in particular the arguments that the "pros" of type-based is that it has "better usability", "is easier to teach", and is "more friendly to generic programming."
I'll just note one of the examples in the reflection paper which is to implement
make_integer_sequence
. The goal here is thatmake_integer_sequence<int, 5>
instantiatesinteger_sequence<int, 0, 1, 2, 3, 4>
. How do you implement that in value-based reflection?Here,
substitute
is a reflection API that takes a reflection of a template and a range of reflections of parameters and returns a reflection of the instantiation of that template. For instance,substitute(^std::tuple, {^int, ^char})
gives you^std::tuple<int, char>
, except that it lets you live in the value domain. It's true that you have to learn what that is, but other than that - this is a fairly straightforward algorithm: you make avector
, and you push stuff onto it. In this case, our "stuff" is heterogeneous, since we have one type template argument and a bunch of non-type template arguments, and that works fine sincestd::meta::info
is the only type. How would you implement this in the type-based approach? The problem with type-based metaprogramming is that it cannot really be imperative - it must be functional.It's true that, as P2560 points out, splicing does require you to have a constant-expression to splice back. And that does certainly affect how you have to program things. But the availability of a rich API means that you can stay in the value domain longer than you might think, and that allows you to use the rest of the standard library API to get your work done. For instance:
Given something like
struct S { int a; char& b; };
,struct_to_tuple_type(^S)
will give you a reflection ofstd::tuple<int, char>
. This latter example is very easily doable in type-based reflection as well, it's just that instead ofstd::ranges::transform
you'd usemp_transform
from Boost.Mp11 and so forth. But it's a completely distinct sublanguage - which is why I question the pros listed in that paper?The paper also points out how
count_if
doesn't work. And it's true that this will be a case that is subtle that people have to learn about:The paper is correct that this is inconsistent - though notably we can still use
count_if
for both, which I would argue makes it the approach that is more friendly to generic programming and easier to teach.And we can always come up with ways to make this better. Like:
I think, overall, that's less machinery than you would need for the type-based approach.