std::meta::reflect_constant_{array,string}
Document #: | P3617R0 [Latest] [Status] |
Date: | 2025-05-16 |
Project: | Programming Language C++ |
Audience: |
LEWG |
Reply-to: |
Barry Revzin <barry.revzin@gmail.com> |
One of the Reflection facilities in flight for C++26 is [P3491R2] (define_static_{string,object,array}). It provides three functions:
template <ranges::input_range R> // only if the value_type is char or char8_t consteval auto define_static_string(R&& r) -> ranges::range_value_t<R> const*; template <class T> consteval auto define_static_object(T&& v) -> remove_reference_t<T> const*; template <ranges::input_range R> consteval auto define_static_array(R&& r) -> span<ranges::range_value_t<R> const>;
These are very useful additions to C++26.
However, there are cases where having a span<T const>
or char const*
is insufficient.
Matthias Wippich sent us some examples of such cases. Consider C++20 code such as:
template <size_t N> struct FixedString { char data[N] = {}; constexpr FixedString(char const(&str)[N]) { ::ranges::copy(str, str+N, data); std} }; template <FixedString S> struct Test { };
This is a widely used pattern for being able to pass string literals as template arguments. However, there is no way to programmatically produce a string to pass in as an argument:
Approach
|
Result
|
---|---|
using A = Test<"foo">; |
✅ |
using B = [: substitute(^^Test, {reflect_constant("foo"sv)}) :]; |
❌ Error: std::string_view
isn’t structural, but even if it was, this wouldn’t work because you
couldn’t deduce the size of S . |
using C = Test<define_static_string("foo")>; |
❌ Error: cannot deduce the size of
S |
using D = [:substitute(^^Test, {reflect_constant(define_static_string("foo"))}):]; |
❌ Error: cannot deduce the size of
S |
The issue here is that
define_static_string
returns a char const*
,
which loses size information. If, instead, we had a lower layer function
that returned a reflection of an array, we could easily use that:
Approach
|
Result
|
---|---|
using E = Test<[:reflect_constant_string("foo"):]>; |
✅ |
using F = [:substitute(^^Test, {reflect_constant_string("foo")}):]; |
✅ |
Another situation in which this comes up is in dealing with
reflections of members. When you want to iterate over your members
one-at-a-time, then
define_static_array
coupled with
[P1306R4]
(Expansion Statements) is perfectly sufficient:
template <class T> auto f(T const& var) -> void { template for (constexpr auto M : define_static_array(nsdms(^^T))) { (var.[:M:]); do_something_with} }
However, some situations require all the members at once.
Such as in my struct of
arrays implementation.
define_static_array
simply returns a
span
, but if we had a function that
returned a reflection of an array, then combining [P1061R10]
(Structured Bindings can introduce a Pack) with [P2686R5]
(constexpr structured bindings and references to constexpr
variables) lets us do this instead:
Status Quo
|
Proposed
|
---|---|
|
|
With the reflection of an array object, we can directly splice it and use structured bindings to get the pack of members we need all in one go — without any layers of indirection. It’s a much more direct solution.
Having the higher level facilities is very useful, having this additional lower level facility would also be useful.
The implementation is actually very straightforward.
define_static_array
already has to
produce a reflection of an array in order to do its job. So we simply
split that step in two:
Status Quo
|
Proposed
|
---|---|
|
|
The only difference is exposing std::reflect_constant_array
instead of it being an implementation detail of std::define_static_array
.
And similar for std::reflect_constant_string
.
It’s a simple refactoring.
This is a pure library addition and doesn’t strictly need to be in C++26. Moreover, it’s implementable on top of [P2996R12] today.
However, I suspect it’s a useful one that people will do themselves and that will proliferate anyway. It really seems closer to completing/fixing [P3491R2] than being a novel proposal in its own right.
The other advantage of having it in the compiler rather than
user-defined is that the implementation can do better than this by
allowing the __fixed_array
objects
to overlap (see discussion in other paper).
So I think we should try to do it for C++26. There is minimal wording review overhead, given that (as you can see below), this feature simply moves the [P3491R2] wording somewhere else, rather than introducing a lot of new wording.
Add the two facilities:
namespace std::meta { template <ranges::input_range R> // only if the value_type is char or char8_t consteval auto reflect_constant_string(R&& r) -> info; template <ranges::input_range R> consteval auto reflect_constant_array(R&& r) -> info; }
The naming here mirrors std::meta::reflect_constant
.
Since these facilities return a reflection, it makes sense for them to
live in
std::meta
,
unlike define_static_{string,array}
— which only use reflection as an implementation detail.
The wording is presented as a diff on [P3491R2].
Change 6.7.2 [intro.object] to
instead have the results of reflect_constant_{string,array}
be potentially non-unique:
9 An object is a potentially non-unique object if it is
- (9.1) a string literal object ([lex.string]),
- (9.2) the backing array of an initializer list ([dcl.init.ref]),
- (9.3) the object declared by a call to
std::define_static_string
std::meta::reflect_constant_string
orstd::define_static_array
std::meta::reflect_constant_array
, or- (9.4) a subobject thereof.
And likewise in 6.8.4 [basic.compound]:
? A pointer value pointing to a potentially non-unique object
O
([intro.object]) is associated with the evaluation of thestring-literal
([lex.string]),orinitializer list ([dcl.init.list]), or a call to eitherstd::define_static_string
orstd::define_static_array
([meta.define.static])std::meta::reflect_constant_string
orstd::meta::reflect_constant_array
([meta.reflection.array]) that resulted in the string literal object or backing array, respectively, that isO
or of whichO
is a subobject. [ Note 1: A pointer value obtained by pointer arithmetic ([expr.add]) from a pointer value associated with an evaluationE
is also associated withE
. — end note ]
Add to [meta.syn], by
reflect_constant
:
namespace std::meta { // [meta.reflection.result], expression result reflection template<class T> consteval info reflect_value(const T& value); template<class T> consteval info reflect_object(T& object); template<class T> consteval info reflect_function(T& fn); + // [meta.reflection.array], promoting to runtime storage + template <ranges::input_range R> + consteval info reflect_constant_string(R&& r); + + template <ranges::input_range R> + consteval info reflect_constant_array(R&& r); }
Move the corresponding wording from [meta.define.static] to the new clause [meta.reflection.array]. The words here are the same as in the other paper — it’s just that we’re returning a reflection now instead of extracting a value out of it:
1 The functions in this clause are useful for promoting compile-time storage into runtime storage.
template <ranges::input_range R> consteval info reflect_constant_string(R&& r);
2 Let
CharT
beranges::range_value_t<R>
.3 Mandates:
CharT
is one ofchar
,wchar_t
,char8_t
,char16_t
, orchar32_t
.4 Let
V
be the pack of elements of typeCharT
inr
. Ifr
is a string literal, thenV
does not include the trailing null terminator ofr
.5 Let
P
be the template parameter object ([temp.param]) of typeconst CharT[sizeof...(V)+1]
initialized with{V..., CharT()}
.6 Returns:
^^P
.7 [ Note 2:
P
is a potentially non-unique object ([intro.object]) — end note ]template <ranges::input_range R> consteval info reflect_constant_array(R&& r);
8 Let
T
beranges::range_value_t<R>
.9 Mandates:
T
is a structural type ([temp.param]) andconstructible_from<T, ranges::range_reference_t<R>>
istrue
andcopy_constructible<T>
istrue
.10 Let
V
be the pack of elements of typeT
constructed from the elements ofr
.11 Let
P
be the template parameter object ([temp.param]) of typeconst T[sizeof...(V)]
initialized with{V...}
.12 Returns:
^^P
.13 [ Note 3:
P
is a potentially non-unique object ([intro.object]) — end note ]
And then simplify the corresponding wording in [meta.define.static]:
1 The functions in this clause are useful for promoting compile-time storage into runtime storage.
template <ranges::input_range R> consteval const ranges::range_value_t<R>* define_static_string(R&& r);
2 Let
CharT
beranges::range_value_t<R>
.3 Mandates:
CharT
is one ofchar
,wchar_t
,char8_t
,char16_t
, orchar32_t
.4 Let
V
be the pack of elements of typeCharT
inr
. Ifr
is a string literal, thenV
does not include the trailing null terminator ofr
.5 Let
P
be the template parameter object ([temp.param]) of typeconst CharT[sizeof...(V)+1]
initialized with{V..., CharT()}
.6 Returns:
P
.7 [ Note 4:
P
is a potentially non-unique object ([intro.object]) — end note ]8 Effects: Equivalent to:
return extract<const ranges::range_value_t<R>*>(meta::reflect_constant_string(r));
template <class T> consteval const remove_cvref_t<T>* define_static_object(T&& t);
9 Let
U
beremove_cvref_t<T>
.10 Mandates:
U
is a structural type ([temp.param]) andconstructible_from<U, T>
istrue
.11 Let
P
be the template parameter object ([temp.param]) of typeconst U
initialized witht
.12 Returns:
std::addressof(P)
.template <ranges::input_range R> consteval span<const ranges::range_value_t<R>> define_static_array(R&& r);
13 Let
T
beranges::range_value_t<R>
.14 Mandates:
T
is a structural type ([temp.param]) andconstructible_from<T, ranges::range_reference_t<R>>
istrue
andcopy_constructible<T>
istrue
.15 Let
V
be the pack of elements of typeT
constructed from the elements ofr
.16 Let
P
be the template parameter object ([temp.param]) of typeconst T[sizeof...(V)]
initialized with{V...}
.17 Returns:
span<const T>(P)
.18 [ Note 5:
P
is a potentially non-unique object ([intro.object]) — end note ]19 Effects: Equivalent to:
using T = ranges::range_value_t<R>; ::info array = meta::reflect_constant_array(r); metareturn span<const T>(extract<const T*>(array), extent(type_of(array)));
Bump the value of
__cpp_lib_define_static
in
17.3.2 [version.syn]:
- #define __cpp_lib_define_static 2025XX // freestanding, also in <meta> + #define __cpp_lib_define_static 2025XX // freestanding, also in <meta>
Thanks to Matthias Wippich for the insightful observation that led to this paper.