| Document #: | P2996R12 [Latest] [Status] |
| Date: | 2025-05-17 |
| Project: | Programming Language C++ |
| Audience: |
CWG, EWG, LWG |
| Reply-to: |
Wyatt Childers <wcc@edg.com> Peter Dimov <pdimov@gmail.com> Dan Katz <dkatz85@bloomberg.net> Barry Revzin <barry.revzin@gmail.com> Andrew Sutton <andrew.n.sutton@gmail.com> Faisal Vali <faisalv@gmail.com> Daveed Vandevoorde <daveed@edg.com> |
make_integer_sequencehash_appendtuple_cat^^)
[:…:])
std::meta::info
identifier_of,
display_string_of,
source_location_oftype_of,
parent_of,
dealiasobject_of,
constant_oftemplate_of,
template_arguments_ofmembers_of,
static_data_members_of,
nonstatic_data_members_of,
bases_of,
enumerators_ofsubstitutereflect_constant,
reflect_object,
reflect_functionextract<T>data_member_spec,
define_aggregatetypedef
specifierusing enum
declaration<type_traits>
synopsis<meta>
synopsisbit_castSince [P2996R11]:
requires-expressionssplice-expressionsreflect_value and
value_of with
reflect_constant and
constant_of.access_context::current()
(including examples)size_of(r)
is no longer constant if r is a
bit-fieldextract to pull a
pointer from an arraySince [P2996R10]:
has_complete_definition
function with more narrow
is_enumerable_typesplice-specifiers from
appearing in CTAD (following CWG3003)reflect-expression followed
by <splice-expression function
callsextract and
object_of to ensure that both
functions can be used with reflections of local variables declared in
immediate functionstype_of for
enumerators called from within the containing
enum-specifierhas_c_language_linkage,
has_parent,
is_consteval_onlyscope() and
naming_class()
members to access_contextaccess_context::current(),
is_accessible,
members_ofSince [P2996R9]:
sizeof(std::meta::info)<meta>
to [headers]reflect_value and
reflect_object (term was retired by
[P2686R5])Since [P2996R8]:
value_of and
extract are usable with reflections
of local variables in consteval functionsdefine_aggregate cannot be used for
a class currently being definedmembers_of(closure-type)
returns an unspecified sequence of reflectionsstatic_assert-declarations
are not plainly constant-evaluatedtype_of,
alignment_of,
bit_size_of,
data_member_spec,
define_aggregate, type traitsvalue_of to a reflection of a local
variable in a
consteval
functionsubstitute
that instantiation may be triggered if needed to deduce a placeholder
typedefine_aggregateSince [P2996R7]:
^ to
^^ following
adoption of [P3381R0](u8)operator_symbol_of
to (u8)symbol_ofoperators
(exclaim ->
exclamation_mark,
three_way_comparison ->
spaceship, and
ampersand_and ->
ampersand_ampersand)define_class to
define_aggregatedefine_static_array,
define_static_string, and
reflect_invokesizeof(std::meta::info) ==sizeof(void
*)`data_member_options_t to
data_member_options, as per LEWG
feedbackdata_member_options and
name_type are non-structural
consteval-only typesstd::meta is
addressablemember_offsets to
member_offset and changing
member_offset members to be
ptrdiff_t
instead of
size_t, to
allow for future use with negative offsetstype_meow to a more bespoke naming
scheme.reflect_value to take a T const&
instead of a T.is_trivial_type, since
the corresponding type trait was deprecated.id-expression is specially
handled for which
splice-expression should be
handled similarlytemplate-name (i.e.,
introduce a
splice-specialization-specifier
parallel to
simple-template-id)data_member_spec and
bases_ofsplice-template-arguments
in [temp.arg.general]Since [P2996R6]:
accessible_members
family of functionsget_public family of
functionstuple and
variant traitsis_mutable_member
function(u8)operator_symbol_of
functions, tweaked enumerator names in std::meta::operatorsmembers_ofis_user_declared for
completeness with
is_user_providedSince [P2996R5]:
members_of and
define_class. An informal
elaboration on this is included in a new section on “Reachability and
injected declarations”.type_of no longer returns
reflections of
typedef-names; added
elaboration of reasoning to the “Handling
Aliases” section.define_static_array,
has_complete_definition.subobjects_of and
accessible_subobjects_of (will be
reintroduced by [P3293R1]).enumerators_of in terms of
has_complete_definition.reflect_{value, object, function}
are expressed as mandates.is_special_member to
is_special_member_function to align
with core language terminology.(u8)identifier_of,
has_identifier,
extract,
data_member_spec,
define_class,
reflect_invoke,
source_location_of).typedef-name” over “alias
of a type” in formal wording.Since [P2996R4]:
access_pair type, and redid API to
be based on an access_contextis_noexceptspan<info const>
to initializer_list<info>test_trait(u8)name_of
and (u8)qualified_name_of;
added (u8)identifier_of,
operator_of,
define_static_string.display_name_of to
display_string_ofis_enumerator,
is_copy_constructor,
is_move_constructor,
is_assignment,
is_move_assignment,
is_copy_assignment,
is_default_constructor,
has_default_member_initializer,
is_lvalue_reference_qualified,
is_rvalue_reference_qualified, is_literal_operator(_template),
is_conversion_function(_template),
is_operator(_template),
is_data_member_spec, has_(thread|automatic)_storage_durationdata_member_spec, and defined
comparison among reflections returned by it.is_alias to is_(type|namespace)_aliasis_incomplete_type to
is_complete_typeSince [P2996R3]:
u8name_of,
u8qualified_name_of,
u8display_name_of.reflect_value:
separated reflect_result into three
functions: reflect_value,
reflect_object,
reflect_functionis_noexcept to apply to
a wider class of entitiestest_type and
test_types to
test_traithas_module_linkage
metafunctionobject_of
metafunctionSince [P2996R2]:
accessible_members_of
variants to restore a TS-era agreementvalue_of to
extract, and expanded it to operate
on functionscan_substitute,
is_value,
is_object, and (new)
value_ofmeta::info
yield a null reflectionreflect_invoke to support template
argumentstype_ to avoid name clashes. added
more generalized is_const,
is_final, and
is_volatileis_noexcept and fixed
is_explicit to only apply to member
functions, not member function templatesSince [P2996R1], several changes to the overall library API:
qualified_name_of (to
partner with name_of)is_static for being
ambiguous, added
has_internal_linkage (and
has_linkage and
has_external_linkage) and
is_static_member insteadis_class_member,
is_namespace_member, and
is_conceptreflect_invokeOther paper changes:
Since [P2996R0]:
synth_struct to
define_classentity_ref and
pointer_to_member into
value_ofThis is a proposal for a reduced initial set of features to support static reflection in C++. Specifically, we are mostly proposing a subset of features suggested in [P1240R2]:
std::meta::info,^^) that
computes a reflection value for its operand construct,consteval
metafunctions to work with reflections (including deriving
other reflections), and[: refl :]).(Note that this aims at something a little broader than pure “reflection”. We not only want to observe the structure of the program: We also want to ease generating code that depends on those observations. That combination is sometimes referred to as “reflective metaprogramming”, but within WG21 discussion the term “reflection” has often been used informally to refer to the same general idea.)
This proposal is not intended to be the end-game as far as reflection and compile-time metaprogramming are concerned. Instead, we expect it will be a useful core around which more powerful features will be added incrementally over time. In particular, we believe that most or all the remaining features explored in P1240R2 and that code injection (along the lines described in [P2237R0]) are desirable directions to pursue.
Our choice to start with something smaller is primarily motivated by the belief that that improves the chances of these facilities making it into the language sooner rather than later.
While we tried to select a useful subset of the P1240 features, we
also made a few additions and changes. Most of those changes are minor.
For example, we added a std::meta::test_trait
interface that makes it convenient to use existing standard type
predicates (such as is_class_v) in
reflection computations.
One addition does stand out, however: We have added metafunctions that permit the synthesis of simple struct and union types. While it is not nearly as powerful as generalized code injection (see [P2237R0]), it can be remarkably effective in practice.
Perhaps the most common suggestion made regarding the framework
outlined in P1240 is to switch from the single std::meta::info
type to a family of types covering various language elements (e.g.,
std::meta::variable,
std::meta::type,
etc.).
We believe that doing so would be a mistake with very serious consequences for the future of C++.
Specifically, it would codify the language design into the type
system. We know from experience that it has been quasi-impossible to
change the semantics of standard types once they were standardized, and
there is no reason to think that such evolution would become easier in
the future. Suppose for example that we had standardized a reflection
type std::meta::variable
in C++03 to represent what the standard called “variables” at the time.
In C++11, the term “variable” was extended to include “references”. Such
an change would have been difficult to do given that C++ by then likely
would have had plenty of code that depended on a type arrangement around
the more restricted definition of “variable”. That scenario is clearly
backward-looking, but there is no reason to believe that similar changes
might not be wanted in the future and we strongly believe that it
behooves us to avoid adding undue constraints on the evolution of the
language.
Other advantages of a single opaque type include:
std::vector<std::meta::info>
can easily represent a mixed template argument list — containing types
and nontypes — without fear of slicing values).Lock3 implemented the equivalent of much that is proposed here in a fork of Clang (specifically, it worked with the P1240 proposal, but also included several other capabilities including a first-class injection mechanism).
EDG has an ongoing implementation of this proposal that is currently available on Compiler Explorer (thank you, Matt Godbolt).
Additionally, Bloomberg has open sourced a fork of Clang which provides a second implementation of this proposal, also available on Compiler Explorer (again thank you, Matt Godbolt), which can be found here: https://github.com/bloomberg/clang-p2996.
Neither implementation is complete, but all significant features proposed by this paper have been implemented by at least one implementation (including namespace and template splicers). Both implementations have their “quirks” and continue to evolve alongside this paper.
Nearly all of the examples below have links to Compiler Explorer demonstrating them in both EDG and Clang.
The implementations notably lack some of the other proposed language features that dovetail well with reflection; most notably, expansion statements are absent. A workaround that will be used in the linked implementations of examples is the following facility:
namespace __impl { template<auto... vals> struct replicator_type { template<typename F> constexpr void operator>>(F body) const { (body.template operator()<vals>(), ...); } }; template<auto... vals> replicator_type<vals...> replicator = {}; } template<typename R> consteval auto expand(R range) { std::vector<std::meta::info> args; for (auto r : range) { args.push_back(reflect_constant(r)); } return substitute(^^__impl::replicator, args); }
Used like:
With expansion statements
|
With expand
workaround
|
|---|---|
|
|
We start with a number of examples that show off what is possible with the proposed set of features. It is expected that these are mostly self-explanatory. Read ahead to the next sections for a more systematic description of each element of this proposal.
A number of our examples here show a few other language features that we hope to progress at the same time. This facility does not strictly rely on these features, and it is possible to do without them - but it would greatly help the usability experience if those could be adopted as well:
Our first example is not meant to be compelling but to show how to go back and forth between the reflection domain and the grammatical domain:
constexpr auto r = ^^int; typename[:r:] x = 42; // Same as: int x = 42; typename[:^^char:] c = '*'; // Same as: char c = '*';
The
typename
prefix can be omitted in the same contexts as with dependent qualified
names (i.e., in what the standard calls type-only contexts).
For example:
using MyType = [:sizeof(int)<sizeof(long)? ^^long : ^^int:]; // Implicit "typename" prefix.
On Compiler Explorer: EDG, Clang.
Our second example enables selecting a member “by number” for a specific type:
struct S { unsigned i:2, j:6; }; consteval auto member_number(int n) { if (n == 0) return ^^S::i; else if (n == 1) return ^^S::j; } int main() { S s{0, 0}; s.[:member_number(1):] = 42; // Same as: s.j = 42; s.[:member_number(5):] = 0; // Error (member_number(5) is not a constant). }
This example also illustrates that bit fields are not beyond the reach of this proposal.
On Compiler Explorer: EDG, Clang.
Note that a “member access splice” like s.[:member_number(1):]
is a more direct member access mechanism than the traditional syntax. It
doesn’t involve member name lookup, access checking, or — if the spliced
reflection value represents a member function — overload resolution.
This proposal includes a number of consteval “metafunctions” that
enable the introspection of various language constructs. Among those
metafunctions is std::meta::nonstatic_data_members_of
which returns a vector of reflection values that describe the non-static
members of a given type. We could thus rewrite the above example as:
struct S { unsigned i:2, j:6; }; consteval auto member_number(int n) { auto ctx = std::meta::access_context::current(); return std::meta::nonstatic_data_members_of(^^S, ctx)[n]; } int main() { S s{0, 0}; s.[:member_number(1):] = 42; // Same as: s.j = 42; s.[:member_number(5):] = 0; // Error (member_number(5) is not a constant). }
On Compiler Explorer: EDG, Clang.
This proposal specifies that namespace
std::meta is
associated with the reflection type (std::meta::info);
the std::meta::
qualification can therefore be omitted in the example above.
Another frequently-useful metafunction is std::meta::identifier_of,
which returns a std::string_view
describing the identifier with which an entity represented by a given
reflection value was declared. With such a facility, we could
conceivably access non-static data members “by string”:
struct S { unsigned i:2, j:6; }; consteval auto member_named(std::string_view name) { auto ctx = std::meta::access_context::current(); for (std::meta::info field : nonstatic_data_members_of(^^S, ctx)) { if (has_identifier(field) && identifier_of(field) == name) return field; } } int main() { S s{0, 0}; s.[:member_named("j"):] = 42; // Same as: s.j = 42; s.[:member_named("x"):] = 0; // Error (member_named("x") is not a constant). }
On Compiler Explorer: EDG, Clang.
Here, sizes will be a std::array<std::size_t, 3>
initialized with {sizeof(int), sizeof(float), sizeof(double)}:
constexpr std::array types = {^^int, ^^float, ^^double}; constexpr std::array sizes = []{ std::array<std::size_t, types.size()> r; std::ranges::transform(types, r.begin(), std::meta::size_of); return r; }();
Compare this to the following type-based approach, which produces the
same array sizes:
template<class...> struct list {}; using types = list<int, float, double>; constexpr auto sizes = []<template<class...> class L, class... T>(L<T...>) { return std::array<std::size_t, sizeof...(T)>{{ sizeof(T)... }}; }(types{});
On Compiler Explorer: EDG, Clang.
make_integer_sequenceWe can provide a better implementation of
make_integer_sequence than a
hand-rolled approach using regular template metaprogramming (although
standard libraries today rely on an intrinsic for this):
#include <utility> #include <vector> template<typename T> consteval std::meta::info make_integer_seq_refl(T N) { std::vector args{^^T}; for (T k = 0; k < N; ++k) { args.push_back(std::meta::reflect_constant(k)); } return substitute(^^std::integer_sequence, args); } template<typename T, T N> using make_integer_sequence = [:make_integer_seq_refl<T>(N):];
On Compiler Explorer: EDG, Clang.
Note that the memoization implicit in the template substitution
process still applies. So having multiple uses of, e.g., make_integer_sequence<int, 20>
will only involve one evaluation of make_integer_seq_refl<int>(20).
struct member_descriptor { std::size_t offset; std::size_t size; }; // returns std::array<member_descriptor, N> template <typename S> consteval auto get_layout() { constexpr auto ctx = std::meta::access_context::current(); constexpr size_t N = std::meta::nonstatic_data_members_of(^^S, ctx).size(); auto members = std::meta::nonstatic_data_members_of(^^S, ctx); std::array<member_descriptor, N> layout; for (int i = 0; i < members.size(); ++i) { layout[i] = { .offset=static_cast<std::size_t>(std::meta::offset_of(members[i]).bytes), .size=std::meta::size_of(members[i]) }; } return layout; } struct X { char a; int b; double c; }; /*constexpr*/ auto Xd = get_layout<X>(); /* where Xd would be std::array<member_descriptor, 3>{{ { 0, 1 }, { 4, 4 }, { 8, 8 } }} */
On Compiler Explorer: EDG, Clang.
One of the most commonly requested facilities is to convert an enum value to a string (this example relies on expansion statements):
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 : std::define_static_array(std::meta::enumerators_of(^^E))) if (value == [:e:]) return std::meta::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' }
We can also do the reverse in pretty much the same way:
template <typename E, bool Enumerable = std::meta::is_enumerable_type(^^E)> requires std::is_enum_v<E> constexpr std::optional<E> string_to_enum(std::string_view name) { if constexpr (Enumerable) template for (constexpr auto e : std::define_static_array(std::meta::enumerators_of(^^E))) if (name == std::meta::identifier_of(e)) return [:e:]; return std::nullopt; }
But we don’t have to use expansion statements - we can also use
algorithms. For instance,
enum_to_string can also be
implemented this way (this example relies on non-transient constexpr
allocation), which also demonstrates choosing a different algorithm
based on the number of enumerators:
template <typename E> requires std::is_enum_v<E> constexpr std::string enum_to_string(E value) { constexpr auto get_pairs = []{ return std::meta::enumerators_of(^^E) | std::views::transform([](std::meta::info e){ return std::pair<E, std::string>(std::meta::extract<E>(e), std::meta::identifier_of(e)); }) }; constexpr auto get_name = [](E value) -> std::optional<std::string> { if constexpr (enumerators_of(^^E).size() <= 7) { // if there aren't many enumerators, use a vector with find_if() constexpr auto enumerators = get_pairs() | std::ranges::to<std::vector>(); auto it = std::ranges::find_if(enumerators, [value](auto const& pr){ return pr.first == value; }); if (it == enumerators.end()) { return std::nullopt; } else { return it->second; } } else { // if there are lots of enumerators, use a map with find() constexpr auto enumerators = get_pairs() | std::ranges::to<std::map>(); auto it = enumerators.find(value); if (it == enumerators.end()) { return std::nullopt; } else { return it->second; } } }; return get_name(value).value_or("<unnamed>"); }
Note that this last version has lower complexity: While the versions
using an expansion statement use an expected O(N) number of comparisons
to find the matching entry, a
std::map
achieves the same with O(log(N)) complexity (where N is the number of
enumerator constants).
On Compiler Explorer: EDG, Clang.
Many many variations of these functions are possible and beneficial depending on the needs of the client code. For example:
enumerators_of(^^E)enum_to_string and
string_to_enum with a minimal
footprintOur next example shows how a command-line option parser could work by automatically inferring flags based on member names. A real command-line parser would of course be more complex, this is just the beginning.
template<typename Opts> auto parse_options(std::span<std::string_view const> args) -> Opts { Opts opts; constexpr auto ctx = std::meta::access_context::current(); template for (constexpr auto dm : nonstatic_data_members_of(^^Opts, ctx)) { auto it = std::ranges::find_if(args, [](std::string_view arg){ return arg.starts_with("--") && arg.substr(2) == identifier_of(dm); }); if (it == args.end()) { // no option provided, use default continue; } else if (it + 1 == args.end()) { std::print(stderr, "Option {} is missing a value\n", *it); std::exit(EXIT_FAILURE); } using T = typename[:type_of(dm):]; auto iss = std::ispanstream(it[1]); if (iss >> opts.[:dm:]; !iss) { std::print(stderr, "Failed to parse option {} into a {}\n", *it, display_string_of(^^T)); std::exit(EXIT_FAILURE); } } return opts; } struct MyOpts { std::string file_name = "input.txt"; // Option "--file_name <string>" int count = 1; // Option "--count <int>" }; int main(int argc, char *argv[]) { MyOpts opts = parse_options<MyOpts>(std::vector<std::string_view>(argv+1, argv+argc)); // ... }
This example is based on a presentation by Matúš Chochlík.
On Compiler Explorer: EDG, Clang.
#include <meta> template<typename... Ts> struct Tuple { struct storage; consteval { define_aggregate(^^storage, {data_member_spec(^^Ts)...}) } storage data; Tuple(): data{} {} Tuple(Ts const& ...vs): data{ vs... } {} }; template<typename... Ts> struct std::tuple_size<Tuple<Ts...>>: public integral_constant<size_t, sizeof...(Ts)> {}; template<std::size_t I, typename... Ts> struct std::tuple_element<I, Tuple<Ts...>> { static constexpr std::array types = {^^Ts...}; using type = [: types[I] :]; }; consteval std::meta::info get_nth_field(std::meta::info r, std::size_t n) { return nonstatic_data_members_of(r, std::meta::access_context::current())[n]; } template<std::size_t I, typename... Ts> constexpr auto get(Tuple<Ts...> &t) noexcept -> std::tuple_element_t<I, Tuple<Ts...>>& { return t.data.[:get_nth_field(^^decltype(t.data), I):]; } // Similarly for other value categories...
This example uses a “magic” std::meta::define_aggregate
template along with member reflection through the
nonstatic_data_members_of
metafunction to implement a
std::tuple-like
type without the usual complex and costly template metaprogramming
tricks that that involves when these facilities are not available.
define_aggregate takes a reflection
for an incomplete class or union plus a vector of non-static data member
descriptions, and completes the give class or union type to have the
described members.
On Compiler Explorer: EDG, Clang.
Similarly to how we can implement a tuple using
define_aggregate to create on the
fly a type with one member for each
Ts..., we
can implement a variant that simply defines a
union
instead of a
struct. One
difference here is how the destructor of a
union is
currently defined:
union U1 { int i; char c; }; union U2 { int i; std::string s; };
U1 has a trivial destructor, but
U2’s destructor is defined as
deleted (because
std::string
has a non-trivial destructor). This is a problem because we need to
define this thing… somehow. However, for the purposes of
define_aggregate, there really is
only one reasonable option to choose here:
template <class... Ts> union U { // all of our members Ts... members; // a defaulted destructor if all of the types are trivially destructible constexpr ~U() requires (std::is_trivially_destructible_v<Ts> && ...) = default; // ... otherwise a destructor that does nothing constexpr ~U() { } };
If we make define_aggregate
for a union
have this behavior, then we can implement a
variant in a much more
straightforward way than in current implementations. This is not a
complete implementation of
std::variant
(and cheats using libstdc++ internals, and also uses Boost.Mp11’s
mp_with_index) but should
demonstrate the idea:
template <typename... Ts> class Variant { union Storage; struct Empty { }; consteval { define_aggregate(^^Storage, { data_member_spec(^^Empty, {.name="empty"}), data_member_spec(^^Ts)... }); } static consteval std::meta::info get_nth_field(std::size_t n) { auto ctx = std::meta::access_context::current(); return nonstatic_data_members_of(^^Storage, ctx)[n+1]; } Storage storage_; int index_ = -1; // cheat: use libstdc++'s implementation template <typename T> static constexpr size_t accepted_index = std::__detail::__variant::__accepted_index<T, std::variant<Ts...>>; template <class F> constexpr auto with_index(F&& f) const -> decltype(auto) { return mp_with_index<sizeof...(Ts)>(index_, (F&&)f); } public: constexpr Variant() requires std::is_default_constructible_v<Ts...[0]> // should this work: storage_{. [: get_nth_field(0) :]{} } : storage_{.empty={}} , index_(0) { std::construct_at(&storage_.[: get_nth_field(0) :]); } constexpr ~Variant() requires (std::is_trivially_destructible_v<Ts> and ...) = default; constexpr ~Variant() { if (index_ != -1) { with_index([&](auto I){ std::destroy_at(&storage_.[: get_nth_field(I) :]); }); } } template <typename T, size_t I = accepted_index<T&&>> requires (!std::is_base_of_v<Variant, std::decay_t<T>>) constexpr Variant(T&& t) : storage_{.empty={}} , index_(-1) { std::construct_at(&storage_.[: get_nth_field(I) :], (T&&)t); index_ = (int)I; } // you can't actually express this constraint nicely until P2963 constexpr Variant(Variant const&) requires (std::is_trivially_copyable_v<Ts> and ...) = default; constexpr Variant(Variant const& rhs) requires ((std::is_copy_constructible_v<Ts> and ...) and not (std::is_trivially_copyable_v<Ts> and ...)) : storage_{.empty={}} , index_(-1) { rhs.with_index([&](auto I){ constexpr auto field = get_nth_field(I); std::construct_at(&storage_.[: field :], rhs.storage_.[: field :]); index_ = I; }); } constexpr auto index() const -> int { return index_; } template <class F> constexpr auto visit(F&& f) const -> decltype(auto) { if (index_ == -1) { throw std::bad_variant_access(); } return mp_with_index<sizeof...(Ts)>(index_, [&](auto I) -> decltype(auto) { return std::invoke((F&&)f, storage_.[: get_nth_field(I) :]); }); } };
Effectively, Variant<T, U>
synthesizes a union type Storage
which looks like this:
union Storage { Empty empty; T unnamed0; U unnamed1; ~Storage() requires std::is_trivially_destructible_v<T> && std::is_trivially_destructible_v<U> = default; ~Storage() { } }
The question here is whether we should be should be able to directly initialize members of a defined union using a splicer, as in:
: storage{.[: get_nth_field(0) :]={}}
Arguably, the answer should be yes - this would be consistent with how other accesses work. This is instead proposed in [P3293R1].
On Compiler Explorer: EDG, Clang.
#include <meta> #include <array> template <typename T, size_t N> struct struct_of_arrays_impl { struct impl; consteval { auto ctx = std::meta::access_context::current(); std::vector<std::meta::info> old_members = nonstatic_data_members_of(^^T, ctx); std::vector<std::meta::info> new_members = {}; for (std::meta::info member : old_members) { auto array_type = substitute(^^std::array, { type_of(member), std::meta::reflect_constant(N), }); auto mem_descr = data_member_spec(array_type, {.name = identifier_of(member)}); new_members.push_back(mem_descr); } define_aggregate(^^impl, new_members); } }; template <typename T, size_t N> using struct_of_arrays = struct_of_arrays_impl<T, N>::impl;
Example:
struct point { float x; float y; float z; }; using points = struct_of_arrays<point, 30>; // equivalent to: // struct points { // std::array<float, 30> x; // std::array<float, 30> y; // std::array<float, 30> z; // };
Again, the combination of
nonstatic_data_members_of and
define_aggregate is put to good
use.
This example also illustrates some requirements that we have on
define_aggregate. In particular,
that function is said to produce an “injected declaration” and the
target scope of the declaration must be within the same “cone of
instantiation” as the evaluation that produced it. Which means that the
following similar structure is ill-formed:
template <class T, size_t N> struct struct_of_arrays_impl; template <typename T, size_t N> using struct_of_arrays = [: []{ // ... same logic .. // error: the target scope of this declaration is a // different instantiation from the one we are currently in. define_aggregate(^^struct_of_arrays_impl<T, N>, new_members); }() :];
That could be fixed if we reorganize it like this:
template <typename T, size_t N> using struct_of_arrays = [: []{ // ... same logic .. // OK, same instantiation struct impl; define_aggregate(^^impl, new_members); }() :];
But now struct_of_arrays<point, 30>
has no linkage, whereas we wanted it to have external linkage. Hence the
structure in the example above where we are instead defining a nested
class in a class template — so that we have a type with external linkage
but don’t run afoul of the “cone of instantiation” rule.
On Compiler Explorer: EDG, Clang.
Now that we’ve seen a couple examples of using std::meta::define_aggregate
to create a type, we can create a more sophisticated command-line parser
example.
This is the opening example for clap (Rust’s Command Line Argument Parser):
struct Args : Clap { Option<std::string, {.use_short=true, .use_long=true}> name; Option<int, {.use_short=true, .use_long=true}> count = 1; }; int main(int argc, char** argv) { auto opts = Args{}.parse(argc, argv); for (int i = 0; i < opts.count; ++i) { // opts.count has type int std::print("Hello {}!", opts.name); // opts.name has type std::string } }
Which we can implement like this:
struct Flags { bool use_short; bool use_long; }; template <typename T, Flags flags> struct Option { std::optional<T> initializer = {}; // some suitable constructors and accessors for flags }; // convert a type (all of whose non-static data members are specializations of Option) // to a type that is just the appropriate members. // For example, if type is a reflection of the Args presented above, then this // function would evaluate to a reflection of the type // struct { // std::string name; // int count; // } consteval auto spec_to_opts(std::meta::info opts, std::meta::info spec) -> std::meta::info { auto ctx = std::meta::access_context::current(); std::vector<std::meta::info> new_members; for (std::meta::info member : nonstatic_data_members_of(spec, ctx)) { auto type_new = template_arguments_of(type_of(member))[0]; new_members.push_back(data_member_spec(type_new, {.name=identifier_of(member)})); } return define_aggregate(opts, new_members); } struct Clap { template <typename Spec> auto parse(this Spec const& spec, int argc, char** argv) { std::vector<std::string_view> cmdline(argv+1, argv+argc) // check if cmdline contains --help, etc. struct Opts; consteval { spec_to_opts(^^Opts, ^^Spec); } constexpr auto ctx = std::meta::access_context::current(); template for (constexpr auto [sm, om] : std::define_static_array( std::views::zip(nonstatic_data_members_of(^^Spec, ctx), nonstatic_data_members_of(^^Opts, ctx)) | std::views::transform([](auto z) { return std::pair(get<0>(z), get<1>(z)); }))) { auto const& cur = spec.[:sm:]; constexpr auto type = type_of(om); // find the argument associated with this option auto it = std::ranges::find_if(cmdline, [&](std::string_view arg){ return (cur.use_short && arg.size() == 2 && arg[0] == '-' && arg[1] == identifier_of(sm)[0]) || (cur.use_long && arg.starts_with("--") && arg.substr(2) == identifier_of(sm)); }); // no such argument if (it == cmdline.end()) { if constexpr (has_template_arguments(type) and template_of(type) == ^^std::optional) { // the type is optional, so the argument is too continue; } else if (cur.initializer) { // the type isn't optional, but an initializer is provided, use that opts.[:om:] = *cur.initializer; continue; } else { std::print(stderr, "Missing required option {}\n", display_string_of(sm)); std::exit(EXIT_FAILURE); } } else if (it + 1 == cmdline.end()) { std::print(stderr, "Option {} for {} is missing a value\n", *it, display_string_of(sm)); std::exit(EXIT_FAILURE); } // found our argument, try to parse it auto iss = ispanstream(it[1]); if (iss >> opts.[:om:]; !iss) { std::print(stderr, "Failed to parse {:?} into option {} of type {}\n", it[1], display_string_of(sm), display_string_of(type)); std::exit(EXIT_FAILURE); } } return opts; } };
On Compiler Explorer: EDG, Clang.
This example is taken from Boost.Describe:
struct universal_formatter { constexpr auto parse(auto& ctx) { return ctx.begin(); } template <typename T> auto format(T const& t, auto& ctx) const { auto out = std::format_to(ctx.out(), "{}{{", has_identifier(^^T) ? identifier_of(^^T) : "(unnamed-type)";); auto delim = [first=true]() mutable { if (!first) { *out++ = ','; *out++ = ' '; } first = false; }; constexpr auto ctx = std::meta::access_context::unchecked(); template for (constexpr auto base : define_static_array(bases_of(^^T, ctx))) { delim(); out = std::format_to(out, "{}", (typename [: type_of(base) :] const&)(t)); } template for (constexpr auto mem : define_static_array(nonstatic_data_members_of(^^T, ctx))) { delim(); std::string_view mem_label = has_identifier(mem) ? identifier_of(mem) : "(unnamed-member)"; out = std::format_to(out, ".{}={}", mem_label, t.[:mem:]); } *out++ = '}'; return out; } }; struct B { int m0 = 0; }; struct X { int m1 = 1; }; struct Y { int m2 = 2; }; class Z : public X, private Y { int m3 = 3; int m4 = 4; }; template <> struct std::formatter<B> : universal_formatter { }; template <> struct std::formatter<X> : universal_formatter { }; template <> struct std::formatter<Y> : universal_formatter { }; template <> struct std::formatter<Z> : universal_formatter { }; int main() { std::println("{}", Z()); // Z{X{B{.m0=0}, .m1 = 1}, Y{{.m0=0}, .m2 = 2}, .m3 = 3, .m4 = 4} }
On Compiler Explorer: Clang.
Note that currently, we do not have the ability to access a base
class subobject using the t.[: base :]
syntax - which means that the only way to get at the base is to use a
cast:
static_cast<[: type_of(base) const& :]>(t),
or(typename [: type_of(base) :] const&)tBoth have to explicitly specify the
const-ness
of the type in the cast. The
static_cast
additionally has to check access. The C-style cast is one many people
find unsavory, though in this case it avoids checking access - but
requires writing
typename
since this isn’t a type-only context.
hash_appendBased on the [N3980] API:
template <typename H, typename T> requires std::is_standard_layout_v<T> void hash_append(H& algo, T const& t) { constexpr auto ctx = std::meta::access_context::unchecked(); template for (constexpr auto mem : nonstatic_data_members_of(^^T, ctx)) { hash_append(algo, t.[:mem:]); } }
Of course, any production-ready
hash_append would include a facility
for classes to opt members in and out of participation in hashing.
Annotations as proposed by [P3394R2] (Annotations for
Reflection) provides just such a mechanism.
This approach requires allowing packs in structured bindings [P1061R10], but can also be written
using std::make_index_sequence:
template <typename T> constexpr auto struct_to_tuple(T const& t) { constexpr auto ctx = std::meta::access_context::current(); constexpr std::size_t N = nonstatic_data_members_of(^^T, ctx).size(); auto members = nonstatic_data_members(^^T, ctx); constexpr auto indices = []{ std::array<int, N> indices; std::ranges::iota(indices, 0); return indices; }(); constexpr auto [...Is] = indices; return std::make_tuple(t.[: members[Is] :]...); }
An alternative approach is:
consteval auto type_struct_to_tuple(info type) -> info { constexpr auto ctx = std::meta::access_context::current(); return substitute(^^std::tuple, nonstatic_data_members_of(type, ctx) | std::views::transform(std::meta::type_of) | std::views::transform(std::meta::remove_cvref) | std::ranges::to<std::vector>()); } template <typename To, typename From, std::meta::info ... members> constexpr auto struct_to_tuple_helper(From const& from) -> To { return To(from.[:members:]...); } template<typename From> consteval auto get_struct_to_tuple_helper() { using To = [: type_struct_to_tuple(^^From): ]; auto ctx = std::meta::access_context::current(); std::vector args = {^^To, ^^From}; for (auto mem : nonstatic_data_members_of(^^From, ctx)) { args.push_back(reflect_constant(mem)); } /* Alternatively, with Ranges: args.append_range( nonstatic_data_members_of(^^From, ctx) | std::views::transform(std::meta::reflect_constant) ); */ return extract<To(*)(From const&)>( substitute(^^struct_to_tuple_helper, args)); } template <typename From> constexpr auto struct_to_tuple(From const& from) { return get_struct_to_tuple_helper<From>()(from); }
Here, type_struct_to_tuple takes
a reflection of a type like struct { T t; U const& u; V v; }
and returns a reflection of the type std::tuple<T, U, V>.
That gives us the return type. Then,
struct_to_tuple_helper is a function
template that does the actual conversion — which it can do by having all
the reflections of the members as a non-type template parameter pack.
This is a
constexpr
function and not a
consteval
function because in the general case the conversion is a run-time
operation. However, determining the instance of
struct_to_tuple_helper that is
needed is a compile-time operation and has to be performed with a
consteval
function (because the function invokes
nonstatic_data_members_of), hence
the separate function template get_struct_to_tuple_helper().
Everything is put together by using
substitute to create the
instantiation of
struct_to_tuple_helper that we need,
and a compile-time reference to that instance is obtained with
extract. Thus
f is a function reference to the
correct specialization of
struct_to_tuple_helper, which we can
simply invoke.
On Compiler Explorer (with a different implementation than either of the above): EDG, Clang.
tuple_catCourtesy of Tomasz Kaminski, on compiler explorer:
template<std::pair<std::size_t, std::size_t>... indices> struct Indexer { template<typename Tuples> // Can use tuple indexing instead of tuple of tuples auto operator()(Tuples&& tuples) const { using ResultType = std::tuple< std::tuple_element_t< indices.second, std::remove_cvref_t<std::tuple_element_t<indices.first, std::remove_cvref_t<Tuples>>> >... >; return ResultType(std::get<indices.second>(std::get<indices.first>(std::forward<Tuples>(tuples)))...); } }; template <class T> consteval auto subst_by_value(std::meta::info tmpl, std::vector<T> args) -> std::meta::info { std::vector<std::meta::info> a2; for (T x : args) { a2.push_back(std::meta::reflect_constant(x)); } return substitute(tmpl, a2); } consteval auto make_indexer(std::vector<std::size_t> sizes) -> std::meta::info { std::vector<std::pair<int, int>> args; for (std::size_t tidx = 0; tidx < sizes.size(); ++tidx) { for (std::size_t eidx = 0; eidx < sizes[tidx]; ++eidx) { args.push_back({tidx, eidx}); } } return subst_by_value(^^Indexer, args); } template<typename... Tuples> auto my_tuple_cat(Tuples&&... tuples) { constexpr typename [: make_indexer({tuple_size(remove_cvref(^^Tuples))...}) :] indexer; return indexer(std::forward_as_tuple(std::forward<Tuples>(tuples)...)); }
The tricky thing with implementing a named tuple is actually strings
as non-type template parameters. Because you cannot just pass "x" into
a non-type template parameter of the form
auto V, that
leaves us with two ways of specifying the constituents:
pair type so
that we can write make_named_tuple<pair<int, "x">, pair<double, "y">>(),
ormake_named_tuple<^^int, std::meta::reflect_constant("x"),
^^double, std::meta::reflect_constant("y")>()We do not currently support splicing string literals, and the
pair approach follows the similar
pattern already shown with
define_aggregate (given a suitable
fixed_string type):
template <class T, fixed_string Name> struct pair { static constexpr auto name() -> std::string_view { return Name.view(); } using type = T; }; template <class... Tags> consteval auto make_named_tuple(std::meta::info type, Tags... tags) { std::vector<std::meta::info> nsdms; auto f = [&]<class Tag>(Tag tag){ nsdms.push_back(data_member_spec( dealias(^^typename Tag::type), {.name=Tag::name()})); }; (f(tags), ...); return define_aggregate(type, nsdms); } struct R; consteval { make_named_tuple(^^R, pair<int, "x">{}, pair<double, "y">{}); } constexpr auto ctx = std::meta::access_context::current(); static_assert(type_of(nonstatic_data_members_of(^^R, ctx)[0]) == ^^int); static_assert(type_of(nonstatic_data_members_of(^^R, ctx)[1]) == ^^double); int main() { [[maybe_unused]] auto r = R{.x=1, .y=2.0}; }
On Compiler Explorer: EDG, Clang.
Alternatively, can side-step the question of non-type template parameters entirely by keeping everything in the value domain:
consteval auto make_named_tuple(std::meta::info type, std::initializer_list<std::pair<std::meta::info, std::string_view>> members) { std::vector<std::meta::data_member_spec> nsdms; for (auto [type, name] : members) { nsdms.push_back(data_member_spec(type, {.name=name})); } return define_aggregate(type, nsdms); } struct R; consteval { make_named_tuple(^^R, {{^^int, "x"}, {^^double, "y"}}); } constexpr auto ctx = std::meta::access_context::current(); static_assert(type_of(nonstatic_data_members_of(^^R, ctx)[0]) == ^^int); static_assert(type_of(nonstatic_data_members_of(^^R, ctx)[1]) == ^^double); int main() { [[maybe_unused]] auto r = R{.x=1, .y=2.0}; }
On Compiler Explorer: EDG
and Clang (the EDG and Clang implementations differ only in Clang
having the updated data_member_spec
API that returns an info, and the
updated name define_aggregate).
The features proposed here make it a little easier to update a ticket
counter at compile time. This is not an ideal implementation (we’d
prefer direct support for compile-time —– i.e.,
consteval —
variables), but it shows how compile-time mutable state surfaces in new
ways.
template<int N> struct Helper; struct TU_Ticket { static consteval int latest() { int k = 0; while (is_complete_type(substitute(^^Helper, { std::meta::reflect_constant(k) }))) ++k; return k; } static consteval void increment() { define_aggregate(substitute(^^Helper, { std::meta::reflect_constant(latest())}), {}); } }; constexpr int x = TU_Ticket::latest(); // x initialized to 0. consteval { TU_Ticket::increment(); } constexpr int y = TU_Ticket::latest(); // y initialized to 1. consteval { TU_Ticket::increment(); } constexpr int z = TU_Ticket::latest(); // z initialized to 2. static_assert(x == 0); static_assert(y == 1); static_assert(z == 2);
On Compiler Explorer: EDG, Clang.
^^)The reflection operator produces a reflection value from a grammatical construct (its operand):
unary-expression:
…
^^::
^^namespace-name
^^type-id
^^id-expression
The expression
^^::
evaluates to a reflection of the global namespace. When the operand is a
namespace-name or
type-id, the resulting
value is a reflection of the designated namespace or type.
When the operand is an
id-expression, the
resulting value is a reflection of the designated entity found by
lookup. This might be any of:
For all other operands, the expression is ill-formed. In a SFINAE context, a failure to substitute the operand of a reflection operator construct causes that construct to not evaluate to constant.
Earlier revisions of this paper allowed for taking the reflection of
any cast-expression that
could be evaluated as a constant expression, as we believed that a
constant expression could be internally “represented” by just capturing
the value to which it evaluated. However, the possibility of side
effects from constant evaluation (introduced by this very paper) renders
this approach infeasible: even a constant expression would have to be
evaluated every time it’s spliced. It was ultimately decided to defer
all support for expression reflection, but we intend to introduce it
through a future paper using the syntax ^^(expr).
This paper does, however, support reflections of values and of objects (including subobjects). Such reflections arise naturally when iterating over template arguments.
template <int P1, const int &P2> void fn() {} static constexpr int p[2] = {1, 2}; constexpr auto spec = ^^fn<p[0], p[1]>; static_assert(is_value(template_arguments_of(spec)[0])); static_assert(is_object(template_arguments_of(spec)[1])); static_assert(!is_variable(template_arguments_of(spec)[1])); static_assert([:template_arguments_of(spec)[0]:] == 1); static_assert(&[:template_arguments_of(spec)[1]:] == &p[1]);
Such reflections cannot generally be obtained using the
^^-operator,
but the std::meta::reflect_constant
and std::meta::reflect_object
functions make it easy to reflect particular values or objects. The
std::meta::constant_of
metafunction can also be used to map a reflection of an object to a
reflection of its value.
The original TS landed on reflexpr(...)
as the syntax to reflect source constructs and [P1240R0] adopted that syntax as well.
As more examples were discussed, it became clear that that syntax was
both (a) too “heavy” and (b) insufficiently distinct from a function
call. SG7 eventually agreed upon the prefix
^ operator.
The “upward arrow” interpretation of the caret matches the “lift” or
“raise” verbs that are sometimes used to describe the reflection
operation in other contexts.
The caret already has a meaning as a binary operator in C++
(“exclusive OR”), but that is clearly not conflicting with a prefix
operator. In C++/CLI (a Microsoft C++ dialect) the caret is also used as
a new kind of ptr-operator
(9.3.1 [dcl.decl.general])
to declare “handles”.
That is also not conflicting with the use of the caret as a unary
operator because C++/CLI uses the usual prefix
* operator
to dereference handles.
Apple also uses the caret in syntax “blocks” and unfortunately we believe that does conflict with our proposed use of the caret.
Since the syntax discussions in SG7 landed on the use of the caret,
new basic source characters have become available:
@,
`, and
$. While we have since discussed
some alternatives (e.g.,
@ for
lifting, \ and
/ for
“raising” and “lowering”), we have grown quite fond of the existing
syntax.
In Wrocław 2024, SG7 and EWG voted to adopt
^^ as the
new reflection operator (as proposed by [P3381R0]). The R8 revision of this
paper integrates that change.
[:…:])A reflection can be “spliced” into source code using one of several splicer forms:
[: r :]
produces an expression evaluating to the entity represented by
r in grammatical contexts that
permit expressions. In type-only contexts (13.8.1 [temp.res.general]/4),
[: r :]
produces a type (and r must be the
reflection of a type). In contexts that only permit a namespace name,
[: r :]
produces a namespace (and r must be
the reflection of a namespace or alias thereof).typename[: r :]
produces a simple-type-specifier corresponding to the type
represented by r.template[: r :]
produces a template-name corresponding to the template
represented by r.[:r:]::
produces a nested-name-specifier corresponding to the
namespace, enumeration type, or class type represented by
r.The operand of a splicer is implicitly converted to a std::meta::info
prvalue (i.e., if the operand expression has a class type that with a
conversion function to convert to std::meta::info,
splicing can still work).
Attempting to splice a reflection value that does not meet the requirement of the splice is ill-formed. For example:
typename[: ^^:: :] x = 0; // Error.
In the same way that &C::mem
can produce a pointer, pointer to member data, pointer to function, or
pointer to member function depending on what
mem refers to, &[: r :]
can likewise produce the same set of pointers if
r is a reflection of a suitable
entity:
r is a reflection of a static
data member or a variable, &[:r:]
is a pointer.r is a reflection
of a non-static data member, &[:r:]
is a pointer to data member.r is a reflection
of a static member function, a function, or a non-static member function
with an explicit object parameter, &[:r:]
is a pointer to functionr is a reflection
of a non-static member function with an implicit object parameter, &[:r:]
is a pointer to member function.r is a reflection
of a function template, &[:r:]
is the address of that overload set - which would then require external
context to resolve as usual.For most members, this doesn’t even require any additional wording since that’s just what you get when you take the address of the splice based on the current rules we have today.
Now, there are a couple interesting cases to point out when &[:r:]
isn’t just the same as &X::f.
When r is a reflection of a
function or function template that is part of an overload set, overload
resolution will not consider the whole overload set, just the specific
function or function template that r
represents:
struct C { template <class T> void f(T); // #1 void f(int); // #2 }; void (C::*p1)(int) = &C::f; // error: ambiguous constexpr auto f1 = ((members_of(^^C) | std::views::filter(std::meta::is_function_template)).front()); constexpr auto f2 = ((members_of(^^C) | std::views::filter(std::meta::is_function)).front()); void (C::*p2)(int) = &[:f1:]; // ok, refers to C::f<int> (#1) void (C::*p3)(int) = &[:f2:]; // ok, refers to C::f (#2)
Another interesting question is what does this mean when
r is the reflection of a constructor
or destructor? Consider the type:
struct X { X(int, int); };
And let rc be a reflection of the
constructor and rd be a reflection
of the destructor. The sensible syntax and semantics for how you would
use rc and
rd should be as follows:
auto x = [: rc :](1, 2); // gives you an X x.[: rd :](); // destroys it
Or, with pointers:
auto pc = &[: rc :]; auto pd = &[: rd :]; auto x = (*pc)(1, 2); // gives you an X (x.*pd)(); // destroys it
That is, splicing a constructor behaves like a free function that
produces an object of that type, so &[: rc :]
has type X(*)(int, int).
On the other hand, splicing a destructor behaves like a regular member
function, so &[: rd :]
has type void (X::*)().
However, we are not proposing splicing constructors or destructors at the moment.
Splicers can appear in many contexts, but our implementation experience has uncovered a small set of circumstances in which a splicer must be disallowed. Mostly these are because any entity designated by a splicer can be dependent on a template argument, so any context in which the language already disallows a dependent name must also disallow a dependent splicer. It also becomes possible for the first time to have the “name” of a namespace or concept become dependent on a template argument. Our implementation experience has helped to sort through which uses of these dependent names pose no difficulties, and which must be disallowed.
This proposal places the following limitations on splicers.
Iterating over the members of a class (e.g., using std::meta::members_of)
allows one, for the first time, to obtain “handles” representing
constructors. An immediate question arises of whether it’s possible to
reify these constructors to construct objects, or even to take their
address. While we are very interested in exploring these ideas, we defer
their discussion to a future paper; this proposal disallows splicing a
reflection of a constructor (or constructor template) in any
context.
namespace A {} constexpr std::meta::info NS_A = ^^A; namespace B { namespace [:NS_A:] { void fn(); // Is this '::A::fn' or '::B::A::fn' ? } }
We found no satisfying answer as to how to interpret examples like the one given above. Neither did we find motivating use cases: many of the “interesting” uses for reflections of namespaces are either to introspect their members, or to pass them as template arguments - but the above example does nothing to help with introspection, and neither can namespaces be reopened within any dependent context. Rather than choose between unintuitive options for a syntax without a motivating use case, we are disallowing splicers from appearing in the opening of a namespace.
template <std::meta::info R> void fn1() { using enum [:R:]::EnumCls; // #1 // ... } template <std::meta::info R> void fn2() { using namespace [:R:]; // #2 // ... }
C++20 already disallowed dependent enumeration types from appearing in using-enum-declarators (as in #1), as it would otherwise force the parser to consider every subsequent identifier as possibly a member of the substituted enumeration type. We extend this limitation to splices of dependent reflections of enumeration types, and further disallow the use of dependent reflections of namespaces in using-directives (as in #2) following the same principle.
template <typename T> concept C = requires { requires true; }; template <std::meta::info R> struct Outer { template <template [:R:] S> struct Inner { /* ... */ }; };
What kind of parameter is S? If
R represents a class template, then
it is a non-type template parameter of deduced type, but if
R represents a concept, it is a type
template parameter. There is no other circumstance in the language for
which it is not possible to decide at parse time whether a template
parameter is a type or a non-type, and we don’t wish to introduce one
for this use case.
The most obvious solution would be to introduce a concept [:R:]
syntax that requires that R reflect
a concept, and while this could be added going forward, we weren’t
convinced of its value at this time - especially since the above can
easily be rewritten:
template <std::meta::info R> struct Outer { template <typename T> requires template [:R:]<T> struct Inner { /* ... */ }; };
We are resolving this ambiguity by simply disallowing a reflection of
a concept, whether dependent or otherwise, from being spliced in the
declaration of a template parameter (thus in the above example, the
parser can assume that S is a
non-type parameter).
struct S { int a; };
constexpr S s = {.[:^^S::a:] = 2};Although we would like for splices of class members to be usable as designators in an initializer-list, we lack implementation experience with the syntax and would first like to verify that there are no issues with dependent reflections. We are very likely to propose this as an extension in a future paper.
The splicers described above all take a single object of type std::meta::info
(described in more detail below). However, there are many cases where we
don’t have a single reflection, we have a range of reflections - and we
want to splice them all in one go. For that, the predecessor to this
paper, [P1240R0], proposed an additional form
of splicer: a range splicer.
Construct the struct-to-tuple example from above. It was demonstrated using a single splice, but it would be simpler if we had a range splice:
With Single Splice
|
With Range Splice
|
|---|---|
|
|
A range splice, [: ... r :],
would accept as its argument a constant range of
meta::info,
r, and would behave as an unexpanded
pack of splices. So the above expression
make_tuple(t.[: ... members :]...)
would evaluate as
make_tuple(t.[:members[0]:], t.[:members[1]:], ..., t.[:members[N-1]:])
This is a very useful facility indeed!
However, range splicing of dependent arguments is at least an order of magnitude harder to implement than ordinary splicing. We think that not including range splicing gives us a better chance of having reflection in C++26. Especially since, as this paper’s examples demonstrate, a lot can be done without them.
Another way to work around a lack of range splicing would be to
implement with_size<N>(f),
which would behave like f(integral_constant<size_t, 0>{}, integral_constant<size_t, 1>{}, ..., integral_constant<size_t, N-1>{}).
Which is enough for a tolerable implementation:
template <typename T> constexpr auto struct_to_tuple(T const& t) { constexpr auto members = nonstatic_data_members_of(^^T); return with_size<members.size()>([&](auto... Is){ return std::make_tuple(t.[: members[Is] :]...); }); }
Early discussions of splice-like constructs (related to the TS
design) considered using unreflexpr(...)
for that purpose. [P1240R0] adopted that option for
expression splicing, observing that a single splicing syntax
could not viably be parsed (some disambiguation is needed to distinguish
types and templates). SG-7 eventually agreed to adopt the [: ... :]
syntax — with disambiguating tokens such as
typename
where needed — which is a little lighter and more distinctive.
We propose
[: and
:] be single
tokens rather than combinations of
[,
], and
:. Among
others, it simplifies the handling of expressions like arr[[:refl():]].
On the flip side, it requires a special rule like the one that was made
to handle
<:: to
leave the meaning of arr[::N]
unchanged and another one to avoid breaking a (somewhat useless)
attribute specifier of the form [[using ns:]].
A syntax that is delimited on the left and right is useful here because spliced expressions may involve lower-precedence operators. Additionally, it’s important that the left- and right-hand delimiters are different so as to allow nested splices when that comes up.
However, there are other possibilities. For example, now that
$ or
@ are available in the basic source
character set, we might consider those. One option that was recently
brought up was @ primary-expression
which would allow writing
@e for the
simple identifier splices
but for the more complex operations still require parenthesizing for
readability. $<expr>
is somewhat natural to those of us that have used systems where
$ is used to expand placeholders in
document templates:
[::]
|
[: :]
(with space)
|
@
|
$
|
|---|---|---|---|
[:refl:] |
[: refl :] |
@refl |
$refl |
[:type_of(refl):] |
[: type_of(refl) :] |
@(type_of(refl)) |
$(type_of(refl)) |
There are two other pieces of functionality that we will probably need syntax for in the future:
+ as an
annotation introducer, but
+ can begin
an expression so another token is probably better. See also: this
thread).So any syntax discussion needs to consider the entirety of the feature.
The prefixes
typename and
template are
only strictly needed in some cases where the operand of the splice is a
dependent expression. In our proposal, however, we only make
typename
optional in the same contexts where it would be optional for qualified
names with dependent name qualifiers. That has the advantage to catch
unfortunate errors while keeping a single rule and helping human readers
parse the intended meaning of otherwise ambiguous constructs.
std::meta::infoThe type std::meta::info
can be defined as follows:
namespace std { namespace meta { using info = decltype(^^::); } }
In our initial proposal a value of type std::meta::info
can represent:
We for now restrict the space of reflectable values to those of structural type in order to meet two requirements:
Values of structural types can already be used as template arguments
(so implementations must already know how to mangle them), and the
notion of template-argument-equivalent values defined on the
class of structural types helps guarantee that &fn<^^value1> == &fn<^^value2>
if and only if &fn<value1> == &fn<value2>.
Notably absent at this time are reflections of expressions. For example, one might wish to walk over the subexpressions of a function call:
template <typename T> void fn(T) {} void g() { constexpr auto call = ^^(fn(42)); static_assert( template_arguments_of(function_of(call))[0] == ^^int); }
Previous revisions of this proposal suggested limited support for reflections of constant expressions. The introduction of side effects from constant evaluations (by this very paper), however, renders this roughly as difficult for constant expressions as it is for non-constant expressions. We instead defer all expression reflection to a future paper, and only present value and object reflection in the present proposal.
The type std::meta::info
is a scalar type for which equality and inequality are
meaningful, but for which no ordering relation is defined.
static_assert(^^int == ^^int); static_assert(^^int != ^^const int); static_assert(^^int != ^^int &); using Alias = int; static_assert(^^int != ^^Alias); static_assert(^^int == dealias(^^Alias)); namespace AliasNS = ::std; static_assert(^^::std != ^^AliasNS); static_assert(^^:: == parent_of(^^::std));
When the
^^ operator
is followed by an id-expression, the resulting std::meta::info
represents the entity named by the expression. Such reflections are
equivalent only if they reflect the same entity.
int x; struct S { static int y; }; static_assert(^^x == ^^x); static_assert(^^x != ^^S::y); static_assert(^^S::y == static_data_members_of(^^S)[0]);
Special rules apply when comparing certain kinds of reflections. A
reflection of an alias compares equal to another reflection if and only
if they are both aliases, alias the same type, and share the same name
and scope. In particular, these rules allow e.g., fn<^^std::string>
to refer to the same instantiation across translation units.
using Alias1 = int; using Alias2 = int; consteval std::meta::info fn() { using Alias1 = int; return ^^Alias; } static_assert(^^Alias1 == ^^Alias1); static_assert(^^Alias1 != ^^int); static_assert(^^Alias1 != ^^Alias2); static_assert(^^Alias1 != fn()); }
A reflection of an object (including variables) does not compare equally to a reflection of its value. Two values of different types never compare equally.
constexpr int i = 42, j = 42; constexpr std::meta::info r = ^^i, s = ^^i; static_assert(r == r && r == s); static_assert(^^i != ^^j); // 'i' and 'j' are different entities. static_assert(constant_of(^^i) == constant_of(^^j)); // Two equivalent values. static_assert(^^i != std::meta::reflect_object(i)) // A variable is distinct from the // object it designates. static_assert(^^i != std::meta::reflect_constant(42)); // A reflection of an object // is not the same as its value.
std::meta
namespaceThe namespace
std::meta is
an associated namespace of std::meta::info,
which allows standard library meta functions to be invoked without
explicit qualification. For example:
#include <meta> struct S {}; std::string name2 = std::meta::identifier_of(^^S); // Okay. std::string name1 = identifier_of(^^S); // Also okay.
Default constructing or value-initializing an object of type std::meta::info
gives it a null reflection value. A null reflection value is equal to
any other null reflection value and is different from any other
reflection that refers to one of the mentioned entities. For
example:
#include <meta> struct S {}; static_assert(std::meta::info() == std::meta::info()); static_assert(std::meta::info() != ^^S);
It’s important that std::meta::info
not be allowed to propagate to runtime. This has no meaning, so it would
be ideal to simply prevent the type from being usable to runtime in any
way whatsoever.
We propose doing this by saying that std::meta::info,
and all types compounded from it (meaning anythig from
info* to
classes with info members,
e.g. tuple<info>
or vector<info>)
are consteval-only types. We then add two kinds of restrictions (both of
which are necessary):
The first we can achieve by requiring such objects to either be part
of a
constexpr
variable, a template parameter object, or have its lifetime entirely
within constant evaluation. The latter we can achieve by plugging into
the already-existing immediate escalating machinery that we have for
immediate functions to also consider consteval-only types as
escalating.
This has an interesting consequence that necessitates an
is_consteval_only type trait that we
discovered. In libc++,
std::sort is
implemented like
this:
template <class _AlgPolicy, class _RandomAccessIterator, class _Comp> inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __sort_impl(_RandomAccessIterator __first, _RandomAccessIterator __last, _Comp& __comp) { std::__debug_randomize_range<_AlgPolicy>(__first, __last); if (__libcpp_is_constant_evaluated()) { std::__partial_sort<_AlgPolicy>( std::__unwrap_iter(__first), std::__unwrap_iter(__last), std::__unwrap_iter(__last), __comp); } else { std::__sort_dispatch<_AlgPolicy>(std::__unwrap_iter(__first), std::__unwrap_iter(__last), __comp); } std::__check_strict_weak_ordering_sorted(std::__unwrap_iter(__first), std::__unwrap_iter(__last), __comp); }
During constant evaluation, we call
__partial_sort (which is
constexpr).
Otherwise, we call __sort_dispatch
(which is not). If we instantiate
__sort_impl with a
_RandomAccessIterator type of std::meta::info*,
then this eventually ends up also instantiating std::__introsort
(here,
also not
constexpr)
which in the body does this:
template <class _AlgPolicy, class _Compare, class _RandomAccessIterator, bool _UseBitSetPartition> void __introsort(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp, typename iterator_traits<_RandomAccessIterator>::difference_type __depth, bool __leftmost = true) { // ... while (true) { difference_type __len = __last - __first; // ... } }
The expression __last - __first,
because these are std::meta::info*s,
must be a constant. Because it’s not here
(__first and
__last are just function
parameters), that triggers the immediate-escalation machinery from [P2564R3]
(consteval needs to propagate up) (before that paper,
it would have been ill-formed on the spot). But because
__introsort is not
constexpr,
propagation fails at that point, and the program is ill-formed.
Even though during we would’ve never actually gotten to this code during runtime.
Let’s go back to the problem function and reduce it and un-uglify the names:
template <class RandomAccessIterator> constexpr void sort(RandomAccessIterator first, RandomAccessIterator last) { if consteval { std::__consteval_only_sort(first, last); } else { std::__runtime_only_sort(first, last); } }
At issue is that
__runtime_only_sort isn’t
constexpr
and instantiating it with std::meta::info*
fails.
We thought of a few ways to approach this issue:
We could just mark
__runtime_only_sort
constexpr —
but marking something
constexpr
that we explicitly do not want to evaluate during constant evaluation
time (as our rename makes obvious), seems like a bad approach. It’s a
confusing annotation at best.
We considered changing the semantics of if consteval
so that it could discard (in the if constexpr
sense) the non-taken branch if actually evaluated during constant
evaluation time. In this case, that would avoid having to instantiate
__runtime_only_sort<meta::info*>
and we’d be okay. But that kind of change seemed very complicated.
We also considered just implicitly making function templates with any
function parameter having consteval-only type be
consteval
anyway. That is,
__runtime_only_sort, despite the
name, actually is
consteval.
That also seemed a bit adventurous, and while it addresses this
particular issue, we weren’t sure what other possible issues might come
up.
Instead, we’re taking a simpler and easier-to-justify approach: adding a new type trait to detect whether a type is consteval-only (a trait which is fairly straightforward to implement on top of the other facilities provided in this proposal). With that trait, the fix is simple (if perhaps surprising to the reader): explicitly discard the runtime branch:
template <class RandomAccessIterator> constexpr void sort(RandomAccessIterator first, RandomAccessIterator last) { if consteval { std::__consteval_only_sort(first, last); } else if constexpr (not is_consteval_only_type(^^RandomAccessIterator)) { std::__runtime_only_sort(first, last); } }
We propose a number of metafunctions declared in namespace
std::meta to
operator on reflection values. Adding metafunctions to an implementation
is expected to be relatively “easy” compared to implementing the core
language features described previously. However, despite offering a
normal consteval C++ function interface, each on of these relies on
“compiler magic” to a significant extent.
In C++23, “constant evaluation” produces pure values without observable side-effects and thus the order in which constant-evaluation occurs is immaterial. In fact, while the language is designed to permit constant evaluation to happen at compile time, an implementation is not strictly required to take advantage of that possibility.
Some of the proposed metafunctions, however, have side-effects that
have an effect on the remainder of the program. For example, we provide
a define_aggregate metafunction that
provides a definition for a given class. Clearly, we want the effect of
calling that metafunction to be “prompt” in a lexical-order sense. For
example:
#include <meta> struct S; void g() { consteval { define_aggregate(^^S, {}); } S s; // S should be defined at this point. }
Hence this proposal also introduces constraints on constant evaluation as follows…
First, consteval blocks (from [P3289R1]) have the property that their evaluation must occur and must succeed in a valid C++ program. We require that a programmer can count on those evaluations occurring exactly once and completing at translation time.
Second, we sequence consteval blocks within the lexical order. Specifically, we require that the evaluation of a non-dependent consteval block occurs before the implementation checks the validity of source constructs lexically following them.
Those constraints are mostly intuitive, but they are a significant change to the underlying principles of the current standard in this respect.
[P2758R4] (Emitting messages at compile time) also has to deal with side effects during constant evaluation. However, those effects (“output”) are of a slightly different nature in the sense that they can be buffered until a manifestly constant-evaluated expression/conversion has completed. “Buffering” a class type completion is not practical (e.g., because other metafunctions may well depend on the completed class type). Still, we are not aware of incompatibilities between our proposal and P2758.
Earlier revisions of this proposal suggested several possible
approaches to handling errors in reflection metafunctions. This question
arises naturally when considering, for instance, examples like template_of(^^int):
the argument is a reflection of a type, but that type is not a
specialization of a template, so there is no valid template that we can
return.
Some of the possibilities that we have considered include:
NaN for floating point) which
carries source location info and some useful message (i.e., the approach
suggested by P1240)std::expected<std::meta::info, E>
for some reflection-specific error type
E, which carries source location
info and some useful messageE,
which requires a language extension for such exceptions to be catchable
during
constexpr
evaluationWe found that we disliked (1) since there is no satisfying value that
can be returned for a call like template_arguments_of(^^int):
We could return a std::vector<std::meta::info>
having a single invalid reflection, but this makes for awkward error
handling. The experience offered by (3) is at least consistent, but
provides no immediate means for a user to “recover” from an error.
Either std::expected or
constexpr exceptions would allow for a consistent and straightforward
interface. Deciding between the two, we noticed that many of usual
concerns about exceptions do not apply during translation:
An interesting example illustrates one reason for our preference for
exceptions over std::expected:
template <typename T> requires (template_of(^^T) == ^^std::optional) void foo();
If template_of returns an
expected<info, E>,
then foo<int>
is a substitution failure — expected<T, E>
is equality-comparable to T, that
comparison would evaluate to
false but
still be a constant expression.
If template_of returns
info but throws an exception, then
foo<int>
would cause that exception to be uncaught, which would make the
comparison not a constant expression. This actually makes the constraint
ill-formed - not a substitution failure. In order to have foo<int>
be a substitution failure, either the constraint would have to first
check that T is a template or we
would have to change the language rule that requires constraints to be
constant expressions (we would of course still keep the requirement that
the constraint is a
bool).
Since the R2 revision of this paper, [P3068R1] has proposed the introduction of constexpr exceptions. The proposal addresses hurdles like compiler modes that disable exception support, and a Clang-based implementation is underway. We believe this to be the most desirable error-handling mechanism for reflection metafunctions.
Because constexpr exceptions have not yet been adopted into the
working draft, we do not specify any functions in this paper that throw
exceptions. Rather, we propose that they fail to be constant expressions
(i.e., case 3 above), and note that this approach will allow us to
forward-compatibly add exceptions at a later time. In the interim
period, implementations should have all of the information needed to
issue helpful diagnostics (e.g., “note:
R does not reflect a template
specialization”) to improve the experience of writing reflection
code.
There are a number of functions, both in the “core” reflection API
that we intend to provide as well as converting some of the standard
library type traits that can accept or return a range of std::meta::info.
For example:
template_arguments_of(^^std::tuple<int>)
is {^^int}substitute(^^std::tuple, {^^int})
is ^^std::tuple<int>This requires us to answer the question: how do we accept a range parameter and how do we provide a range return.
For return, we intend on returning std::vector<std::meta::info>
from all such APIs. This is by far the easiest for users to deal with.
We definitely don’t want to return a std::span<std::meta::info const>,
since this requires keeping all the information in the compiler memory
forever (unlike
std::vector
which could free its allocation). The only other option would be a
custom container type which is optimized for compile-time by being able
to produce elements lazily on demand - i.e. so that nonstatic_data_members_of(^^T)[3]
wouldn’t have to populate all the data members, just do enough
work to be able to return the 4th one. But that adds a lot of complexity
that’s probably not worth the effort.
For parameters, there are basically three options:
std::span<std::meta::info const>,
which now accepts braced-init-list arguments so it’s pretty convenient
in this regard.std::vector<std::meta::info>type_value is std::meta::info.Now, for compiler efficiency reasons, it’s definitely better to have
all the arguments contiguously. So the compiler wants
span (or something like it). There’s
really no reason to prefer vector
over span. Accepting any range would
look something like this:
namespace std::meta { template <typename R> concept reflection_range = ranges::input_range<R> && same_as<ranges::range_value_t<R>, info>; template <reflection_range R = initializer_list<info>> consteval auto substitute(info tmpl, R&& args) -> info; }
This API is more user friendly than accepting span<info const>
by virtue of simply accepting more kinds of ranges. The default template
argument allows for braced-init-lists to still work. Example.
Specifically, if the user is doing anything with range adaptors, they
will either end up with a non-contiguous or non-sized range, which will
no longer be convertible to span -
so they will have to manually convert their range to a vector<info>
in order to pass it to the algorithm. Because the implementation wants
contiguity anyway, that conversion to
vector will happen either way - so
it’s just a matter of whether every call needs to do it manually or the
implementation can just do it once.
For example, converting a struct to a tuple type:
span only
|
any range
|
|---|---|
|
|
This shouldn’t cause much compilation overhead. Checking
convertibility to span
already uses Ranges machinery. And implementations can just do
the right thing interally:
consteval auto __builtin_substitute(info tmpl, info const* arg, size_t num_args) -> info; template <reflection_range R = initializer_list<info>> consteval auto substitute(info tmpl, R&& args) -> info { if constexpr (ranges::sized_range<R> && ranges::contiguous_range<R>) { return __builtin_substitute(tmpl, ranges::data(args), ranges::size(args)); } else { auto as_vector = ranges::to<vector<info>>((R&&)args); return __builtin_substitute(tmpl, as_vector.data(), as_vector.size()); } }
As such, we propose that all the range-accepting algorithms accept any range.
Consider
using A = int;
In C++ today, A and
int can be
used interchangeably and there is no distinction between the two types.
With reflection as proposed in this paper, that will no longer be the
case. ^^A
yields a reflection of an alias to
int, while
^^int
yields a reflection of
int. ^^A == ^^int
evaluates to
false, but
there will be a way to strip aliases - so dealias(^^A) == ^^int
evaluates to
true.
This opens up the question of how various other metafunctions handle aliases and it is worth going over a few examples:
using A = int; using B = std::unique_ptr<int>; template <class T> using C = std::unique_ptr<T>;
This paper is proposing that:
is_type(^^A)
is true.
^^A is an
alias, but it’s an alias to a type, and if this evaluated as
false then
everyone would have to dealias
everything all the time.has_template_arguments(^^B)
is false
while has_template_arguments(^^C<int>)
is true.
Even though B is an alias to a type
that itself has template arguments (unique_ptr<int>),
B itself is simply a type alias and
does not. This reflects the actual usage.template_arguments_of(^^C<int>)
yields {^^int}
while template_arguments_of(^^std::unique_ptr<int>)
yields {^^int, ^^std::default_deleter<int>}.
This is because C has its own
template arguments that can be reflected on.What about when querying the type of an entity?
std::string Str; const std::string &Ref = Str; constexpr std::meta::info StrTy = type_of(^^Str); constexpr std::meta::info RefTy = type_of(^^Ref);
What are StrTy and
RefTy? This question is more
difficult. Two distinct issues complicate the answer:
Our experience using these facilities has consistently shown that
if StrTy represents
std::string,
many uses of StrTy require writing
dealias(StrTy)
rather than using StrTy directly
(because a reflection of a type aliases compares unequal with a
reflection of the aliased type). Failure to do so often yields subtle
bugs.
While we would like for RefTy
to represent const std::string &,
it can only represent const std::basic_string<char, std::allocator<char>> &.
Why? Because since
std::string
is only a “name” for std::basic_string<char, std::allocator<char>>,
the language provides no semantic answer to what “const std::string &”
is. It is only a source-level “grammatical” construct: A
type-id. Reflecting type-ids is a brittle path, since it opens
questions like whether a reflection of const int
is the same as a reflection of int const.
Furthermore, nothing currently requires an implementation to “remember”
that the type of Ref was “spelled”
with the alias
std::string
after parsing it, and we aren’t confident that all major implementations
do so today. Lastly, even if we could form a reflection of
const std::string &,
our existing metafunction and type-trait “machinery” gives no means of
unwrapping the cv-ref qualification to get ^^std::string
without decaying all the way to ^^std::basic_string<char, std::allocator<char>>.
In light of the above, our position is that
type_of should never return aliases:
That is, StrTy represents std::basic_string<char, std::allocator<char>>.
We believe that it would be desirable to in the future introduce an
aliased_type_of function capable of
returning representations of both
std::string
and const std::string &
for Str and
Ref respectively - but this requires
both discussions with implementers, and likely new wording technology
for the Standard. To avoid jeopardizing the goal declared by the title
of this paper, we are not proposing such a function at this time.
One of the most “obvious” abilities of reflection — retrieving the name of an entity — turns out to raise issues that aren’t obvious at all: How do we represent source text in a C++ program?
Thanks to recent work originating in SG16 (the “Unicode” study group)
we can assume that all source code is ultimately representable as
Unicode code points. C++ now also has types to represent UTF-8-encoded
text
(incl. char8_t,
u8string, and
u8string_view) and corresponding
literals like u8"Hi".
Unfortunately, what can be done with those types is still limited at the
time of this writing. For example,
#include <iostream> int main() { std::cout << u8"こんにちは世界\n"; }
is not standard C++ because the standard output stream does not have support for UTF-8 literals.
In practice ordinary strings encoded in the “ordinary literal encoding” (which may or may not be UTF-8) are often used. We therefore need mechanisms to produce the corresponding ordinary string types as well.
Orthogonal to the character representation is the data structure used to traffic in source text. An implementation can easily have at least three potential representations of reflected source text:
the internal representation used, e.g., in the compiler front end’s AST-like structures (persistent)
the representation of string literals in the AST (persistent)
the representation of array of character values during constant-evaluation (transient)
(some compilers might share some of those representations). For
transient text during constant evaluation we’d like to use
string/u8string
values, but because of the limitations on non-transient allocation
during constant evaluation we cannot easily transfer such types to the
non-constant (i.e., run-time) environment. E.g., if
identifier_of were a (consteval)
metafunction returning a
std::string
value, the following simple example would not work:
#include <iostream> #include <meta> int main() { int hello_world = 42; std::cout << identifier_of(^^hello_world) << "\n"; // Doesn't work if identifier_of produces a std::string. }
We can instead return a std::string_view
or std::u8string_view,
but that has the downside that it effectively makes all results of
querying source text persistent for the compilation.
For now, however, we propose that queries like
identifier_of do produce “string
view” results. For example:
consteval std::string_view identifier_of(info); consteval std::u8string_view identifier_of(info);
An alternative strategy that we considered is the introduction of a “proxy type” for source text:
namespace std::meta { struct source_text_info { ... template<typename T> requires (^^T == dealias(^^std::string_view) || ^^T == dealias(^^std::u8string_view) || ^^T == dealias(^^std::string) || ^^T == dealias(^^std::u8string)) consteval T as(); ... }; }
where the as<...>()
member function produces a string-like type as desired. That idea was
dropped, however, because it became unwieldy in actual use cases.
With a source text query like identifier_of(refl)
it is possible that the some source characters of the result are not
representable. We can then consider multiple options, including:
the query fails to evaluate,
any unrepresentable source characters are translated to a
different presentation, such as universal-character-names of the form
\u{ hex-number },
any source characters not in the basic source character set are translated to a different presentation (as in (2)).
Following much discussion with SG16, we propose #1: The query fails to evaluate if the identifier cannot be represented in the ordinary literal encoding.
Earlier revisions of this proposal (and its predecessor, [P1240R2]) included a metafunction
called name_of, which we defined to
return a string_view containing the
“name” of the reflected entity. As the paper evolved, it became
necessary to sharpen the specification of what this “name” contains.
Subsequent revisions (beginning with P2996R2, presented in Tokyo)
specified that name_of returns the
unqualified name, whereas a new
qualified_name_of would give the
fully qualified name.
Most would agree that qualified_name_of(^^size_t)
might reasonably return "std::size_t",
or that qualified_name_of(^^std::any::reset)
could return "std::any::reset".
But what about for local variables, or members of local classes? Should
inline and anonymous namespaces be rendered as a part of the qualified
name? Should we standardize the spelling of such scopes, or leave it
implementation defined?
The situation is possibly even less clear for unqualified names.
Should cv-qualified types be rendered as const int
or int const?
Should the type for a function returning a pointer be rendered as
T *(*)(),
T* (*)(),
or T * (*)()?
Should such decisions be standardized, or left to implementations? But
the real kicker is when one considers non-type template arguments, which
can (and do) contain arbitrarily complex values of arbitrary structural
types (along with any complete object, or subobject thereof, which has
static storage duration).
The more that we tried to specify formatting behavior for just the
unqualified names of arbitrary types, the more convinced we became that
this did not feel like an algorithm that should be frozen in the
standard library - at least, not at this time. There are just too many
toggles that a programmer might reasonably want to flip (one need only
look at Clang’s
PrettyPrinter class for
inspiration). On the other hand, it is perfectly reasonable to ask that
implementations give some means of describing what it is that a
reflection contains - that is exactly the purpose of the
display_string_of function.
Our stance is therefore that reflection pretty printers, for now,
should be left to organically develop within the ecosystem of
open-source C++ libraries. To ensure that this is possible, the
Clang/P2996 fork has implemented its
display_string_of metafunction
entirely within the library. It is capable of printing type names, value
representations, template arguments, and much more. Best of all, it can
be extended without modifying the compiler.
What of name_of and
qualified_name_of? As of the R5
revision of this paper, we have removed them. In their stead is
identifier_of, which is only a
constant expression if the name of the represented construct is an
identifier, and has_identifier for
checking this condition. A few other metafunctions fill in some gaps:
operator_of determines the identity
of an overloaded operator, and predicates like
is_operator_function and
is_conversion_function_template let
printing libraries handle those unqualified names that are not
identifiers. parent_of supports
walking up the chain of functions, namespaces, and classes enclosing the
declaration of an entity, thus enabling homegrown implementations of
qualified_name_of. Meanwhile, the
prime real estate of name_of remains
available for future library extensions.
As a nice side-effect, the
identifier_of model altogether
dodges some contentious questions that arose during LEWG discussions in
St Louis: Should asking the “name” of an anonymous entity (e.g.,
anonymous unions) return the empty string, or fail to be a constant
expression? Since the C++ grammar requires that an
identifier contain at least
one character, the identifier_of
function never returns an empty string: it is seen that the only
possibility is to fail to be a constant expression.
Certain metafunctions (e.g.,
members_of) return reflections that
represent entities without ever naming those entities in source code
(i.e., eliding lookup). Although it is often clear which entities should
be returned from the perspective of a reader, or even the perspective of
an implementation, core wording has no notion that directly corresponds
to “compilation state”.
Lookup is rather defined in terms of “reachability”, which is roughly a mapping from a “program point” to the set of declarations reachable from that point. Lookup frequently occurs from a single point, but template instantiation (and a few other niche circumstances) can lead to lookup taking place from multiple points (i.e., the point in a template from which a name is specified, and the point from which the template was instantiated). The set of points from which lookup takes place is the instantiation context ([module.context]).
template <typename T> int fn() { return /*P1*/ T::value; } struct S { static const int value = 42; } int main() { return /*P2*/ fn<S>(); } // The instantiation context when looking up 'S::value' in 'fn<T>' is {P1, P2}. // Even though 'S' is not found from P1, it is found from P2; lookup succeeds.
This works because the notion of template instantiation is baked into
the definition of “instantiation context”, which is thereafter used to
define lookup. But we have no such benefit in the case of metafunctions
like members_of, which do not
utilize template instantiation.
consteval size_t count_fields(std::meta::info Ty) { return /*P1*/ nonstatic_data_members_of(Ty).size(); } struct S { int i, j, k; } static_assert(/*P2*/ count_fields(^^S) == 3);
If we naively define
nonstatic_data_members_of to return
members reachable from the “point of call”, then the above code would
fail: after all, S is not reachable
from P1. We instead must
define the declarations to be those reachable from where constant
evaluation begins (i.e.,
P2). We encode this idea in
our definition of the evaluation context:
22 During the evaluation of a manifestly constant-evaluated expression
M, the evaluation context of an expressionEcomprises […] the instantiation context ofM([module.context]), […] .
This gives the tool needed to define the declarations returned by
members_of to be (roughly) those
reachable from the evaluation context. However, a second
problem related to reachability is posed by
define_aggregate.
consteval std::meta::info make_defn(std::meta::info Cls, std::meta::info Mem) { // Synthesizes: // struct Mem {}; // struct Cls { Mem m; }; return /*P1*/ define_aggregate(Cls, { data_member_spec(/*P2*/ define_aggregate(Mem, {}), {.name="m"}) }); } /* P3*/ struct C; /* P4*/ struct M; static_assert(/*P5*/ is_type(make_defn(^^C, ^^M)) /*P6*/); /*P7*/ C obj;
Although we want this code to be valid, we have several obstacles to navigate.
C and
M be defined from
P1 and
P2 when no declarations of
those classes are reachable from those program points?C and
M (i.e., from what program points
will the generated definitions be reachable)?M is reachable during the evaluation
of define_aggregate on
C?The prior discourse regarding
members_of gives a straightforward
answer to (1); the define_aggregate
function is defined in terms of the evaluation context, which
makes available all declarations reachable from
P5.
An answer to (2) can be seen by considering the declarations at
P3,
P4, and
P7: Since we want the
declaration of obj to be
well-formed, the generated definition of
C must precede
P7. On the other hand,
placing the definition of C
prior to P4 would weirdly
place the definition of the class C,
which contains a data member of type
M, prior to the declaration of
M itself. We propose that the point
of declaration for all definitions generated by
define_aggregate immediately follows
the end of the manifestly constant-evaluated expression that produces
the definition: In this case, just prior to
P6.
This leaves one gap, and it is the question posed by (3): If the
definition of M, generated by
evaluation of define_aggregate(Mem, {}),
is located just prior to
P6, then the definition is
still not reachable from the evaluation context (such as we have defined
it) during evaluation of define_aggregate(Cls, ...).
Circling back to “reachability” as a mapping from program points to declarations, there are two clear paths forward: Either modify which declarations are reachable from a program point, or modify the set of program points in the evaluation context. We choose the later approach, and attempt to provide some machinery that can be reused for future “generative reflection” proposals.
We begin by specially indicating that the generated definitions of
C and
M are not just declarations, but
injected declarations, and that such injected declarations are
produced by an evaluation of an expression. The reachability of
these declarations is evidently different from other declarations: It
depends not only on a program point, but also on which compile-time
evaluations of expressions (which have no relation to lexical ordering)
are sequenced after the production of the injected
declarations.
To bridge the world of program points to the world of sequenced
evaluations, we introduce a notion dual to “injected declarations”: For
every injected declaration, there is a corresponding synthesized
point. Injected points have a special property: the only
declaration reachable from a synthesized point is its corresponding
injected declaration. Jumping back to our above example, joining the
synthesized point of the injected declaration of
M to our evaluation context gives
exactly what is needed for M to be
usable during the definition of C.
More precisely: M is reachable
during the definition of C because
the evaluation of the expression that produces the definition of
M is sequenced before the
evalauation of the expression that produces
C. This is captured by our full and
final definition of the evaluation context:
22 The evaluation context is a set of points within the program that determines which declarations are found by certain expressions used for reflection. During the evaluation of a manifestly constant-evaluated expression
M, the evaluation context of an expressionEcomprises the union of
Lastly, we clarify that during the definition of an injected
declaration, the instantiation context consists of the
evaluation context of the expression that is producing the
declaration. In our example above, this ensures that the definition of
M is reachable not just
from the invocation of
define_aggregate for
C, but from within the actual
generated definition of
C.
This machinery is “off in the weeds” of technicalities related to modules, lookup, etc., but we believe (hope?) that it provides a sound basis upon which to build generative reflection within the framework provided by core language wording: not only for P2996, but for future papers as well.
The advancement of this proposal through WG21 has naturally led to increased scrutiny of the mechanisms here proposed. One such area is the possibility of leveraging injected declarations to observe failed template substitutions. Consider the following example:
struct S; template <typename> struct TCls { static consteval bool sfn() // #1 requires ([] { consteval { define_aggregate(^^S, {}); } }(), false) { return false; // never selected } static consteval bool sfn() // #2 requires (true) { return true; // always selected } }; static_assert(TCls<void>::sfn()); static_assert(is_complete_type(^^S));
The above example observes the effects of the failed substitution of
#1 by way of
the completeness of S. Such tricks
can be used to observe implementation details, like the order in which
overloads are checked, that may be unportable (and which implementations
might desire to change over time).
Our proposed solution, specified in [expr.const]/23.2, is to make it
ill-formed to produce an injected declaration from a manifestly
constant-evaluated expression inside of an instantiation to
outside of that instantiation, or visa versa. Because that
expression in the example above (define_aggregate(^^S, {}))
is within the instantiation of the requires clause of TCls<void>::sfn,
and the target scope of the injected declaration is outside of that same
instantiaton, the example becomes ill-formed (diagnostic required). Note
that this does not prevent writing
consteval
function templates that wrap
define_aggregate:
template <std::meta::info R> consteval bool tfn() { define_aggregate(R, {}); return true; } struct S; constexpr bool b = tfn<^^S>(); // OK, both manifestly constant-evaluated expression tfn<^^S>() and target scope of // injected declaration for 'S' are in the global namespace
Nor does this rule prevent a class template from producing a declaration whose target scope is the same specialization.
template <typename> struct TCls1 { struct Incomplete; consteval { define_aggregate(^^Incomplete, {}); // OK, Incomplete is in the same instantiation as the define_aggregate call } static constexpr bool b = false; }; template <typename T> struct TCls2 { static consteval bool sfn() // #1 requires (TCls1<T>::b) { return false; // never selected } static consteval bool sfn() // #2 requires (true) { return true; // always selected } }; static_assert(TCls<void>::sfn());
Athough the instantiation of TCls1<void>
in the requires-clause of
#1 causes an
injected declaration to be produced, it is not discernibly a side-effect
of the failed substitution: Observing the side effect will first require
one to write (some moral equivalent of) TCLs1<void>::Incomplete,
the act of which would otherwise itself trigger the same
side-effect.
Although this rule constrains the manner with which
define_aggregate can be used, we are
not aware of any motivating use cases for P2996 that are harmed. Worth
mentioning, however: the rule has more dire implications for other code
injection papers being considered by WG21, most notably [P3294R2] (“Code Injection With
Token Sequences”). With this rule as it is, it becomes impossible
for e.g., the instantiation of a class template specialization TCls<Foo>
to produce an injected declaration of std::formatter<TCls<Foo>>
(since the target scope would be the global namespace).
In this context, we do believe that relaxations of the rule can be
considered: For instance, we ought to be able to say that the
instantiation of std::formatter<TCls<Foo>>
is sequenced strictly after the instantiation of TCls<Foo>,
and observations such as these might make it possible to permit such
injections without making it “discernible” whether they resulted from
failed substitutions. The key to such an approach would be to define a
partial order over the instantiations of a program, and to allow
constructs to be injected across instantiations when the
relative order of their respective instantiations is defined.
All of that said, these relaxations are not needed for the code injection introduced by this proposal, and we do not seek to introduce them at this time.
Several important metafunctions, such as std::meta::nonstatic_data_members_of,
return a
std::vector
value. Unfortunately, that means that they are currently not usable in a
freestanding environment, but [P3295R0] (Freestanding constexpr containers and
constexpr exception types) currently proposes
freestanding
std::vector,
std::string,
and std::allocator in
constant evaluated contexts, explicitly to make the facilities proposed
by this paper work in freestanding.
Here is a synopsis for the proposed library API. The functions will be explained below.
namespace std::meta { using info = decltype(^^::); template <typename R> concept reflection_range = /* see above */; // name and location consteval auto identifier_of(info r) -> string_view; consteval auto u8identifier_of(info r) -> u8string_view; consteval auto display_string_of(info r) -> string_view; consteval auto u8display_string_of(info r) -> u8string_view; consteval auto source_location_of(info r) -> source_location; // type queries consteval auto type_of(info r) -> info; consteval auto parent_of(info r) -> info; consteval auto dealias(info r) -> info; // object and constant queries consteval auto object_of(info r) -> info; consteval auto constant_of(info r) -> info; // template queries consteval auto template_of(info r) -> info; consteval auto template_arguments_of(info r) -> vector<info>; // member queries consteval auto members_of(info r) -> vector<info>; consteval auto bases_of(info type_class) -> vector<info>; consteval auto static_data_members_of(info type_class) -> vector<info>; consteval auto nonstatic_data_members_of(info type_class) -> vector<info>; consteval auto enumerators_of(info type_enum) -> vector<info>; // substitute template <reflection_range R = initializer_list<info>> consteval auto can_substitute(info templ, R&& args) -> bool; template <reflection_range R = initializer_list<info>> consteval auto substitute(info templ, R&& args) -> info; // reflect expression results template <typename T> consteval auto reflect_constant(const T& value) -> info; template <typename T> consteval auto reflect_object(T& value) -> info; template <typename T> consteval auto reflect_function(T& value) -> info; // extracttemplate <typename T> consteval auto extract(info) -> T; // other type predicates (see the wording) consteval auto is_public(info r) -> bool; consteval auto is_protected(info r) -> bool; consteval auto is_private(info r) -> bool; consteval auto is_virtual(info r) -> bool; consteval auto is_pure_virtual(info r) -> bool; consteval auto is_override(info r) -> bool; consteval auto is_final(info r) -> bool; consteval auto is_deleted(info r) -> bool; consteval auto is_defaulted(info r) -> bool; consteval auto is_explicit(info r) -> bool; consteval auto is_noexcept(info r) -> bool; consteval auto is_bit_field(info r) -> bool; consteval auto is_enumerator(info r) -> bool; consteval auto is_const(info r) -> bool; consteval auto is_volatile(info r) -> bool; consteval auto is_mutable_member(info r) -> bool; consteval auto is_lvalue_reference_qualified(info r) -> bool; consteval auto is_rvalue_reference_qualified(info r) -> bool; consteval auto has_static_storage_duration(info r) -> bool; consteval auto has_thread_storage_duration(info r) -> bool; consteval auto has_automatic_storage_duration(info r) -> bool; consteval auto has_internal_linkage(info r) -> bool; consteval auto has_module_linkage(info r) -> bool; consteval auto has_external_linkage(info r) -> bool; consteval auto has_linkage(info r) -> bool; consteval auto is_class_member(info r) -> bool; consteval auto is_namespace_member(info r) -> bool; consteval auto is_nonstatic_data_member(info r) -> bool; consteval auto is_static_member(info r) -> bool; consteval auto is_base(info r) -> bool; consteval auto is_data_member_spec(info r) -> bool; consteval auto is_namespace(info r) -> bool; consteval auto is_function(info r) -> bool; consteval auto is_variable(info r) -> bool; consteval auto is_type(info r) -> bool; consteval auto is_type_alias(info r) -> bool; consteval auto is_namespace_alias(info r) -> bool; consteval auto is_complete_type(info r) -> bool; consteval auto is_enumerable_type(info r) -> bool; consteval auto is_template(info r) -> bool; consteval auto is_function_template(info r) -> bool; consteval auto is_variable_template(info r) -> bool; consteval auto is_class_template(info r) -> bool; consteval auto is_alias_template(info r) -> bool; consteval auto is_conversion_function_template(info r) -> bool; consteval auto is_operator_function_template(info r) -> bool; consteval auto is_literal_operator_template(info r) -> bool; consteval auto is_constructor_template(info r) -> bool; consteval auto is_concept(info r) -> bool; consteval auto is_structured_binding(info r) -> bool; consteval auto is_value(info r) -> bool; consteval auto is_object(info r) -> bool; consteval auto has_template_arguments(info r) -> bool; consteval auto has_default_member_initializer(info r) -> bool; consteval auto is_special_member_function(info r) -> bool; consteval auto is_conversion_function(info r) -> bool; consteval auto is_operator_function(info r) -> bool; consteval auto is_literal_operator(info r) -> bool; consteval auto is_constructor(info r) -> bool; consteval auto is_default_constructor(info r) -> bool; consteval auto is_copy_constructor(info r) -> bool; consteval auto is_move_constructor(info r) -> bool; consteval auto is_assignment(info r) -> bool; consteval auto is_copy_assignment(info r) -> bool; consteval auto is_move_assignment(info r) -> bool; consteval auto is_destructor(info r) -> bool; consteval auto is_user_provided(info r) -> bool; consteval auto is_user_declared(info r) -> bool; // define_aggregate struct data_member_options; consteval auto data_member_spec(info type_class, data_member_options options) -> info; template <reflection_range R = initializer_list<info>> consteval auto define_aggregate(info type_class, R&&) -> info; // data layout struct member_offset { ptrdiff_t bytes; ptrdiff_t bits; constexpr auto total_bits() const -> ptrdiff_t; auto operator<=>(member_offset const&) const = default; }; consteval auto offset_of(info r) -> member_offset; consteval auto size_of(info r) -> size_t; consteval auto alignment_of(info r) -> size_t; consteval auto bit_size_of(info r) -> size_t; }
identifier_of,
display_string_of,
source_location_ofnamespace std::meta { consteval auto identifier_of(info) -> string_view; consteval auto u8identifier_of(info) -> u8string_view; consteval auto display_string_of(info) -> string_view; consteval auto u8display_string_of(info) -> u8string_view; consteval auto has_identifier(info) -> bool; consteval auto source_location_of(info r) -> source_location; }
Given a reflection r representing
a language construct X whose
declaration introduces an identifier, and if that identifier is
representable using the ordinary literal encoding, then identifier_of(r)
returns a non-empty string_view
containing that identifier. Otherwise, it is not a constant expression.
Whether a reflected construct has an identifier can be checked with the
has_identifier metafunction.
The function u8identifier_of
returns the same identifier but as a
u8string_view. Note that since all
identifiers can be represented as UTF-8 string literals,
u8identifier_of never fails to be a
constant expression because of representability concerns.
Given any reflection r, display_string_of(r)
and u8display_string_of(r)
return an unspecified non-empty
string_view and
u8string_view, respectively.
Implementations are encouraged to produce text that is helpful in
identifying the reflected construct (note: as an exercise, the Clang
implementation of this proposal implements a pretty-printing
display_string_of as
a non-intrinsic library function).
Given a reflection r, source_location_of(r)
returns an unspecified
source_location. Implementations are
encouraged to produce the correct source location of the item designated
by the reflection.
type_of,
parent_of,
dealiasnamespace std::meta { consteval auto type_of(info r) -> info; consteval auto parent_of(info r) -> info; consteval auto dealias(info r) -> info; }
If r is a reflection designating
a typed entity, type_of(r)
is a reflection designating its type. If
r is already a type, type_of(r)
is not a constant expression. This can be used to implement the C
typeof
feature (which works on both types and expressions and strips
qualifiers):
consteval auto type_doof(std::meta::info r) -> std::meta::info { return remove_cvref(is_type(r) ? r : type_of(r)); } #define typeof(e) [: type_doof(^^e) :]
parent_of(r)
is a reflection designating its immediately enclosing class, function,
or (possibly inline or anonymous) namespace.
If r represents an alias, dealias(r)
represents the underlying entity. Otherwise, dealias(r)
produces r.
dealias is recursive - it strips all
aliases:
using X = int; using Y = X; static_assert(dealias(^^int) == ^^int); static_assert(dealias(^^X) == ^^int); static_assert(dealias(^^Y) == ^^int);
object_of,
constant_ofnamespace std::meta { consteval auto object_of(info r) -> info; consteval auto constant_of(info r) -> info; }
If r is a reflection of a
variable denoting an object with static storage duration, then object_of(r)
is a reflection of the object designated by the variable. If
r is already a reflection of an
object, object_of(r)
is r. For all other inputs, object_of(r)
is not a constant expression.
int x; int &y = x; static_assert(^^x != ^^y); static_assert(object_of(^^x) == object_of(^^y));
If r is a reflection of an
enumerator, then constant_of(r)
is a reflection of the value of the enumerator. Otherwise, if
r is a reflection of an object
usable in constant expressions, then:
r has scalar type, then constant_of(r)
is a reflection of the value of the object.constant_of(r)
is a reflection of the object.For all other inputs, constant_of(r)
is not a constant expression. For more, see reflect_constant.
template_of,
template_arguments_ofnamespace std::meta { consteval auto template_of(info r) -> info; consteval auto template_arguments_of(info r) -> vector<info>; }
If r is a reflection designating
a specialization of some template, then template_of(r)
is a reflection of that template and template_arguments_of(r)
is a vector of the reflections of the template arguments. In other
words, the preconditions on both is that has_template_arguments(r)
is true.
For example:
std::vector<int> v = {1, 2, 3}; static_assert(template_of(type_of(^^v)) == ^^std::vector); static_assert(template_arguments_of(type_of(^^v))[0] == ^^int);
members_of,
static_data_members_of,
nonstatic_data_members_of,
bases_of,
enumerators_ofnamespace std::meta { consteval auto members_of(info r) -> vector<info>; consteval auto bases_of(info type_class) -> vector<info>; consteval auto static_data_members_of(info type_class) -> vector<info>; consteval auto nonstatic_data_members_of(info type_class) -> vector<info>; consteval auto enumerators_of(info type_enum) -> vector<info>; }
The template members_of returns a
vector of reflections representing the direct members of the class type
or namespace represented by its first argument. Any non-static data
members appear in declaration order within that vector. Anonymous unions
appear as a non-static data member of corresponding union type.
Reflections of structured bindings shall not appear in the returned
vector.
The template bases_of returns the
direct base classes of the class type represented by its first argument,
in declaration order.
static_data_members_of and
nonstatic_data_members_of return
reflections of the static and non-static data members, preserving their
order, respectively.
enumerators_of returns the
enumerator constants of the indicated enumeration type in declaration
order.
substitutenamespace std::meta { template <reflection_range R = initializer_list<info>> consteval auto can_substitute(info templ, R&& args) -> bool; template <reflection_range R = initializer_list<info>> consteval auto substitute(info templ, R&& args) -> info; }
Given a reflection for a template and reflections for template
arguments that match that template,
substitute returns a reflection for
the entity obtained by substituting the given arguments in the template.
If the template is a concept template, the result is a reflection of a
constant of type
bool.
For example:
constexpr auto r = substitute(^^std::vector, std::vector{^^int}); using T = [:r:]; // Ok, T is std::vector<int>
This process might kick off instantiations outside the immediate context, which can lead to the program being ill-formed.
Note that the template is only substituted, not instantiated. For example:
template<typename T> struct S { typename T::X x; }; constexpr auto r = substitute(^^S, std::vector{^^int}); // Okay. typename[:r:] si; // Error: T::X is invalid for T = int.
can_substitute(templ, args)
simply checks if the substitution can succeed (with the same caveat
about instantiations outside of the immediate context). If can_substitute(templ, args)
is false,
then substitute(templ, args)
will be ill-formed.
reflect_constant,
reflect_object,
reflect_functionnamespace std::meta { template<typename T> consteval auto reflect_constant(const T& expr) -> info; template<typename T> consteval auto reflect_object(T& expr) -> info; template<typename T> consteval auto reflect_function(T& expr) -> info; }
These metafunctions produce a reflection of the result from
evaluating the provided expression. One of the most common use-cases for
such reflections is to specify the template arguments with which to
build a specialization using std::meta::substitute.
reflect_constant(expr)
can best be understood from the equivalence that given the template
template <auto P> struct C { };
that:
reflect_constant(V) == template_arguments_of(^^C<V>)[0]
In other words, letting T be the
cv-unqualified, de-aliased type of
expr.
expr has scalar type, then
reflect_constant(expr)
is a reflection of the value of
expr, whose type is
T.expr has class type, then
reflect_constant(expr)
is a reflection of the template parameter object that is
template-argument-equivalent to an object of type
T copy-initialized from
expr.Either way, the result needs to be a permitted result of a constant
expression. Notably, reflect_constant(e)
can be either a reflection of a value or a reflection of an object,
depending on the type of e. This
seeming inconsistence is actually useful for two reasons:
static_assert(substitute(^^std::array, {^^int, std::meta::reflect_constant(5)}) ==
^^std::array<int, 5>);reflect_object(expr)
produces a reflection of the object designated by
expr. This is frequently used to
obtain a reflection of a subobject, which might then be used as a
template argument for a non-type template parameter of reference
type.
template <int &> void fn();
int p[2];
constexpr auto r = substitute(^^fn, {std::meta::reflect_object(p[1])});reflect_function(expr)
produces a reflection of the function designated by
expr. It can be useful for
reflecting on the properties of a function for which only a reference is
available.
consteval bool is_global_with_external_linkage(void(*fn)()) {
std::meta::info rfn = std::meta::reflect_function(*fn);
return (has_external_linkage(rfn) && parent_of(rfn) == ^^::);
}extract<T>namespace std::meta { template<typename T> consteval auto extract(info) -> T; }
If r is a reflection for a value
of type T, extract<T>(r)
is a prvalue whose evaluation computes the reflected value.
If r is a reflection for an
object of non-reference type T,
extract<T&>(r)
and extract<T const&>(r)
are lvalues referring to that object. If the object is usable in
constant expressions [expr.const], extract<T>(r)
evaluates to its value.
If r is a reflection for an
object of reference type T usable in
constant-expressions, extract<T>(r)
evaluates to that reference.
If r is a reflection for a
function of type F, extract<F*>(r)
evaluates to a pointer to that function.
If r is a reflection for a
non-static member function and T is
the type for a pointer to the reflected member function, extract<T>(r)
evaluates to a pointer to the member function.
If r is a reflection for an
enumerator constant of type E, extract<E>(r)
evaluates to the value of that enumerator.
If r is a reflection for a
non-bit-field non-reference non-static member of type
M in a class
C, extract<M C::*>(r)
is the pointer-to-member value for that non-static member.
For other reflection values r,
extrace<T>(r)
is ill-formed.
The function template extract may
feel similar to splicers, but unlike splicers it does not require its
operand to be a constant-expression itself. Also unlike splicers, it
requires knowledge of the type associated with the entity represented by
its operand.
data_member_spec,
define_aggregatenamespace std::meta { struct data_member_options { struct name_type { template <typename T> requires constructible_from<u8string, T> consteval name_type(T &&); template <typename T> requires constructible_from<string, T> consteval name_type(T &&); }; optional<name_type> name; optional<int> alignment; optional<int> bit_width; bool no_unique_address = false; }; consteval auto data_member_spec(info type, data_member_options options) -> info; template <reflection_range R = initializer_list<info>> consteval auto define_aggregate(info type_class, R&&) -> info; }
data_member_spec returns a
reflection of a data member description for a data member of given type.
Optional alignment, bit-field-width, and name can be provided as well.
An inner class name_type, which may
be implicitly constructed from any of several “string-like” types (e.g.,
string_view,
u8string_view, char8_t[],
char_t[]),
is used to represent the name. If a
name is provided, it must be a valid
identifier when interpreted as a sequence of code-units. Otherwise, the
name of the data member is unspecified.
define_aggregate takes the
reflection of an incomplete class/struct/union type and a range of
reflections of data member descriptions and completes the given class
type with data members as described (in the given order). The given
reflection is returned. For now, only data member reflections are
supported (via data_member_spec) but
the API takes in a range of info
anticipating expanding this in the near future.
For example:
union U; consteval { define_aggregate(^^U, { data_member_spec(^^int), data_member_spec(^^char), data_member_spec(^^double), }); } // U is now defined to the equivalent of // union U { // int _0; // char _1; // double _2; // }; template<typename T> struct S; constexpr auto s_int_refl = define_aggregate(^^S<int>, { data_member_spec(^^int, {.name="i", .alignment=64}), data_member_spec(^^int, {.name=u8"こんにち"}), }); // S<int> is now defined to the equivalent of // template<> struct S<int> { // alignas(64) int i; // int こんにち; // };
When defining a
union, if
one of the alternatives has a non-trivial destructor, the defined union
will still have a destructor provided - that simply does
nothing. This allows implementing variant without having to further
extend support in define_aggregate
for member functions.
If define_aggregate is called
multiple times with the same arguments, all calls after the first will
have no effect. Calling
define_aggregate for a type that was
defined using other arguments, defined through other means, or is in the
process of being defined, is not a constant expression.
Revisions of this paper prior to P2996R8 named this function
define_class. We find
define_aggregate to be a better name
for a few reasons:
define_class is left
available for a future, more fully-featured API.namespace std::meta { struct member_offset { ptrdiff_t bytes; ptrdiff_t bits; constexpr auto total_bits() const -> ptrdiff_t { return CHAR_BIT * bytes + bits; } auto operator<=>(member_offset const&) const = default; }; consteval auto offset_of(info r) -> member_offset; consteval auto size_of(info r) -> size_t; consteval auto alignment_of(info r) -> size_t; consteval auto bit_size_of(info r) -> size_t; }
These are generalized versions of some facilities we already have in the language.
offset_of takes a reflection of
a non-static data member or a base class subobject and returns the
offset of it - in bytes and then leftover bits (always between
0 and
7
inclusive).size_of takes the reflection of
a type, object, variable, non-static data member, or base class
subobject and returns its size.alignment_of takes the
reflection of a type, non-static data member, or base class subobject
and returns its alignment.bit_size_of gives the size of a
base class subobject or non-static data member, except in bits.struct Msg { uint64_t a : 10; uint64_t b : 8; uint64_t c : 25; uint64_t d : 21; }; static_assert(offset_of(^^Msg::a) == member_offset{0, 0}); static_assert(offset_of(^^Msg::b) == member_offset{1, 2}); static_assert(offset_of(^^Msg::c) == member_offset{2, 2}); static_assert(offset_of(^^Msg::d) == member_offset{5, 3}); static_assert(bit_size_of(^^Msg::a) == 10); static_assert(bit_size_of(^^Msg::b) == 8); static_assert(bit_size_of(^^Msg::c) == 25); static_assert(bit_size_of(^^Msg::d) == 21); static_assert(offset_of(^^Msg::a).total_bits() == 0); static_assert(offset_of(^^Msg::b).total_bits() == 10); static_assert(offset_of(^^Msg::c).total_bits() == 18); static_assert(offset_of(^^Msg::d).total_bits() == 43);
There is a question of whether all the type traits should be provided
in
std::meta.
For instance, a few examples in this paper use std::meta::remove_cvref(t)
as if that exists. Technically, the functionality isn’t strictly
necessary - since it can be provided indirectly:
Direct
|
Indirect
|
|---|---|
|
|
|
|
The indirect approach is a lot more typing, and you have to remember
to dealias the result of the type
traits as well (because substitute(^^std::remove_cvref_t, {^^int const})
gives you a reflection of an alias to
int, not a
reflection of
int), so
it’s both more tedious and more error prone.
Having std::meta::meow
for every trait
std::meow is
more straightforward and will likely be faster to compile, though means
we will have a much larger library API. There are quite a few traits in
21 [meta] - but it
should be easy enough to specify all of them. So we’re doing it.
Now, one thing that came up is that the straightforward thing we want
to do is to simply add a std::meta::meow
for every trait
std::meow
and word it appropriately. That’s what we initially tried to do.
However, we’ve run into some conflicts.
The standard library type traits are all type traits - they
only accept types. As such, their names are simply things like std::is_pointer,
std::is_const,
std::is_lvalue_reference,
and so forth. Renaming it to std::type_is_pointer,
for instance, would be a waste of characters since there’s nothing else
the argument could be save for a type.
But this is no longer the case. Consider the name
is_function. It could be:
A consteval function equivalent of the type trait std::is_function<T>,
such that std::meta::is_function(e)
mandates that e represents a type
and checks if that type is a function type.
A new kind of reflection query std::meta::is_function(e)
which asks if e is the reflection of
a function (as opposed to a type or a namespace or a template, etc.).
This is the same category of query as std::meta::is_template
or std::meta::is_concept
or std::meta::is_namespace.
Both of these are useful, yet they mean different things entirely -
the first is ill-formed when passed a reflection of a function (as
opposed to a function type), and the second would simply answer
false for
the reflection of any type (function type or otherwise).
Moreover, in this case it’s actually important that the reflection
query std::meta::is_function
does not return
true for a
function type so that using
is_function as a filter for
members_of does the expected thing —
only giving you back functions, rather than also types.
A similar kind of clash could occur with other functions — for
instance, we don’t have an is_array(r)
right now that would check if r were
the reflection of an array (as opposed to an array type), but we could
in the future.
There are a few other examples of name clashes where we want the
reflection query to apply to more inputs than simply types. For example,
the type trait std::is_final can
only ask if a type is a final class type, but the metafunction std::meta::is_final
can ask if a member function is a final member function. Likewise std::meta::is_const
can apply to objects or types too, and so forth.
The question becomes — how can we incorporate the type traits into the consteval metafunction domain while avoiding these name clash issues. We know of a few approaches.
Put all the type traits in their own namespace, like std::meta::traits::meow.
This has the benefit that we preserve the existing name, but now we lose
ADL. We can’t write traits::remove_cvref(type)
unless we bring in traits as a
namespace alias for std::meta::traits,
and if we bring in the entire namespace then we’re back to the name
clash problem (it’s just that now the calls become ambiguous).
Add a prefix or suffix to every type trait. This preserves the
ability to use ADL and makes the new names easy to remember (since std::meow_v<T>
just directly translates into std::meta::type_meow(type)
for all meow), at the cost of worse
names.
Do something more tailored on a case-by-case basis.
We don’t think the nested namespace approach (#1) is a good idea because of the loss of ADL and the more inconvenient call syntax.
Previous revisions of this proposal used the
type_ prefix (#2) uniformly. This
had the downside that some type traits end up reading awkwardly
(type_is_pointer as opposed to
is_pointer_type) but several others
do read much better
(type_has_virtual_destructor as
opposed to
has_virtual_destructor_type). Some
type traits look equally ridiculous with either a prefix or suffix
(type_common_type vs
common_type_type).
A more bespoke approach (#3) would be to do something based on the grammar of the existing type traits:
is_meow can become
is_meow_type. This reads quite
nicely for most of them
(is_pointer_type,
is_trivially_copyable_type,
is_void_type, etc.).
is_swappable_with_type or
is_pointer_convertible_base_of_type
or is_invocable_type maybe aren’t
amazing, but they’re not terrible either. There are 76 of these and
having a uniform transformation is valuable. We could even simply
special case the few that are known to conflict (or, in the case of
is_array, might conflict in the
future, but that’s a little harder to internalize).std::meta::
form as well. There are a few things to point out with these remaining
traits though:
add_const could potentially also
apply to member functions for the purposes of generating code (although
some of these, like
add_lvalue_reference, we’d want to
spell in terms of the qualifier, so those wouldn’t conflict). It’d
probably be okay to start with an
add_const that only applies to types
and eventually extend it, if we go that route though.alignment_of goes away entirely
(since we already have std::meta::alignment_of).
Nobody will notice.has_virtual_destructor,
but we would still only apply to types.Note that either way, we’re also including a few common traits that
aren’t defined in the same places — those are the tuple traits
(tuple_size/tuple_element)
and the variant traits
(variant_size/variant_alternative).
Starting from R8, this paper uses option #3. That is: every type
trait
std::is_meow
is introduced as std::meta::is_meow_type,
while all other type traits
std::meow
are introduced as std::meta::meow.
Static reflection invariably brings new ways to violate ODR.
// File 'cls.h' struct Cls { void odr_violator() { if constexpr (members_of(parent_of(^^std::size_t)).size() % 2 == 0) branch_1(); else branch_2(); } };
Two translation units including
cls.h can
generate different definitions of Cls::odr_violator()
based on whether an odd or even number of declarations have been
imported from std. Branching on the
members of a namespace is dangerous because namespaces may be redeclared
and reopened: the set of contained declarations can differ between
program points.
The creative programmer will find no difficulty coming up with other
predicates which would be similarly dangerous if substituted into the
same if constexpr
condition: for instance, given a branch on is_complete_type(^^T),
if one translation unit
#includes a
forward declaration of T, another
#includes a
complete definition of T, and they
both afterwards #include "cls.h",
the result will be an ODR violation.
Additional papers are already in flight proposing additional
metafunctions that pose similar dangers. For instance, [P3096R2] proposes the
parameters_of metafunction. This
feature is important for generating language bindings (e.g., Python,
JavaScript), but since parameter names can differ between declarations,
it would be dangerous for a member function defined in a header file to
branch on the name of a parameter.
These cases are not difficult to identify: Given an entity
E and two program points
P1 and
P2 from which a reflection of
E may be optained, it is unsafe to
branch runtime code generation on any property of
E (e.g., namespace members,
parameter names, completeness of a class) that can be modified between
P1 and
P2. Worth noting as well, these
sharp edges are not unique (or new) to reflection: It is already
possible to build an ODR trap based on the completeness of a class using
C++23.
Education and training are important to help C++ users avoid such sharp edges, but we do not find them sufficiently concerning to give pause to our enthusiasm for the features proposed by this paper.
[ Editor's note:
Throughout the wording, we say that a reflection (an object of type
std::meta::info)
represents some source construct, while splicing that
reflection designates that source construct. For instance,
^^int represents the type
int and
[: ^^int :] designates the type
int. ]
Add splice-specifier to
the list of template argument forms in definition 3.5.
(3.5) argument
⟨template instantiation⟩
constant-expression,type-id,orid-expression, orsplice-specifierin the comma-separated list bounded by the angle brackets
[ Editor's note: In addition to changes necessary for this proposal, we are applying the “drive-by fix” of merging phases 7/8, in order to clarify that template instantiation is interleaved with translation. In so doing, we replace the notion of “instantiation units” with a partial ordering among all program constructs in a translation unit. ]
Modify the wording for phases 7-8 of 5.2 [lex.phases] as follows:
7-8 Each preprocessing token is converted into a token (5.10 [lex.token]). Whitespace characters separating tokens are no longer significant. The resulting tokens constitute a translation unit and are syntactically and semantically analyzed as a
translation-unit([basic.link]) and translated.[ Note 3: The process of analyzing and translating the tokens can occasionally result in one token being replaced by a sequence of other tokens ([temp.names]) — end note ]
It is implementation-defined whether the sources for module-units and header units on which the current translation unit has an interface dependency (10.1 [module.unit], 10.3 [module.import]) are required to be available.
[ Note 4: Source files, translation units and translated translation units need not necessarily be stored as files, nor need there be any one-to-one correspondence between these entities and any external representation. The description is conceptual only, and does not specify any particular implementation. — end note ]
Translated translation units and instantiation units are combined as follows:
[ Note 5: Some or all of these can be supplied from a library. — end note ]
Each translated translation unit is examined to produce a list of required instantiations.While the tokens constituting translation units are being analyzed and translated, required instantiations are performed.
[ Note 5: This can include instantiations which have been explicitly requested ([temp.explicit]). — end note ]
The contexts from which instantiations may be performed are determined by their respective points of instantiation (13.8.4.1 [temp.point]).
[ Note 6: Other requirements in this document can further constrain the context from which an instantiation can be performed. For example, a constexpr function template specialization might have a point of instantation at the end of a translation unit, but its use in certain constant expressions could require that it be instantiated at an earlier point ([temp.inst]). — end note ]
The definitions of the required templates are located. It is implementation-defined whether the source of the translation units containing these definitions is required to be available.
[ Note 7: An implementation can choose to encode sufficient information into the translated translation unit so as to ensure the source is not required here. — end note ]
All required instantiations are perfomed to produce instantiation units.
[ Note 8: These are similar to translated translation units, but contain no references to uninstantiated templates and no template definitions. — end note ]Each instantiation results in new program constructs. The program is ill-formed if any instantiation fails.
During the analysis and translation of tokens, certain expressions are evaluated ([expr.const]). Constructs appearing at a program point
Pare analyzed in a context where each side effect of evaluating an expressionEas a full-expression is complete if and only if
(7-8.1)
Eis the expression corresponding to aconsteval-block-declaration([dcl.pre]), and(7-8.2) either that
consteval-block-declarationor the template definition from which it is instantiated is reachable from[ Example 1:— end example ]class S { class Incomplete; class Inner { void fn() { /* p1 */ Incomplete i; // OK, constructs at P1 are analyzed in a context where the side effect of // the call to define_aggregate is evaluated because: // * E is the expression corresponding to a consteval block, and // * P1 is in a complete-class context of S and the consteval block // is reachable from P3. } }; /* p2 */ consteval { define_aggregate(^^Incomplete, {}); } }; /* p3 */8
AllTranslated translation units are combined and all external entity references are resolved. Library components are linked to satisfy external references to entities not defined in the current translation. All such translator output is collected into a program image which contains information needed for execution in its execution environment.
Add a bullet after bullet (4.2):
4 If the input stream has been parsed into preprocessing tokens up to a given character:
(4.1) …
(4.2) Otherwise, if the next three characters are
<::and the subsequent character is neither:nor>, the<is treated as a preprocessing token by itself and not as the first character of the alternative token<:.(4.3) Otherwise, if the next three characters are
[::and the subsequent character is not:, or if the next three characters are[:>, the[is treated as a preprocessing token by itself and not as the first character of the preprocessing token[:.[ Note 1: The tokens
[:and:]cannot be composed from digraphs. — end note ](4.4) …
Change the grammar for
operator-or-punctuator in
paragraph 1 of 5.8 [lex.operators] to
include the reflection operator and the
splice-specifier
delimiters:
operator-or-punctuator: one of { } [ ] ( )[: :]<: :> <% %> ; : ... ? :: . .* -> ->* ~ ! + - * / % ^^^& | = += -= *= /= %= ^= &= |= == != < > <= >= <=> && || << >> <<= >>= ++ -- , and or xor not bitand bitor compl and_eq or_eq xor_eq not_eq
Modify paragraph 7 such that denoting a variable by its name finds the variable, not the associated object.
7 A variable is introduced by the declaration of a reference other than a non-static data member or of an object.
The variable’s name, if any, denotes the reference or object.
Add type aliases and namespace aliases to the list of entities in
paragraph 8. As drive-by fixes, remove “value”, “object”, “reference”,
and “template specialization”; replace “class member” with “non-static
data member”, since all other cases are subsumed by existing one. Add
“template parameters” and
“init-captures”, which
collectively subsume “packs”. Introduce a notion of an “underlying
entity” in the same paragraph, and utilize it for the definition of a
name “denoting” an entity. Type aliases are now entities, so also modify
accordingly.
8 An entity is a
value, object, referencevariable, structured binding, result binding, function, enumerator, type, type alias,classnon-static data member, bit-field, template,template specialization,namespace, namespace alias, template parameter, function parameter, orinit-capturepack. The underlying entity of an entity is that entity unless otherwise specified. A name denotes the underlying entity of the entity declared by each declaration that introduces the name.An entityEis denoted by the name (if any) that is introduced by a declaration ofEor by atypedef-nameintroduced by a declaration specifyingE.[ Note 1: Type aliases and namespace aliases have underlying entities that are distinct from themselves. — end note ]
Modify the third sentence of paragraph 1 to clarify that type aliases are now entities.
1 […] A declaration of an entity
ortypedef-nameXis a redeclaration ofXif another declaration ofXis reachable from it (10.7 [module.reach]); otherwise, it is a first declaration. […]
Since namespace aliases are now entities but their declarations are
not definitions, add
namespace-alias-definition
to the list of declarations in paragraph 2, just before
using-declaration. Also
replace
static_assert-declaration
with vacuous-decalaration,
which also encompasses consteval blocks:
2 Each entity declared by a
declarationis also defined by that declaration unless:
- (2.1) it declares a function without specifying the function’s body ([dcl.fct.def]),
[…]
- (2.10) it is an
alias-declaration([dcl.typedef]),- (2.11-) it is a
namespace-alias-definition([namespace.alias]),- (2.11) it is a
using-declaration([namespace.udecl]),- (2.12) it is a
deduction-guide([temp.deduct.guide]),- (2.13) it is a
static_assert-declarationvacuous-declaration([dcl.pre]),- (2.14) it is an
attribute-declaration([dcl.pre]),- (2.15) it is an
empty-declaration([dcl.pre]){.rm},[…]
Also modify the example that follows:
[ Example 1: All but one of the following are definitions:
int a; // defines a
extern const int c = 1; // defines c
int f(int x) { return x+a; } // defines f and defines x
struct S { int a; int b; }; // defines S, S::a, and S::b
struct X { // defines X
int x; // defines non-static data member x
static int y; // declares static data member y
X() : x(0) { } // defines a constructor of X
};
int X::y = 1; // defines X::y
enum { up, down }; // defines up and down
namespace N {int d; } // defines N and N::d
- namespace N1 = N; // defines N1
X anX; // defines anXwhereas these are just declarations:
extern int a; // declares a
extern const int c; // declares c
int f(int); // declares f
struct S; // declares S
typedef int Int; // declares Int
+ namespace N1 = N; // declares N1
extern X anotherX; // declares anotherX
using N::d; // declares dAdd splice-expressions
to the set of potential results of an expression in paragraph 3.
3 An expression or conversion is potentially evaluated unless it is an unevaluated operand ([expr.context]), a subexpression thereof, or a conversion in an initialization or conversion sequence in such a context. The set of potential results of an expression
Eis defined as follows:
- (3.1) If
Eis anid-expression(7.5.5 [expr.prim.id]) or asplice-expression([expr.prim.splice]), the set contains onlyE.- (3.2) […]
[ Note 1: This set is a (possibly-empty) set of
id-expressions andsplice-expressions, each of which is eitherEor a subexpression ofE. — end note ][ Example 1: In the following example, the set of potential results of the initializer of
ncontains the firstS::xsubexpression, but not the secondS::xsubexpression. The set of potential results of the initializer ofocontains the[:^^S::x:]subexpression.— end example ]struct S { static const int x = 0; }; const int &f(const int &r); int n = b ? (1, S::x) // S::x is not odr-used here : f(S::x); // S::x is odr-used here, so a definition is required + int o = [:^^S::x:];
Break bullet 4.1 into sub-bullets and modify it to cover splicing of functions:
(4.1) A function is named by an expression or conversion
Eif it is the selected member of an overload set ([basic.lookup], [over.match], [over.over]) in an overload resolution performed as part of forming that expression or conversion, unless it is a pure virtual function andeither the expression
Modify the first sentence of paragraph 5 to cover splicing of variables:
5 A variable is named by an expression if the expression is an
id-expressionorsplice-expression([expr.prim.splice]) thatdenotesdesignates it.
Modify paragraph 6 to cover splicing of structured bindings:
6 A structured binding is
odr-used if it appears as a potentially-evaluatednamed by an expression if that expression is either anid-expressionor asplice-expressionthat designates that structured binding. A structured binding is odr-used if it is named by a potentially-evaluated expression.
Prepend before paragraph 15 of 6.3 [basic.def.odr]:
15pre If a class
Cis defined in a translation unit as a result of a call to a specialization ofstd::meta::define_aggregateand another translation unit contains a definition ofCthat is not a result of calling the same specialization with the same function arguments, the program is ill-formed; a diagnostic is required only ifCis attached to a named module and a prior definition is reachable at the point where a later definition occurs.15 For any other definable item
Dwith definitions in multiple translation units,
- if
Dis a non-inline non-templated function or variable, or- if the definitions in different translation units do not satisfy the following requirements,
the program is ill-formed; a diagnostic is required only if the definable item is attached to a named module and a prior definition is reachable at the point where a later definition occurs. […]
Prefer the verb “denote” in bullet 15.5 to emphasize that ODR “looks through” aliases, and clarify that objects are not entities in bullet 15.5.2.
- (15.5) In each such definition, corresponding names, looked up according to [basic.lookup], shall
refer todenote the same entity, after overload resolution ([over.match]) and after matching of partial template specialization ([temp.over]), except that a name can refer to
Clarify in bullet 15.11 that default template-arguments in
splice-specialization-specifiers
also factor into ODR:
- (15.11) In each such definition, a default argument used by an (implicit or explicit) function call or a default template argument used by an (implicit or explicit)
template-id,orsimple-template-id, orsplice-specialization-specifieris treated as if its token sequence were present in the definition ofD; that is, the default argument or default template argument is subject to the requirements described in this paragraph (recursively).
And add a bullet thereafter that factors the result of a
reflect-expression into
ODR.
- (15.11+) In each such definition, corresponding
reflect-expressions ([expr.reflect]) compute equivalent values ([expr.eq]).
[ Editor's note: The introduction of a “host scope” in paragraph 2 is part of the resolution to [CWG2701]. ]
Define the “host scope” of a declaration in paragraph 2:
2 Unless otherwise specified:
- (2.1) The smallest scope that contains a scope
Sis the parent scope ofS.[…]
- (2.5) Any names (re)introduced by a declaration are bound to it in its target scope.
The host scope of a declaration is the inhabited scope if that scope is a block scope and the target scope otherwise. An entity belongs to a scope
SifSis the target scope of a declaration of the entity.
Change bullet 4.2 to refer to the declaration of a “type alias”
instead of a
typedef-name.
(4.2) one declares a type (not a
type alias) and the other declares a variable, non-static data member other than an anonymous union ([class.union.anon]), enumerator, function, or function template, ortypedef-name
Adjust paragraph 4 since type aliases are now entities.
4 In certain contexts, only certain kinds of declarations are included. After any such restriction, any declarations of classes or enumerations are discarded if any other declarations are found.
[ Note 4: A type (but not a
type alias or template) is therefore hidden by any other entity in its scope. — end note ]typedef-nameHowever, if lookup is type-only, only declarations of types and templates whose specializations are types are considered; furthermore, if declarations of a
type alias and oftypedef-namethe type to which it refersits underlying entity are found, the declaration of thetype alias is discarded instead of the type declaration.typedef-name
Modify the first bullet of paragraph 3 of 6.5.4 [basic.lookup.argdep] as follows:
3 … Any
typedef-names andusing-declarations used to specify the types do not contribute to this set. The set of entities is determined in the following way:
(3.1-) If
Tisstd::meta::info([meta.reflection.synop]), its associated set of entities is the singleton containing the enumeration typestd::meta::operators([meta.reflection.operators]).[ Note 1: The
std::meta::infotype is a type alias, so an explicit rule is needed to associate calls whose arguments are reflections with the namespacestd::meta. — end note ](3.1) If
Tisaany other fundamental type, its associated set of entities is empty.(3.2) If
Tis a class type …
Extend paragraph 1 to cover
splice-specifiers:
1 Lookup of an identifier followed by a
::scope resolution operator considers only namespaces, types, and templates whose specializations are types. If a name,template-id,splice-scope-specifier, orcomputed-type-specifieris followed by a::, it shall either be a dependentsplice-scope-specifier([temp.dep.splice]) or it shall designate a namespace, class, enumeration, or dependent type, and the::is never interpreted as a completenested-name-specifier.
Add
qualified-reflection-name
([expr.reflect]) to the list of non-terminals in bullet 2.4.5 that are
“qualified names” in the presence of a
nested-name-specifier:
Add a new subsection after 6.5 [basic.lookup], and renumber accordingly:
Splice specifiers [basic.splice]
splice-specifier: [: constant-expression :] splice-specialization-specifier: splice-specifier < template-argument-listopt >1 The
constant-expressionof asplice-specifiershall be a converted constant expression of typestd::meta::info(7.7 [expr.const]). Asplice-specifierwhose convertedconstant-expressionrepresents a constructXis said to designate either
- (1.1) the underlying entity of
XifXis an entity (6.1 [basic.pre]), or- (1.2)
Xotherwise.[ Note 1: A
splice-specifieris dependent if the convertedconstant-expressionis value-dependent ([temp.dep.splice]). — end note ]2 The
splice-specifierof a non-dependentsplice-specialization-specifiershall designate a template.3 [ Note 2: A
<following asplice-specifieris interpreted as the delimiter of atemplate-argument-listwhen thesplice-specifieris preceded bytypenameortemplate, or when it appears in a type-only context (13.3 [temp.names]). — end note ][ Example 1:— end example ]constexpr int v = 1; template <int V> struct TCls { static constexpr int s = V + 1; }; using alias = [:^^TCls:]<[:^^v:]>; // OK, a splice-specialization-specifier with a splice-specifier // as a template argument static_assert(alias::s == 2); auto o1 = [:^^TCls:]<[:^^v:]>(); // error: < means less than auto o2 = typename [:^^TCls:]<[:^^v:]>(); // ok, o2 is an object of type TCls<1> consteval int bad_splice(std::meta::info v) { return [:v:]; // error: v is not constant }
Add a bullet to paragraph 13 and handle
splice-expressions in the
existing bullets:
13 A declaration
Dnames an entityEif
- (13.1)
Dcontains alambda-expressionwhose closure type isE,- (13.1+)
Dcontains a manifestly constant-evaluated expression of typestd::meta::infothat representsE,- (13.2)
Eis not a function or function template andDcontains anid-expression,type-specifier,nested-name-specifier,template-name, orconcept-namedenotingEor asplice-specifierorsplice-expressiondesignatingE, or- (13.3)
Eis a function or function template andDcontains an expression that namesE([basic.def.odr]) or anid-expressionorsplice-expressionthat refers to a set of overloads that containsE.
Modify paragraph 15 to make all type aliases and namespace aliases explicitly TU-local.
An entity is TU-local if it is
[ Editor's note: The below addition of “value or object of a TU-local type” is in part a drive-by fix to make sure that enumerators in a TU-local enumeration are also TU-local ]
Extend the definition of TU-local values and objects in p16 to include reflections:
16 A value or object is TU-local if
- (16.0) it is of TU-local type,
- (16.1) it is, or is a pointer to, a TU-local function or the object associated with a TU-local variable,
or
- (16.2) it is an object of class or array type and any of its subobjects or any of the objects or functions to which its non-static data members of reference type refer is TU-local and is usable in constant expressions.
Change the first sentence in paragraph 9 of 6.8.1 [basic.types.general] as follows:
9 Arithmetic types (6.8.2 [basic.fundamental]), enumeration types, pointer types, pointer-to-member types (6.8.4 [basic.compound]),
std::meta::info,std::nullptr_t, and cv-qualified versions of these types are collectively called scalar types. …
Add a new paragraph at the end of 6.8.1 [basic.types.general] as follows:
12 A type is consteval-only if it is either
std::meta::infoor a type compounded from a consteval-only type ([basic.compound]). Every object of consteval-only type shall be
- (12.1) the object associated with a constexpr variable or a subobject thereof,
- (12.2) a template parameter object (13.2 [temp.param]) or a subobject thereof, or
- (12.3) an object whose lifetime begins and ends during the evaluation of a core constant expression.
Add new paragraphs before the last paragraph of 6.8.2 [basic.fundamental] as follows:
16 The types denoted by
cv std::nullptr_tare distinct types. […]x A value of type
std::meta::infois called a reflection. There exists a unique null reflection; every other reflection is a representation of
- (x.1) a value of scalar type ([temp.param]),
- (x.2) an object with static storage duration ([basic.stc]),
- (x.3) a variable ([basic.pre]),
- (x.4) a structured binding ([dcl.struct.bind]),
- (x.5) a function ([dcl.fct]),
- (x.6) an enumerator ([dcl.enum]),
- (x.7) a type alias ([dcl.typedef]),
- (x.8) a type ([basic.types]),
- (x.9) a class member ([class.mem]),
- (x.10) an unnamed bit-field ([class.bit]),
- (x.11) a primary class template ([temp.pre]),
- (x.12) a function template ([temp.pre]),
- (x.13) a primary variable template ([temp.pre]),
- (x.14) an alias template ([temp.alias]),
- (x.15) a concept ([temp.concept]),
- (x.16) a namespace alias ([namespace.alias]),
- (x.17) a namespace ([basic.namespace.general]),
- (x.18) a direct base class relationship ([class.derived.general]), or
- (x.19) a data member description ([class.mem.general]).
A reflection is said to represent the corresponding construct.
[ Note 1: A reflection of a value can be produced by library functions such as
std::meta::constant_ofandstd::meta::reflect_constant— end note ][ Example 1:— end example ]int arr[] = {1, 2, 3}; auto [a1, a2, a3] = arr; void fn(); enum Enum { A }; using Alias = int; struct B {}; struct S : B { int mem; int : 0; }; template <auto> struct TCls {}; template <auto> void TFn(); template <auto> int TVar; template <auto> concept Concept = requires { true; }; namespace NS {}; namespace NSAlias = NS; constexpr auto ctx = std::meta::access_context::current(); constexpr auto r1 = std::meta::reflect_constant(42); // represents int value of 42 constexpr auto r2 = std::meta::reflect_object(arr[1]); // represents int object constexpr auto r3 = ^^arr; // represents a variable constexpr auto r4 = ^^a3; // represents a structured binding constexpr auto r5 = ^^fn; // represents a function constexpr auto r6 = ^^Enum::A; // represents an enumerator constexpr auto r7 = ^^Alias; // represents a type alias constexpr auto r8 = ^^S; // represents a type constexpr auto r9 = ^^S::mem; // represents a class member constexpr auto r10 = std::meta::members_of(^^S, ctx)[1]; // represents an unnamed bit-field constexpr auto r11 = ^^TCls; // represents a class template constexpr auto r12 = ^^TFn; // represents a function template constexpr auto r13 = ^^TVar; // represents a variable template constexpr auto r14 = ^^Concept; // represents a concept constexpr auto r15 = ^^NSAlias; // represents a namespace alias constexpr auto r16 = ^^NS; // represents a namespace constexpr auto r17 = std::meta::bases_of(^^S, ctx)[0]; // represents a direct base class relationship constexpr auto r18 = std::meta::data_member_spec(^^int, {.name="member"}); // represents a data member descriptiony Recommended practice: Implementations should not represent other constructs specified in this document, such as partial template specializations, attributes, placeholder types, statements, or expressions, as values of type
std::meta::info.z [ Note 2: Future revisions of this document can specify semantics for reflections representing any such constructs. — end note ]
17 The types described in this subclause are called fundamental types.
Introduce a new kind of side effect in paragraph 7 (i.e., injecting a declaration).
7 Reading an object designated by a
volatileglvalue ([basic.lval]), modifying an object, producing an injected declaration ([expr.const]), calling a library I/O function, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution or translation environment. Evaluation of an expression (or a subexpression) in general includes both value computations (including determining the identity of an object for glvalue evaluation and fetching a value previously assigned to an object for prvalue evaluation) and initiation of side effects. When a call to a library I/O function returns or an access through a volatile glvalue is evaluated, the side effect is considered complete, even though some external actions implied by the call (such as the I/O itself) or by thevolatileaccess may not have completed yet.
Add a new paragraph to the end of [intro.execution] specifying a stronger sequencing during constant evaluation.
15+ During the evaluation of an expression as a core constant expression ([expr.const]), evaluations of operands of individual operators and of subexpressions of individual expresssions that are otherwise either unsequenced or indeterminately sequenced are evaluated in lexical order.
Apply a drive-by fix to bullet 1.1 clarifying that a glvalue can also determine the identity of a non-static data member.
- (1.1) A glvalue is an expression whose evaluation determines the identity of an object,
orfunction, or non-static data member.
Account for move-eligible
splice-expressions in
bullet 4.1 of Note 3.
- (4.1) a move-eligible
id-expression([expr.prim.id.unqual]) orsplice-expression([expr.prim.splice]),
Add reflect-expressions
to the list of unevaluated operands in paragraph 1.
1 In some contexts, unevaluated operands appear ([expr.prim.req], [expr.typeid], [expr.sizeof], [expr.unary.noexcept], [expr.reflect], [dcl.type.decltype], [temp.pre], [temp.concept]). An unevaluated operand is not evaluated.
Add splice-expression to
the list of expressions in paragraph 2.
2 In some contexts, an expression only appears for its side effects. Such an expression is called a discarded-value expression. The array-to-pointer and function-to-pointer standard conversions are not applied. The lvalue-to-rvalue conversion is applied if and only if the expression is a glvalue of volatile-qualified type and it is one of the following:
Add splice-expression to
the grammar for
primary-expression:
primary-expression: literal this ( expression ) id-expression lambda-expression fold-expression requires-expression + splice-expression
Modify paragraph 2 to avoid transforming non-static members into
implicit member accesses when named as operands to
reflect-expressions.
2 If an
id-expressionEdenotes a non-static non-type member of some classCat a point where the current class (7.5.3 [expr.prim.this]) isXand
- (2.1)
Eis potentially evaluated orCisXor a base class ofX, and- (2.2)
Eis not theid-expressionof a class member access expression (7.6.1.5 [expr.ref]), and- (2.2+)
Eis not theid-expressionof areflect-expression([expr.reflect]), and- (2.3) if
Eis aqualified-id,Eis not the un-parenthesized operand of the unary&operator (7.6.2.2 [expr.unary.op]),the
id-expressionis transformed into a class member access expression using(*this)as the object expression.
And extend paragraph 4 to account for splices:
4 An
id-expressionorsplice-expressionthat denotes a non-static data member or implicit object member function of a class can only be used:
- (4.1) as part of a class member access (after any implicit transformation (see above)) in which the object expression refers to the member’s class or a class derived from that class, or
- (4.2) to form a pointer to member ([expr.unary.op]), or
- (4.3) if that
id-expressionorsplice-expressiondesignatesdenotesa non-static data member and it appears in an unevaluated operand.[ Example 1:— end example ]struct S { int m; }; int i = sizeof(S::m); // OK int j = sizeof(S::m + 42); // OK + int S::*k = &[:^^S::m:]; // OK
Modify paragraph 15 to allow
splice-expressions to be
move-eligible:
15 An implicitly movable entity is a variable with automatic storage duration that is either a non-volatile object or an rvalue reference to a non-volatile object type. An
id-expressionorsplice-expression([expr.prim.splice]) is move-eligible if
- (15.1) it
namesdesignates an implicitly movable entity,- (15.2) it is the (possibly parenthesized) operand of a
return([stmt.return]) orco_return([stmt.return.coroutine]) statement or of athrow-expression([expr.throw]), and- (15.3) each intervening scope between the declaration of the entity and the innermost enclosing scope of the
expression is a block scope and, for aid-expressionthrow-expression, is not the block scope of atry-blockorfunction-try-block.
Extend the grammar for
nested-name-specifier as
follows:
nested-name-specifier: :: type-name :: namespace-name :: computed-type-specifier :: + splice-scope-specifier :: nested-name-specifier identifier :: nested-name-specifier templateopt simple-template-id :: + + splice-scope-specifier: + splice-specifier + templateopt splice-specialization-specifier
Add a paragraph after paragraph 1 specifying the rules for parsing a
splice-scope-specifier, as
well as an example:
1+ A
splice-specifierorsplice-specialization-specifierthat is not followed by::is never interpreted as part of asplice-scope-specifier. Thetemplatemay only be omitted from the formtemplateopt splice-specialization-specifier ::when thesplice-specialization-specifieris preceded bytypename.[ Example 1:— end example ]template <int V> struct TCls { static constexpr int s = V; using type = int; }; constexpr int v1 = [:^^TCls<1>:]::s; constexpr int v2 = template [:^^TCls:]<2>::s; // OK, template binds to splice-scope-specifier constexpr typename [:^^TCls:]<3>::type v3 = 3; // OK, typename binds to the qualified name template [:^^TCls:]<3>::type v4 = 4; // OK, template binds to the splice-scope-specifier void fn() { constexpr [:^^TCls:]<3>::type v5 = 5; // error: < means less than }
Clarify in paragraph 2 that a splice cannot appear in a declarative
nested-name-specifier:
2 A
nested-name-specifieris declarative if it is part of
- a
class-head-name,- an
enum-head-name,- a
qualified-idthat is theid-expressionof adeclarator-id, or- a declarative
nested-name-specifier.A declarative
nested-name-specifiershall not have acomputed-type-specifieror asplice-scope-specifier. A declaration that uses a declarativenested-name-specifiershall be a friend declaration or inhabit a scope that contains the entity being redeclared or specialized.
Break the next paragraph into a bulleted list, extend it to also cover splices, and prefer the verb “designate” over “nominate”:
[ Drafting note: Here
and in a few other places, the wording for the entity represented by a
splice-specialization-specifier
is complicated. This is primarily because the possibility that such a
construct might form a
concept-id (which is a
prvalue) precludes us from generically saying that a
splice-specialization-specifier
designates an entity. ]
3 The entity designated by a
nested-name-specifieris determined as follows:
- (3.1) The
nested-name-specifier::nominatesdesignates the global namespace.- (3.2) A
nested-name-specifierwith acomputed-type-specifiernominatesdesignates the same typedenoteddesignated by thecomputed-type-specifier, which shall be a class or enumeration type.- (3.3) For a
nested-name-specifierof the formsplice-specifier ::, thesplice-specifiershall designate a class or enumeration type or a namespace. Thenested-name-specifierdesignates the same entity as thesplice-specifier.- (3.4) For a
nested-name-specifierof the formtemplateopt splice-specialization-specifier ::, thesplice-specifierof thesplice-specialization-specifiershall designate a primary class template or an alias templateT. LettingSbe the specialization ofTcorresponding to thetemplate-argument-list(if any) of thesplice-specialization-specifier,Sshall either be a class template specialization or an alias template specialization that denotes a class or enumeration type. Thenested-name-specifierdesignatesSifTis a class template or the type denoted bySifTis an alias template.- (3.5) If a
nested-name-specifierN is declarative and has asimple-template-idwith a template argument list A that involves a template parameter, let T be the templatenominateddesignated by N without A. T shall be a class template.
- (3.5.1) If
Ais the template argument list (13.4 [temp.arg]) of the correspondingtemplate-headH(13.7.3 [temp.mem]),Nnominatesdesignates the primary template ofT;Hshall be equivalent to thetemplate-headofT(13.7.7.2 [temp.over.link]).- (3.5.2) Otherwise,
Nnominatesdesignates the partial specialization (13.7.6 [temp.spec.partial]) ofTwhose template argument list is equivalent toA(13.7.7.2 [temp.over.link]); the program is ill-formed if no such partial specialization exists.- (3.6) Any other
nested-name-specifiernominatesdesignates the entitydenoteddesignated by itstype-name,namespace-name,identifier, orsimple-template-id. If thenested-name-specifieris not declarative, the entity shall not be a template.
We have to say that a closure type is not complete until the
}:
1 The type of a lambda-expression (which is also the type of the closure object) is a unique, unnamed non-union class type, called the closure type, whose properties are described below.
x The closure type is not complete until the end of its corresponding
compound-statement.
And say that the call operator is a direct member:
4 The closure type for a lambda-expression has a public inline function call operator (for a non-generic lambda) or function call operator template (for a generic lambda) ([over.call]) whose parameters and return type are those of the lambda-expression’s parameter-declaration-clause and trailing-return-type respectively, and whose template-parameter-list consists of the specified template-parameter-list, if any. The function call operator or the function call operator template are direct members of the closure type. The requires-clause of the function call operator template […]
And that the conversion function is:
10 The closure type for a non-generic lambda-expression with no lambda-capture and no explicit object parameter ([dcl.fct]) whose constraints (if any) are satisfied has a conversion function to pointer to function with C++ language linkage having the same parameter and return types as the closure type’s function call operator. The conversion function is a direct member of the closure type. The conversion is to “pointer to noexcept function” if the function call operator has a non-throwing exception specification.
11 For a generic lambda with no lambda-capture and no explicit object parameter ([dcl.fct]), the closure type has a conversion function template to pointer to function. The conversion function template is a direct member of the closure type. The conversion function template has the same invented template parameter list, […]
Allow splices in type requirements:
type-requirement: typename nested-name-specifieropt type-name ; + typename splice-specifier + typename splice-specialization-specifier1 A
type-requirementasserts the validity of a type. The component names (if any) of atype-requirementare those of itsnested-name-specifier(if any)andtype-name.[ Example 1:— end example ]template<typename T, typename T::type = 0> struct S; template<typename T> using Ref = T&; template<typename T> concept C = requires { typename T::inner; // required nested member name typename S<T>; // required valid ([temp.names]) template-id; // fails if T::type does not exist as a type to which 0 can be implicitly converted typename Ref<T>; // required alias template substitution, fails if T is void + typename [:T::r1:]; // fails if T::r1 is not a reflection of a type + typename [:T::r2:]<int>; // fails if T::r2 is not a reflection of a template T for + // which T<int> is a type };
Add a new subsection of 7.5 [expr.prim] following 7.5.8 [expr.prim.req]
Expression Splicing [expr.prim.splice]
splice-expression: splice-specifier template splice-specifier template splice-specialization-specifier1 A
splice-specifierorsplice-specialization-specifierimmediately followed by::or preceded bytypenameis never interpreted as part of asplice-expression.[ Example 1:— end example ]struct S { static constexpr int a = 1; }; template <typename> struct TCls { static constexpr int b = 2; }; constexpr int c = [:^^S:]::a; // [:^^S:] is not an expression constexpr int d = template [:^^TCls:]<int>::b; // template [:^^TCls:]<int> is not // an expression template <auto V> constexpr int e = [:V:]; // splice-expression constexpr int f = template [:^^e:]<^^S::a>; // splice-expression auto g = typename [:^^int:](42); // [:^^int:] forms part of a type, not a splice-expression2 For a
splice-expressionof the formsplice-specifier, letSbe the construct designated bysplice-specifier.
(2.1) The expression is ill-formed if
Sis(2.2) Otherwise, if
Sis a functionF, the expression denotes an overload set containing all declarations ofFthat precede either the expression or the point immediately following theclass-specifierof the outermost class for which the expression is in a complete-class context; overload resolution is performed to select a unique function ([over.match], [over.over]).(2.3) Otherwise, if
Sis an object or a non-static data member, the expression is an lvalue designatingS. The expression has the same type asS, and is a bit-field if and only ifSis a bit-field. [ Note 1: The implicit transformation (7.5.5 [expr.prim.id]) whereby anid-expressiondenoting a non-static member becomes a class member access does not apply to asplice-expression. — end note ](2.4) Otherwise, if
Sis a variable or a structured binding,Sshall either have static or thread storage duration or shall inhabit a scope enclosing the expression. The expression is an lvalue referring to the object or functionXassociated with or referenced byS, has the same type asS, and is a bit-field if and only ifXis a bit-field.[ Note 2: The type of a
splice-expressiondesignating a variable or structured binding of reference type will be adjusted to a non-reference type (7.2.2 [expr.type]). — end note ](2.5) Otherwise, if
Sis a value or an enumerator, the expression is a prvalue that computesSand whose type is the same asS.(2.6) Otherwise, the expression is ill-formed.
3 For a
splice-expressionof the formtemplate splice-specifier, thesplice-specifiershall designate a function templateTthat is not a constructor template. The expression denotes an overload set containing all declarations ofTthat precede either the expression or the point immediately following theclass-specifierof the outermost class for which the expression is in a complete-class context; overload resolution is performed to select a unique function. [ Note 3: During overload resolution, candidate function templates undergo template argument deduction and the resulting specializations are considered as candidate functions. — end note ]4 For a
splice-expressionof the formtemplate splice-specialization-specifier, thesplice-specifierof thesplice-specialization-specifiershall designate a templateT.
(4.1) If
Tis a function template, the expression denotes an overload set containing all declarations ofTthat precede either the expression or the point immediately following theclass-specifierof the outermost class for which the expression is in a complete-class context; overload resolution is performed to select a unique function ([over.match], [over.over]).(4.2) Otherwise, if
Tis a primary variable template, letSbe the specialiation ofTcorresponding to thetemplate-argument-list(if any) of thesplice-specialization-specifier. The expression is an lvalue referring to the same object associated withSand has the same type asS.(4.3) Otherwise, the expression is ill-formed.
[ Note 4: Class members designated by
splice-expressions are accessible from any point ([class.access.base]). A class member access expression whose right operand is asplice-expressionis ill-formed if the left operand (considered as a pointer) cannot be implicitly converted to a pointer to the designating class of the right operand. — end note ]
Add a production to
postfix-expression for
splices in member access expressions:
[1]{.pnum} Postfix expressions group left-to-right. postfix-expression: ... postfix-expression . templateopt id-expression + postfix-expression . splice-expression postfix-expression -> templateopt id-expression + postfix-expression -> splice-expression
Modify paragraph 1 to account for splices in member access expressions:
1 A postfix expression followed by a dot
.or an arrow->, optionally followed by the keywordtemplate, and then followed by anid-expressionor asplice-expression, is a postfix expression.[ Note 1: If the keyword
templateis used, the following unqualified name is considered to refer to a template ([temp.names]). If asimple-template-idresults and is followed by a::, theid-expressionis aqualified-id. — end note ]
Modify paragraph 2 to account for splices in member access expressions:
2 For
the first option (dot), if thea dot that is followed by anid-expressionnamesorsplice-expressionthat designates a static member or an enumerator, the first expression is a discarded-value expression ([expr.context]); if theid-expressionorsplice-expressiondesignatesnamesa non-static data member, the first expression shall be a glvalue.For the second option (arrow), the first expressionA postfix expression that is followed by an arrow shall be a prvalue having pointer type. The expressionE1->E2is converted to the equivalent form(*(E1)).E2; the remainder of [expr.ref] will address onlythe first option (dot)the form using a dot49.
Modify paragraph 3 to account for splices in member access expressions:
3 The postfix expression before the dot is evaluated;50 the result of that evaluation, together with the
id-expressionorsplice-expression, determines the result of the entire postfix expression.
Modify paragraph 4 to account for splices in member access expressions:
4 Abbreviating
postfix-expression.id-expressionorpostfix-expression.splice-expressionasE1.E2,E1is called theobject expression. […]
Adjust the language in paragraphs 6-9 to account for
splice-expressions.
Explicitly add a fallback to paragraph 7 that makes other cases
ill-formed. Update the term “naming class” to “designated class” in
paragraph 8.
* If
E2is asplice-expression, thenE2shall designate a member of the type ofE1.6 If
E2isdesignates a bit-field,E1.E2is a bit-field. […]7 If
E2designates an entity that is declared to have type “reference toT”, thenE1.E2is an lvalue of typeT.IfIn that case, ifE2isdesignates a static data member,E1.E2designates the object or function to which the reference is bound, otherwiseE1.E2designates the object or function to which the corresponding reference member ofE1is bound. Otherwise, one of the following rules applies.
(7.1) If
E2isdesignates a static data member and the type ofE2isT, thenE1.E2is an lvalue; […](7.2) Otherwise, if
IfE2isdesignates a non-static data member and the type ofE1is “cq1 vq1X”, and the type ofE2is “cq2 vq2T”, […]. If the entity designated byE2is declared to be amutablemember, then the type ofE1.E2is “vq12T”. If the entity designated byE2is not declared to be amutablemember, then the type ofE1.E2is “cq12 vq12T”.(7.3) Otherwise, if
IfE2is an overload set, […](7.4) Otherwise, if
IfE2isdesignates a nested type, the expressionE1.E2is ill-formed.(7.5) Otherwise, if
IfE2isdesignates a member enumerator and the type ofE2isT, the expressionE1.E2is a prvalue of typeTwhose value is the value of the enumerator.(7.6) Otherwise, the program is ill-formed.
8 If
E2isdesignates a non-static member, the program is ill-formed if the class of whichE2is directlydesignates a direct member is an ambiguous base (6.5.2 [class.member.lookup]) of thenamingdesignating class (11.8.3 [class.access.base]) ofE2.9 If the entity designated by
E2is a non-static member and the result ofE1is an object whose type is not similar ([conv.qual]) to the type ofE1, the behavior is undefined.
Add reflect-expression
to the grammar for
unary-expression in
paragraph 1:
1 Expressions with unary operators group right-to-left.
unary-expression: ... delete-expression + reflect-expression
Modify paragraphs 3 and 4 to permit forming a pointer-to-member with a splice.
3 The operand of the unary
&operator shall be an lvalue of some typeT.
(3.1) If the operand is a
qualified-idorsplice-expressionnamingdesignating a non-static or variant member of some classC, other than an explicit object member function, the result has type “pointer to member of classCof typeT” and designatesC::m.(3.2) Otherwise, the result has type “pointer to
T” and points to the designated object (6.7.1 [intro.memory]) or function (6.8.4 [basic.compound]). If the operand designates an explicit object member function (9.3.4.6 [dcl.fct]), the operand shall be aqualified-idor asplice-expression.4 A pointer to member is only formed when an explicit
&is used and its operand is aqualified-idorsplice-expressionnot enclosed in parentheses.
Add a new subsection of 7.6.2 [expr.unary] following 7.6.2.9 [expr.delete]
The reflection operator [expr.reflect]
reflect-expression: ^^ :: ^^ qualified-reflection-name ^^ type-id ^^ id-expression qualified-reflection-name: nested-name-specifieropt identifier nested-name-specifier template identifier1 The unary
^^operator, called the reflection operator, yields a prvalue of typestd::meta::info(6.8.2 [basic.fundamental]).[ Note 1: This document places no restriction on representing, by reflections, constructs not described by this document or using such constructs as operands of
reflect-expressions. — end note ]2 The component names of a
qualified-reflection-nameare those of itsnested-name-specifier(if any) and itsidentifier.3 A
reflect-expressionis parsed as the longest possible sequence of tokens that could syntactically form areflect-expression. Areflect-expressionwhose terminal name is aconcept-nameor atemplate-nameshall not be followed by<.[ Example 1:— end example ]static_assert(std::meta::is_type(^^int())); // ^^ applies to the type-id "int()" template<bool> struct X {}; consteval bool operator<(std::meta::info, X<false>) { return false; } consteval void g(std::meta::info r, X<false> xv) { r == ^^int && true; // error: ^^ applies to the type-id "int&&" r == ^^int & true; // error: ^^ applies to the type-id "int&" r == (^^int) && true; // OK r == ^^int &&&& true; // error: 'int &&&&' is not a valid type ^^X < xv; // error: reflect-expression whose terminal name is a // template-name is followed by < (^^X) < xv; // OK }4 A
reflect-expressionof the form^^ ::represents the global namespace.5 If a
reflect-expressionRmatches the form^^ qualified-reflection-name, it is interpreted as such and its representation is determined as follows:
- (5.1) If the
identifieris anamespace-namethat names a namespace alias ([namespace.alias]),Rrepresents that namespace alias. For any othernamespace-name,Rrepresents the denoted namespace.- (5.2) Otherwise, if the
identifieris aconcept-name([temp.concept]),Rrepresents the denoted concept.- (5.3) Otherwise, if the
identifieris atemplate-name([temp.names]), the representation ofRis determined as follows:
- (5.3.1) If the
template-namenames an injected-class-name ([class.pre]), then:
- (5.3.1.1) If the
qualified-reflection-nameis of the formnested-name-specifier template identifier, thenRrepresents the class template named by the injected-class-name.- (5.3.1.2) Otherwise, the injected-class-name shall be unambiguous when considered as a
type-nameandRrepresents the class template specialization so named.- (5.3.2) Otherwise, if the
template-namenames a function templateF, then thetemplate-nameinterpreted as anid-expressionshall denote an overload set containing onlyF.RrepresentsF.- (5.3.3) Otherwise, if the
template-namedenotes a primary class template, primary variable template, or alias template,Rrepresents that template.- (5.4) Otherwise, if the
identifiernames a type alias that was introduced by the declaration of a template parameter,Rrepresents the underlying entity of that type alias. For any otheridentifierthat names a type alias,Rrepresents that type alias.- (5.5) Otherwise, if the
identifieris aclass-nameor anenum-name,Rrepresents the denoted type.- (5.6) Otherwise, the
qualified-reflection-nameshall be anid-expressionIandRis^^ I(see below).6 A
reflect-expressionRof the form^^ type-idrepresents an entity determined as follows:
- (6.1) If the
type-iddesignates a placeholder type,Ris ill-formed.- (6.2) Otherwise, if the
type-idnames a type alias that is a specialization of an alias template,Rrepresents that type alias.- (6.3) Otherwise,
Rrepresents the type denoted by thetype-id.[ Editor's note: All other cases for
^^ type-idare covered byqualified-reflection-name. ]7 A
reflect-expressionRof the form^^ id-expressionrepresents an entity determined as follows:
(7.1) If the
id-expressiondenotes an overload setS, overload resolution for the expression&Swith no target shall select a unique function ([over.over]);Rrepresents that function.(7.2) Otherwise, if the
id-expressiondenotes a variable declared by aninit-capture([expr.prim.lambda.capture]),Ris ill-formed.(7.3) Otherwise, if the
id-expressiondenotes a local parameter introduced by arequires-expression([expr.prim.req]),Ris ill-formed.(7.4) Otherwise, if the
id-expressiondenotes a local entityE([basic.pre]) for which there is a lambda scope that intervenes betweenRand the point at whichEwas introduced,Ris ill-formed.(7.5) Otherwise, if the
id-expressiondenotes a function-local predefined variable ([dcl.fct.def.general]),Ris ill-formed. For any otherid-expressionthat denotes a variable,Rrepresents that variable.(7.6) Otherwise, if the
id-expressiondenotes a structured binding, enumerator, or non-static data member,Rrepresents that entity.(7.7) Otherwise,
Ris ill-formed. [ Note 2: This includespack-index-expressions and constant template parameters. — end note ]The
id-expressionof areflect-expressionis an unevaluated operand ([expr.context]).[ Example 2:— end example ]template <typename T> void fn() requires (^^T != ^^int); template <typename T> void fn() requires (^^T == ^^int); template <typename T> void fn() requires (sizeof(T) == sizeof(int)); constexpr auto a = ^^fn<char>; // OK constexpr auto b = ^^fn<int>; // error: ambiguous constexpr auto c = ^^std::vector; // OK template <typename T> struct S { static constexpr auto r = ^^T; using type = T; }; static_assert(S<int>::r == ^^int); static_assert(^^S<int>::type != ^^int); typedef struct X {} Y; typedef struct Z {} Z; constexpr auto e = ^^Y; // OK, represents the type alias Y constexpr auto f = ^^Z; // OK, represents the type Z (not the type alias)
Add a new paragraph between paragraphs 5 and 6:
5 Two operands of type
std::nullptr_tor one operand of typestd::nullptr_tand the other a null pointer constant compare equal.5+ If both operands are of type
std::meta::info, comparison is defined as follows:
- (5+.1) If one operand is a null reflection value, then they compare equal if and only if the other operand is also a null reflection value.
- (5+.2) Otherwise, if one operand represents a value, then they compare equal if and only if the other operand represents a value that is template-argument-equivalent (13.6 [temp.type]).
- (5+.3) Otherwise, if one operand represents an object, then they compare equal if and only if the other operand represents the same object.
- (5+.4) Otherwise, if one operand represents an entity, then they compare equal if and only if the other operand represents the same entity.
- (5+.5) Otherwise, if one operand represents a direct base class relationship, then they compare equal if and only if the other operand represents the same direct base class relationship.
- (5+.6) Otherwise, both operands
O1andO2represent data member descriptions. The operands compare equal if and only if the data member descriptions represented byO1andO2compare equal (11.4.1 [class.mem.general]).6 If two operands compare equal, the result is
truefor the==operator andfalsefor the!=operator. If two operands compare unequal, the result isfalsefor the==operator andtruefor the!=operator. Otherwise, the result of each of the operators is unspecified.
Add a bullet to paragraph 10 between 10.27 and 10.28 to disallow the production of injected declarations from any core constant expression that isn’t a consteval block.
10 An expression
Eis a core constant expression unless the evaluation ofE, following the rules of the abstract machine ([intro.execution]), would evaluate one of the following:[…]
- (10.27) a
dynamic_cast([expr.dynamic.cast]) expression,typeid([expr.typeid]) expression, ornew-expression([expr.new]) that would throw an exception where no definition of the exception type is reachable;- (10.27+) an expression that would produce an injected declaration (see below), unless
Eis the corresponding expression of aconsteval-block-declaration([dcl.pre]);- (10.28) an
asm-declaration([dcl.asm]);- (10.29) […]
Modify paragraph 17 to mention
splice-expressions:
17 During the evaluation of an expression
Eas a core constant expression, allid-expressions,splice-expressions, and uses of*thisthat refer to an object or reference whose lifetime did not begin with the evaluation ofEare treated as referring to a specific instance of that object or reference whose lifetime and that of all subobjects (including all union members) includes the entire constant evaluation. […]
Modify paragraph 22 to disallow returning non-consteval-only pointers and references to consteval-only objects from constant expressions.
22 A constant expression is either a glvalue core constant expression
Ethat
(22.1) refers to an object or a non-immediate function, and
(22.2) if
Edesignates a function of consteval-only type (6.8.1 [basic.types.general]) or an object whose complete object is of consteval-only type, thenEis also of consteval-only type,[ Example 1:— end example ]struct Base { }; struct Derived : Base { std::meta::info r; }; consteval const Base& fn(const Derived& derived) { return derived; } constexpr auto obj = Derived{^^::}; // OK constexpr auto const& d = obj; // OK constexpr auto const& b = fn(obj); // error: not a constant expression // because Derived is a consteval-only type but Base is not.or a prvalue core constant expression whose result object ([basic.lval]) satisfies the following constraints:
- (22.3) each constituent reference refers to an object or a non-immediate function,
- (22.4) no constituent value of scalar type is an indeterminate value or erroneous value (6.7.5 [basic.indet]),
- (22.5) no constituent value of pointer type is a pointer to an immediate function or an invalid pointer value ([basic.compound]),
and- (22.6) no constituent value of pointer-to-member type designates an immediate function
., and
- (22.7) unless the value is of consteval-only type, no constituent value of pointer or pointer-to-member type is
nor does any constituent reference refer to an object or function of consteval-only type.
- a pointer to a function or member of consteval-only type, or
- a pointer to or past an object whose complete object has consteval-only type,
Modify (and clean up) the definition of immediate-escalating expression in paragraph 25 to also apply to expressions of consteval-only type.
25 A
npotentially-evaluated expression or conversion is immediate-escalating if it isnotneither initially in an immediate function context nor a subexpression of an immediate invocation, and it iseither
- (25.1)
a potentially-evaluatedanid-expressionorsplice-expressionthatdenotesdesignates an immediate function,that is not a subexpression of an immediate invocation, or- (25.2) an immediate invocation that is not a constant expression, or
and is not a subexpression of an immediate invocation.- (25.3) of consteval-only type (6.8.1 [basic.types.general]).
Extend the definition of immediate function in paragraph 27 to include functions containing a declaration of a variable of consteval-only type.
27 An immediate function is a function or constructor that is either
After the example following the definition of manifestly constant-evaluated, introduce new terminology and rules for injecting declarations and renumber accordingly:
28 The evaluation of an expression can introduce one or more injected declarations. Each such declaration has an associated synthesized point which follows the last non-synthesized program point in the translation unit containing that declaration. The evaluation is said to produce the declaration.
[ Note 13: Special rules concerning reachability apply to synthesized points (10.7 [module.reach]). — end note ]
No member of an injected declaration shall have a name reserved by the implementation ([lex.name]); no diagnostic is required.
29 Let
Cbe aconsteval-block-declaration, the evaluation of whose corresponding expression produces an injected declarationD([meta.reflection.define.aggregate]). The scope ofDshall not encloseC. The program is ill-formed if a scopeSencloses exactly one ofCorDwhereSis[ Example 2:— end example ]struct S0 { consteval { std::meta::define_aggregate(^^S0, {}); // error: S0 encloses the consteval block } }; struct S1; consteval { std::meta::define_aggregate(^^S1, {}); } // OK template <std::meta::info R> consteval void tfn1() { std::meta::define_aggregate(R, {}); } struct S2; consteval { tfn1<^^S2>(); } // OK, tfn1<^^S2>() and S2 are enclosed by the same scope template <std::meta::info R> consteval void tfn2() { consteval { std::meta::define_aggregate(R, {}); } return b; } struct S3; consteval { tfn2<^^S3>(); } // error: complete_type(^^S3) is enclosed tfn2<^^S3>, but S3 is not template <typename> struct TCls { struct S4; static void sfn() requires ([] { consteval { std::meta::define_aggregate(^^S4, {}); } return true; }) { } }; consteval { TCls<void>::sfn(); } // error: TCls<void>::S4 is not enclosed by requires-clause lambda struct S5; struct Cls { consteval { std::meta::define_aggregate(^^S5, {}); } // error: S5 is not enclosed by class Cls }; struct S6; consteval { // #1 struct S7; // local class consteval { // #2 std::meta::define_aggregate(^^S6, {}); // error: consteval block #1 encloses consteval block #2 but not S6 std::meta::define_aggregate(^^S7, {}); // OK, consteval block #1 encloses both #2 and S7 } }30 The evaluation context is a set of program points that determines the behavior of certain functions used for reflection ([meta.reflection]). During the evaluation of an expression
Eas a core constant expression, the evaluation context is defined as follows:
- (30.1) If
Eis a potentially-evaluated subexpression ([intro.execution]) of a default member initializer used by an aggregate initialization that appears at a pointQ, the evaluation context ofQ.- (30.2) Otherwise, if
Eis a potentially-evaluated subexpression of a default argument ([dcl.fct.default]), and that default argument is being used by an invocation of a function ([expr.call]) that appears at a pointQ, the evaluation context ofQ.- (30.3) Otherwise, the instantiation context of the point at which
Eappears.
Introduce the non-terminal
vacuous-declaration in
paragraph 9.1 to encompass static assertions, empty declarations, and
consteval blocks:
block-declaration: simple-declaration asm-declaration namespace-alias-definition using-declaration using-enum-declaration using-directive - static_assert-declaration alias-declaration opaque-enum-declaration + vacuous-declaration [...] + vacuous-declaration: + static_assert-declaration + consteval-block-declaration static_assert-declaration: static_assert ( constant-expression ) ; static_assert ( constant-expression , static_assert-message ) ; + consteval-block-declaration: + consteval compound-statement
Strike the assertion that a
typedef-name is synonymous
with its associated type from paragraph 8 (type aliases are entities
now).
8 If the
decl-specifier-seqcontains thetypedefspecifier, the declaration is a typedef declaration and eachdeclarator-idis declared to be atypedef-name, synonymous with its associated type(9.2.4 [dcl.typedef]).
Insert the following after paragraph 14 in relation to consteval blocks:
14 Recommended practice: When a
static_assert-declarationfails, […]* For a
consteval-block-declarationD, the expressionEcorresponding toDis:[] -> void static consteval compound-statement ()
Dis equivalent to:static_assert((E, true));[ Note 1: Evaluating a
consteval-block-declarationcan produce injected declarations as side effects ([expr.const]). — end note ][ Example 1:— end example ]struct S; consteval { std::meta::define_aggregate(^^S, {}); // ok template <class T> struct X { }; // error: local templates are not allowed template <class T> concept C = true; // error: local concepts are not allowed return; // ok }15 An
empty-declarationhas no effect.
typedef
specifierModify paragraphs 1-2 to clarify that the
typedef
specifier now introduces an entity.
1 Declarations containing the
decl-specifiertypedefdeclareidentifiers that can be used later for namingtype aliases whose underlying entities are fundamental (6.8.2 [basic.fundamental]) or compound (6.8.4 [basic.compound]) types. Thetypedefspecifier shall not be combined in adecl-specifier-seqwith any other kind of specifier except adefining-type-specifier, and it shall not be used in thedecl-specifier-seqof aparameter-declaration(9.3.4.6 [dcl.fct]) nor in thedecl-specifier-seqof afunction-definition(9.6 [dcl.fct.def]). If atypedef-specifierappears in a declaration without adeclarator, the program is ill-formed.typedef-name: identifier simple-template-idA name declared with the
typedefspecifier becomes atypedef-name.AThe underlying entity of the type alias is the type associated with thetypedef-namenamesidentifier(9.3 [dcl.decl]) orsimple-template-id(13.1 [temp.pre]); atypedef-nameisthusa synonym fordenotes another type. Atypedef-namedoes not introduce a new type the way a class declaration (11.3 [class.name]) or enum declaration (9.8.1 [dcl.enum]) does.2 A
type alias can also betypedef-nameintroduceddeclared by analias-declaration. Theidentifierfollowing theusingkeyword is not looked up; it becomesathetypedef-nameof a type alias and the optionalattribute-specifier-seqfollowing theidentifierappertains to thattype alias. Such atypedef-nametype alias has the same semantics as if it were introduced by thetypedef-nametypedefspecifier. In particular, it does not define a new type.
Extend the grammar for
computed-type-specifier as
follows:
computed-type-specifier: decltype-specifier pack-index-specifier + splice-type-specifier
Extend the definition of “placeholder for a deduced class type” in p3
to accommodate
splice-type-specifiers.
3 A
placeholder-type-specifieris a placeholder for a type to be deduced ([dcl.spec.auto]). Atype-specifierof the formis a placeholder for a deduced class type ([dcl.type.class.deduct]) if it eithertypenameopt nested-name-specifieropt template-name
- (3.1) is of the form
typenameopt nested-name-specifieropt template-name, or- (3.2) is of the form
typenameopt splice-specifierand thesplice-specifierdesignates a class template or alias template.The
nested-name-specifierorsplice-specifier, if any, shall be non-dependent and thetemplate-nameorsplice-specifiershallnamedesignate a deducible template. A deducible template is either a class template or is an alias template whosedefining-type-idis of the formtypenameopt nested-name-specifieropt templateopt simple-template-idwhere the
nested-name-specifier(if any) is non-dependent and thetemplate-nameof thesimple-template-idnames a deducible template.
Add a row to [tab:dcl.type.simple] to cover the
splice-type-specifier
production.
Table 17: simple-type-specifiers and the types they specify [tab:dcl.type.simple]
Specifier(s) Typetype-namethe type named simple-template-idthe type as defined in [temp.names] decltype-specifierthe type as defined in [dcl.type.decltype] pack-index-specifierthe type as defined in [dcl.type.pack.index] placeholder-type-specifierthe type as defined in [dcl.spec.auto] template-namethe type as defined in [dcl.type.class.deduct] splice-type-specifierthe type as defined in [dcl.type.splice] ...…
Add a bullet after bullet 1.3 to apply to
splice-expressions, and
extend the example that follows the paragraph:
1 For an expression
E, the type denoted bydecltype(E)is defined as follows:[…]
- (1.3) otherwise, if
Eis an unparenthesizedid-expressionor an unparenthesized class member access ([expr.ref]),decltype(E)is the type of the entity named byE. If there is no such entity, the program is ill-formed;- (1.3+) otherwise, if
Eis an unparenthesizedsplice-expression,decltype(E)is the type of the entity, object, or value designated by thesplice-specifierofE;[…]
The operand of the
decltypespecifier is an unevaluated operand.[ Example 1:— end example ]const int && foo(); int i; struct A {double x; }; const A* a = new A(); decltype(foo()) x1 = 17; // type is const int&& decltype(i) x2; // type is int decltype(a->x) x3; // type is double decltype((a->x)) x4 = x3; // type is const double&decltype([:^^x1:]) x5 = 18; // type is const int&&void f() { [](auto ...pack) { decltype(pack...[0])x5x6; // type is int decltype((pack...[0]))x6x7; // type is int& } }
Extend the wording in 9.2.9.7.1 [dcl.spec.auto.general]/13 to account for splicing:
13 If a variable or function with an undeduced placeholder type is either named by an expression ([basic.def.odr]) or designated by a
splice-specifierin an expression, the program is ill-formed. Once a non-discardedreturnstatement has been seen in a function, however, the return type deduced from that statement can be used in the rest of the function, including in otherreturnstatements.[ Example 1:— end example ]auto n = n; // error: n's initializer refers to n auto f(); void g() { &f; } // error: f's return type is unknown auto sum(int i) { if (i == 1) return i; // sum's return type is int else return sum(i-1)+i; // OK, sum's return type has been deduced } + auto f2() { + int x; + return [:std::meta::parent_of(^^x):](); // error: f2's return type is unknown + }
Add a new subsection of (9.2.9 [dcl.type]) following (9.2.9.8 [dcl.type.class.deduct]).
Type Splicing [dcl.type.splice]
splice-type-specifier: typenameopt splice-specifier typenameopt splice-specialization-specifier1 A
splice-specifierorsplice-specialization-specifierimmediately followed by::is never interpreted as part of asplice-type-specifier. Asplice-specifierorsplice-specialization-specifiernot preceded bytypenameis only interpreted as asplice-type-specifierwithin a type-only context (13.8.1 [temp.res.general]).[ Example 1:— end example ]struct S { using type = int; }; template <auto R> struct TCls { typename [:R:]::type member; // typename applies to the qualified name }; void fn() { [:^^S::type:] *var; // error: [:^^S::type:] is an expression typename [:^^S::type:] *var; // OK, declares variable with type int* } using alias = [:^^S::type:]; // OK, type-only context2 For a
splice-type-specifierof the formtypenameopt splice-specifier, thesplice-specifiershall designate a type, a primary class template, or an alias template. Thesplice-type-specifierdesignates the same entity as thesplice-specifier.3 For a
splice-type-specifierof the formtypenameopt splice-specialization-specifier, thesplice-specifierof thesplice-specialization-specifiershall designate a primary class template or an alias template. Thesplice-type-specifierdesignates the specialization ofTcorresponding to thetemplate-argument-list(if any) of thesplice-specialization-specifier.
[ Editor's note: This change is part of the resolution to [CWG2701]. ]
Use “host scope” in lieu of “inhabits” in paragraph 8:
8 Furthermore, if there is a reachable declaration of the entity that
inhabitshas the same host scope in which the bound was specified, an omitted array bound is taken to be the same as in that earlier declaration, and similarly for the definition of a static data member of a class.
Use “denoted by” instead of “named by” in paragraph 9 to be more clear about the entity being referred to, and add a bullet to allow for reflections of abominable function types:
9 A function type with a
cv-qualifier-seqor aref-qualifier(including a typenameddenoted bytypedef-name([dcl.typedef], [temp.param])) shall appear only as:
- (9.1) the function type for a non-static member function,
- (9.2) the function type to which a pointer to member refers,
- (9.3) the top-level function type of a function typedef declaration or
alias-declaration,- (9.4) the
type-idin the default argument of atype-parameter([temp.param]),- (9.5) the
type-idof atemplate-argumentfor atype-parameter([temp.arg.type])., or
- (9.6) the operand of a
reflect-expression([expr.reflect]).
Extend the example that follows to demonstrate taking the reflection of an abominable function type:
[ Example 4:— end example ]typedef int FIC(int) const; FIC f; // error: does not declare a member function struct S { FIC f; // OK }; FIC S::*pm = &S::f; // OKconstexpr std::meta::info r = ^^void(int) &; // OK
[ Editor's note: The changes related to “host scopes” in paragraphs 4 and 9 are part of the resolution to [CWG2701]. ]
Use “host scope” in lieu of “inhabits” in paragraph 4:
4 For non-template functions, default arguments can be added in later declarations of a function that
inhabithave the same host scope. Declarations thatinhabithave different host scopes have completely distinct sets of default arguments. […]
Modify paragraph 9 to allow reflections of non-static data members to appear in default function arguments, extend example 8 which follows, and use “host scope” rather than “inhabits” following example 9.
9 A default argument is evaluated each time the function is called with no argument for the corresponding parameter.
[…]
A non-static member shall not appear or be designated in a default argument unless it appears as the
id-expressionorsplice-expressionof a class member access expression ([expr.ref]) or unless it is used to form a pointer to member ([expr.unary.op]) or a reflection ([expr.reflect]).[ Example 8: The declaration of
X::mem1()in the following example is ill-formed because no object is supplied for the non-static memberX::aused as an initializer.int b; class X { int a; int mem1(int i = a); // error: non-static member a used as default argument int mem2(int i = b); // OK; use X::bconsteval void mem3(std::meta::info r = ^^a) {}; // OKint mem4(int i = [:^^a:]); // error: non-static member a designated in default argumentstatic int b; }[…]
When an overload set contains a declaration of a function
that inhabits awhose host scope isS, any default argument associated with any reachable declarationthat inhabitswhose host scope isSis available to the call.[ Note 7: The candidate might have been found through a
using-declaratorfrom which the declaration that provides the default argument is not reaachable. — end note ] — end example ]
Change paragraphs 6-8 of 9.5.1 [dcl.init.general] [ Editor's note: No changes are necessary for value-initialization, which already forwards to zero-initialization for scalar types ]:
6 To zero-initialize an object or reference of type
Tmeans:
- (6.0) if
Tisstd::meta::info, the object is initialized to a null reflection value;- (6.1) if
Tisaany other scalar type (6.8.1 [basic.types.general]), the object is initialized to the value obtained by converting the integer literal0(zero) toT;- (6.2) […]
7 To default-initialize an object of type
Tmeans:
- (7.1) If
Tis a (possibly cv-qualified) class type ([class]), […]- (7.2) If T is an array type, […]
- (7.*) If
Tisstd::meta::info, the object is zero-initialized.- (7.3) Otherwise, no initialization is performed.
8 A class type
Tis const-default-constructible if default-initialization ofTwould invoke a user-provided constructor ofT(not inherited from a base class) or if
- (8.1) […]
If a program calls for the default-initialization of an object of a const-qualified type
T,Tshall bestd::meta::infoor a const-default-constructible class type, or array thereof.9 To value-initialize an object of type T means: […]
Disallow using
__func__ in
a consteval
block:
7 A function-local predefined variable is a variable with static storage duration that is implicitly defined in a function parameter scope, other than the function parameter scope of the expression corresponding to a
consteval-block-declaration.8 The function-local predefined variable
__func__is defined as if a definition of the form […]
Change paragraph 2 of 9.6.3 [dcl.fct.def.delete] to allow for reflections of deleted functions:
2 A program that refers to a deleted function implicitly or explicitly, other than to declare it or to use as the operand of a
reflect-expression([expr.reflect]), is ill-formed.
using enum
declarationExtend the grammar for
using-enum-declarator as
follows:
using-enum-declaration: using enum using-enum-declarator ; using-enum-declarator: nested-name-specifieropt identifier nested-name-specifieropt simple-template-id + splice-type-specifier
Modify paragraph 1 to handle
splice-type-specifiers:
1 A
using-enum-declaratorof the formsplice-type-specifierdesignates the same construct designated by thesplice-type-specifier. Any otherAusing-enum-declaratornames the set of declarations found by type-only lookup ([basic.lookup.general]) for theusing-enum-declarator(6.5.3 [basic.lookup.unqual], 6.5.5 [basic.lookup.qual]). Theusing-enum-declaratorshall designate a non-dependent type with a reachableenum-specifier.
Modify the grammar for
namespace-alias-definition
in paragraph 1, and clarify that such declarations declare a “namespace
alias” (which is now an entity as per [basic.pre]).
1 A
namespace-alias-definitiondeclaresan alternative name for a namespacea namespace alias according to the following grammar:namespace-alias: identifier namespace-alias-definition: namespace identifier = qualified-namespace-specifier + namespace identifier = splice-specifier qualified-namespace-specifier: nested-name-specifieropt namespace-nameThe
splice-specifier(if any) shall designate a namespace other than the global namespace.
Remove the details about what the
namespace-alias denotes;
this will fall out from the “underlying entity” of the namespace alias
defined below:
2 The
identifierin anamespace-alias-definitionbecomes anamespace-aliasand denotes the namespace denoted by the.qualified-namespace-specifier
Add the following paragraph after paragraph 2 and before the note:
2+ The underlying entity (6.1 [basic.pre]) of the namespace alias is the namespace either denoted by the
qualified-namespace-specifieror designated by thesplice-specifier.
Add splice-specifier to
the grammar for
using-directive:
using-directive: attribute-specifier-seqopt using namespace nested-name-specifieropt namespace-name + attribute-specifier-seqopt using namespace splice-specifier
Add the following prior to the first paragraph of 9.9.4 [namespace.udir], and renumber accordingly:
0 The
splice-specifier(if any) shall designate a namespace other than the global namespace. Thenested-name-specifier,namespace-name, andsplice-specifiershall not be dependent.1 A
using-directiveshall not appear in class scope, but may appear in namespace scope or in block scope.[…]
Prefer the verb “designate” rather than “nominate” in the notes that follow:
[ Note 2: A
using-directivemakes the names in thenominateddesignated namespace usable in the scope […]. During unqualified name lookup, the names appear as if they were declared in the nearest enclosing namespace which contains both theusing-directiveand thenomindateddesignated namespace. — end note ][…]
[ Note 4: A
using-directiveis transitive: if a scope contains ausing-directivethatnominatesdesignates a namespace that itself containsusing-directives, the namespacesnominateddesignated by thoseusing-directivesare also eligible to be considered. — end note ]
Add a production to the grammar for
attribute-specifier as
follows:
attribute-specifier: [ [ attribute-using-prefixopt attribute-list ] ] + [ [ using attribute-namespace :] ] alignment-specifier
and update the grammar for balanced token as follows:
balanced-token : ( balanced-token-seqopt ) [ balanced-token-seqopt ] { balanced-token-seqopt } - any token other than a parenthesis, a bracket, or a brace + [: balanced-token-seqopt :] + any token other than (, ), [, ], {, }, [:, or :]
Change a sentence in paragraph 4 of 9.13.1 [dcl.attr.grammar] as follows:
4 […] An
attribute-specifierthat contains noattributes and noalignment-specifierhas no effect. [ Note 1: That includes anattribute-specifierof the form[ [ using attribute-namespace :] ]which is thus equivalent to replacing the:]token by the two-token sequence:]. — end note ] …
Prefer “type alias” to
“typedef-name” in paragraph
2.
2 The attribute may be applied to the declaration of a class, a
type alias, a variable, a non-static data member, a function, a namespace, an enumeration, an enumerator, a concept, or a template specialization.typedef-name
Prefer “type alias” to
“typedef-name” in paragraph
2.
2 The attribute may be applied to the declaration of a class,
type alias, variable (including a structured binding declaration), structured binding, non-static data member, function, enumeration, or enumerator, or to antypedef-nameidentifierlabel (8.2 [stmt.label]).
Specify in paragraph 3 that it is unspecified whether spliced types are replaced by their designated types, and renumber accordingly. Add an additional bullet further clarifying that it is unspecified whether any splice specifier is replaced.
3 […]
In this determination, it is unspecified
- (3.6) whether a reference to an
alias-declaration,typedefdeclaration,using-declaration, ornamespace-alias-definitionis replaced by the declarations they name prior to this determination,- (3.7) whether a
simple-template-idthat does not denote a dependent type and whosetemplate-namenames an alias template is replaced by its denoted type prior to this determination,- (3.8) whether a
decltype-specifierorsplice-type-specifierthat does notdenotedesignate a dependent type is replaced by itsdenoteddesignated type prior to this determination,and- (3.9) whether a non-value-dependent constant expression is replaced by the result of constant evaluation prior to this determination
., and- (3.10) whether a
splice-specifierorsplice-expressionthat is not dependent is replaced by the construct that it designates prior to this determination.
Modify paragraphs 2 through 6 to relax the phrasing used to define the points in the instantiation context, add a new paragraph to include synthesized points in the instantiation context, and add a paragraph clarifying that the context contains only these points.
2 During the implicit definition of a defaulted function ([special], [class.compare.default]), the instantiation context
iscontains each point in the union of the instantiation context from the definition of the class and the instantiation context of the program construct that resulted in the implicit definition of the defaulted function.3 During the implicit instantiation of a template whose point of instantiation is specified as that of an enclosing specialization ([temp.point]), the instantiation context
iscontains each point inthe union ofthe instantiation context of the enclosing specialization and, if the template is defined in a module interface unit of a moduleMand the point of instantiation is not in a module interface unit ofM, the point at the end of thedeclaration-seqof the primary module interface unit ofM(prior to theprivate-module-fragment, if any).4 During the implicit instantiation of a template that is implicitly instantiated because it is referenced from within the implicit definition of a defaulted function, the instantiation context
iscontains each point in the instantiation context of the defaulted function.5 During the instantiation of any other template specialization, the instantiation context
comprisescontains the point of instantiation of the template.6 In any other case, the instantiation context at a point within the program
comprisescontains that point.6+ During the evaluation
Eof an expression, or during the implicit instantiation of any construct that resulted from that evaluation, the instantiation context also contains each synthesized point ([expr.const]) corresponding to an injected declaration produced by any evaluation sequenced beforeE([intro.execution]).6++ The instantiation context contains only those points specified above.
Modify the definition of reachability to account for injected declarations:
3 A declaration
Dis reachable from a pointPif
- (3.1)
Pis not a synthesized point andDappears prior toPin the same translation unit,or- (3.2)
Dis an injected declaration for whichPis the corresponding synthesized point, or- (3.3)
Dis not discarded (10.4 [module.global.frag]), appears in a translation unit that is reachable fromP, and does not appear within a private-module-fragment.[ Example 1:— end example ]class S { class Incomplete; /* P1 */ consteval { // OK, n == 7. The member Incomplete::x members-of-precedes the synthesized // point P2 associated with the injected declaration produced by the call to // define_aggregate. int n = members_of( define_aggregate(^^Incomplete, {data_member_spec(^^int, {.name="x"})}), std::meta::access_context::current() ).size(); } }; /* P2 */
Modify the grammar for
member-declaration as
follows:
member-declaration: attribute-specifier-seqopt decl-specifier-seqopt member-declarator-listopt; function-definition friend-type-declaration using-declaration using-enum-declaration - static_assert-declaration + vacuous-declaration template-declaration explicit-specialization deduction-guide alias-declaration opaque-enum-declaration empty-declaration
Update paragraph 4 accordingly:
4 A
member-declarationdoes not declare new members of the class if it is
Extend paragraph 6, and modify note 3, to clarify the existence of subobjects corresponding to non-static data members of reference types.
6 A data member or member function may be declared
staticin itsmember-declaration, in which case it is a static member (see [class.static]) (a static data member ([class.static.data]) or static member function ([class.static.mfct]), respectively) of the class. Any other data member or member function is a non-static member (a non-static data member or non-static member function ([class.mfct.non.static]), respectively). The layout-associated type of a non-static data member isT*if it has typeT&orT&&and the type of the member otherwise. For layout purposes, it is as if the member were declared with its layout-associated type.[ Note 3: A non-static data member of non-reference type is a member subobject of a class object. — end note ]
Add a new paragraph to the end of the section defining data member description:
30+ A data member description is a quintuple (
T,N,A,W,NUA) describing the potential declaration of a nonstatic data member where
- (30+.1)
Tis a type or type alias,- (30+.2)
Nis anidentifieror ⊥,- (30+.3)
Ais an alignment or ⊥,- (30+.4)
Wis a bit-field width or ⊥, and- (30+.5)
NUAis a boolean value.Two data member descriptions are equal if each of their respective components are same types, same identifiers, and equal values.
[ Note 4: The components of a data member description describe a data member such that
- (30+.6) its type is specified using the type or type alias given by
T,- (30+.7) it is declared with the name given by
NifNdoes not equal ⊥ and is otherwise unnamed,- (30+.8) it is declared with the
alignment-specifier(9.13.2 [dcl.align]) given byalignas(A)ifAdoes not equal ⊥ and is otherwise declared without analignment-specifier,- (30+.9) it is a bit-field (11.4.10 [class.bit]) with the width given by
WifWdoes not equal ⊥ and is otherwise not a bit-field,- (30+.10) it is declared with the attribute
[[no_unique_address]](9.13.11 [dcl.attr.nouniqueaddr]) ifNUAistrueand is otherwise declared without that attribute.Data member descriptions are represented by reflections (6.8.2 [basic.fundamental]) returned by
std::meta::data_member_spec([meta.reflection.define.aggregate]) and can be reified as data members of a class usingstd::meta::define_aggregate([meta.reflection.define.aggregate]). — end note ]
Replace
static_assert-declaration
with vacuous-declaration in
paragraph 1.
1 […] Each
member-declarationin themember-specificationof an anonymous union shall either define one or more public non-static data members or be astatic_assert-declarationvacuous-declaration. […]
Introduce the term “direct base class relationship” to paragraph 2.
2 The component names of a
class-or-decltypeare those of itsnested-name-specifier,type-name, and/orsimple-template-id. Aclass-or-decltypeshall denote a (possily cv-qualified) class type that is not an incompletely defined class (11.4 [class.mem]); any cv-qualifiers are ignored. The class denoted by theclass-or-decltypeof abase-specifieris called a direct base class for the class being defined; each suchbase-specifierintroduces a direct base class relationship between the class being defined and the direct base class. The lookup for the component name of thetype-nameorsimple-template-idis type-only (6.5 [basic.lookup]). […]
Prefer “type alias” rather than
typedef-name in the note
that follows paragraph 4.
[ Note 3: Because access control applies to the declarations named, if access control is applied to a
type alias, only the accessibility of the typedef or alias declaration itself is considered. The accessibility of thetypedef-nameentity referred to by theunderlying entity is not considered. — end note ]typedef-name
Update paragraph 5 to handle
splice-expressions, and to
make more clear that the “naming class” (renamed “designating class”
here) is a property of the expression. State explicitly that members
designated through
splice-expressions are
accessible.
5 […]
The access to a member is affected by the class in which the member is named. This naming class is theAn expressionEthat designates a membermhas a designating class that affects the access tom. This designating class is either
- (5.1) the innermost class of which
mis directly a member ifEis asplice-expressionor- (5.2) the class in whose scope lookup performed a search that found
mthe memberotherwise.[ Note 3: This class can be explicit, e.g., when a
qualified-idis used, or implicit, e.g., when a class member access operator ([expr.ref]) is used (including cases where an implicit “this->” was added). If both a class member access operator and aqualified-idare used tonamedesignate the member (as inp->T::m), the classnamingdesignating the member is the class denoted by thenested-name-specifierof thequalified-id(that is,T). — end note ]A member
mis accessible at the pointRwhennameddesignated in classNif
- (5.3)
mas a member ofNis public, or- (5.4)
mas a member ofNis private, andRoccurs in a direct member or friend of classN, or- (5.5)
mas a member ofNis protected, andRoccurs in a direct member or friend of classN, or in a member of a classPderived fromN, wheremas a member ofPis public, private, or protected, or- (5.6)
mis designated by asplice-expression, or- (5.7) there exists a base class
BofNthat is accessible atR, andmis accessible atRwhennameddesignated in classB.
Update paragraph 6, and the note which follows, to use the term “designated class”:
6 If a class member access operator, including an implicit “
this->”, is used to access a non-static data member or non-static member function, the reference is ill-formed if the left operand (considered as a pointer in the “.” case) cannot be implicitly converted to a pointer to thenamingdesignating class of the right operand.[ Note 4: This requirement is in addition to the requirement that the member be accessible as
nameddesignated. — end note ]
Add a note explaining the expressions that form overload sets after paragraph 2.
2 When a function is named or designated in a call, which function declaration is being referenced and the validity of the call are determined by comparing the types of the arguments at the point of use with the types of the parameters in the declarations in the overload set. This function selection process is called overload resolution and is defined in [over.match].
[ Note 1: Overload sets are formed by
id-expressions naming functions and function templates and bysplice-expressions designating entities of the same kinds. — end note ]
Change the section title:
Modify paragraph 1 to clarify that this section will also apply to splices of function templates.
1 Of interest in [over.call.func] are only those function calls in which the
posfix-expressionultimately contains anid-expressionorsplice-expressionthat denotes one or more functions. Such apostfix-expression, perhaps nested arbitrarily deep in parentheses, has one of the following forms:postfix-expression: postfix-expression . id-expression + postfix-expression . splice-expression postfix-expression -> id-expression + postfix-expression -> splice-expression primary-expressionThese represent two syntactic subcategories of function calls: qualified function calls and unqualified function calls.
Modify paragraph 2 to account for overload resolution of
splice-expressions. Massage
the wording to better account for member function templates.
2 In qualified function calls, the function is
nameddesignated by anid-expressionorsplice-expressionpreceded by an->or.operator. Since the constructA->Bis generally equivalent to(*A).B, the rest of [over] assumes, without loss of generality, that all member function calls have been normalized to the form that uses an object and the.operator. Furthermore, [over] assumes that thepostfix-expressionthat is the left operand of the.operator has type “cvT” whereTdenotes a class.102TheA set of function declarations, either found by name lookup ([class.member.lookup]) if the dot is followed by anid-expression, or determined as specified in [expr.prim.splice] if followed by asplice-expression, constitute the set of candidate functions. The argument list is theexpression-listin the call augmented by the addition of the left operand of the.operator in the normalized member function call as the implied object argument (12.2.2 [over.match.funcs]).
Modify paragraph 3 to account for overload resolution of
splice-expressions. Massage
the wording to better account for member function templates.
3 In unqualified function calls, the function is
nameddesignated by aprimary-expression(call itE).TheA set of function declarations, either found by name lookup ([basic.lookup]) ifEis a (possibly parenthesized)id-expression, or determined as specified in [expr.prim.splice] ifEis a (possibly parenthesized)splice-expression, constitute the set of candidate functions. Because of the rules for name lookup, the set of candidate functions consists either entirely of non-member functions or entirely of member functions of some classT. In the former case or iftheprimary-expressionEis either asplice-expressionor the address of an overload set, the argument list is the same as theexpression-listin the call. Otherwise, the argument list is theexpression-listin the call augmented by the addition of an implied function argument as in a qualified function call. If the current class is, or is derived from,T, and the keywordthis(7.5.3 [expr.prim.this]) refers to it, then the implied object argument is(*this). Otherwise, a contrived object of typeTbecomes the implied object argument;103 if overload resolution selects a non-static member function, the call is ill-formed.
Extend paragraph 1 to work with
splice-type-specifiers.
1 When resolving a placeholder for a deduced class type (9.2.9.8 [dcl.type.class.deduct]) where the
template-nameorsplice-type-specifiernamesdesignates a primary class templateC, a set of functions and function templates, called the guides ofC, is formed comprising:
- (1.1) …
Extend paragraph 3 to also cover
splice-type-specifiers.
3 When resolving a placeholder for a deduced class type (9.2.9.3 [dcl.type.simple]) where the
template-nameorsplice-type-specifiernamesdesignates an alias templateA, thedefining-type-idofAmust be of the formtypenameopt nested-name-specifieropt templateopt simple-template-idas specified in 9.2.9.3 [dcl.type.simple]. The guides of
Aare the set of functions or function templates formed as follows. …
[ Editor's note: The
changes to paragraph 2.3 (except for the wording related to
splice-expressions) are
a part of the resolution to [CWG2701]. These changes render
[over.match.best.general]/4 redundant, hence the relocation of its
associated example to this section. ]
Specify rules for overload sets denoted by
splice-expressions in
paragraph 2, make drive-by fixes to help clear up the situation more
generally, and move the example that formerly followed
[over.match.best.general]/4 to follow after paragraph 2 (with a new
example covering
splice-expressions).
2 First, to be a viable function, a candidate function shall have enough parameters to agree in number with the arguments in the list.
- (2.1) If there are
marguments in the lists, all candidate functions having exactlymparameters are viable.- (2.2) A candidate function having fewer than
mparameters is viable only if it has an ellipsis in its parameter list ([dcl.fct]). For the purposes of overload resolution, any argument for which there is no corresponding parameter is considered to “match the ellipsis” ([over.ics.ellipsis]).- (2.3) A candidate function having more than
mparameters is viable only if there is a declarationDconsidered by the overload resolution such that for eachallparametersfollowing themthhave default arguments, a reachable declaration whose host scope is the same as that ofDspecifies a default argument for that parameter ([dcl.fct.default]). If the candidate function is selected as the best viable function, no other host scope of a declaration considered by overload resolution shall be the host scope of a declaration that specifies a default argument for the (m+1)st parameter; the host scope ofDshall not be a block scope if the declarations considered by overload resolution were denoted by asplice-expression([expr.prim.splice]). The default arguments used in a call to the selected function are the default arguments introduced in the host scope ofD. For the purposes of overload resolution, the parameter list is truncated on the right, so that there are exactlymparameters.[ Example 1:— end example ]namespace A { extern "C" void f(int = 5); } namespace B { extern "C" void f(int = 5); } void splice() { [:^^A::f:](3); // OK, default argument was not used for viability [:^^A::f:](); // error: found default argument twice } using A::f; using B::f; void use() { f(3); // OK, default argument was not used for viability f(); // error: found default argument twice }
[ Editor's note: The changes to [over.match.viable]/2.3 included in this proposal (part of the resolution to [CWG2701]) render paragraph 4 redundant; the contents of example 9 now follow [over.match.viable]/2. ]
Delete paragraph 4 and example 9.
4 If the best viable function resolves to a function for which multiple declarations were found, and if any two of these declarations inhabit different scopes and specify a default argument that made the function viable, the program is ill-formed.
[ Example 9:— end example ]namespace A { extern "C" void f(int = 5); } namespace B { extern "C" void f(int = 5); } using A::f; using B::f; void use() { f(3); // OK, default argument was not used for viability f(); // error: found default argument twice }
Remove the explicit references to
id-expressions from
paragraph 1 to allow taking the address of an overload set specified by
a splice-expression:
1 An
expression that designates an overload setid-expressionwhose terminal name refers toSand that appears without arguments is resolved to a function, a pointer to function, or a pointer to member function for a specific function that is chosen from a set of functions selected fromSdetermined based on the target type required in the context (if any), as described below. […]The
expression can be preceded by theid-expression&operator.
Add built-in operator candidates for std::meta::info
to 12.5 [over.built]:
16 For every
T, whereTis a pointer-to-member type,std::meta::info, orstd::nullptr_t, there exist candidate operator functions of the formbool operator==(T, T); bool operator!=(T, T);
Extend
type-tt-parameter-default
and variable-tt-parameter
to permit splice-specifiers
as default template arguments for template template parameters.
type-tt-parameter-default: nested-name-specifieropt template-name nested-name-specifier template template-name + splice-template-argument variable-tt-parameter: template-head auto ...opt $identifieropt template-head auto identifieropt = nested-name-specifieropt template-name + template-head auto identifieropt = splice-template-argument concept-tt-parameter: template < template-parameter-list > concept ...opt identifieropt template < template-parameter-list > concept identifieropt = nested-name-specifieropt template-name + template < template-parameter-list > concept identifieropt = splice-template-argument
Add a paragraph after paragraph 3 to disallow dependent concepts
being used in a
type-constraint:
3+ The
nested-name-specifierof atype-constraint, if any, shall not be dependent.
Define the term
splice-template-argument,
and add it as a production for
template-argument.
template-argument: constant-expression type-id nested-name-specifieropt template-name nested-name-specifieropt template template-name + splice-template-argument + splice-template-argument: + splice-specifier
Extend and re-format paragraph 3 of 13.3 [temp.names]:
3 A
<is interpreted as the delimiter of atemplate-argument-listif it follows
- (3.1) a
splice-specifierthat either appears in a type-only context or is preceded bytemplateortypename, or- (3.2) a name that is not a
conversion-function-idand
- (3.2.1) that follows the keyword template or a ~ after a nested-name-specifier or in a class member access expression, or
- (3.2.2) for which name lookup finds the injected-class-name of a class template or finds any declaration of a template, or
- (3.2.3) that is an unqualified name for which name lookup either finds one or more functions or finds nothing, or
- (3.2.4) that is a terminal name in a using-declarator ([namespace.udecl]), in a declarator-id ([dcl.meaning]), or in a type-only context other than a nested-name-specifier ([temp.res]).
[ Note 1: If the name is an identifier, it is then interpreted as a template-name. The keyword template is used to indicate that a dependent qualified name (13.8.3.2 [temp.dep.type]) denotes a template where an expression might appear. — end note ]
[ Example 1:— end example ]struct X { template<std::size_t> X* alloc(); template<std::size_t> static X* adjust(); }; template<class T> void f(T* p) { T* p1 = p->alloc<200>(); // error: < means less than T* p2 = p->template alloc<200>(); // OK, < starts template argument list T::adjust<100>(); // error: < means less than T::template adjust<100>(); // OK, < starts template argument list + static constexpr auto r = ^^T::adjust; + T* p3 = [:r:]<200>(); // error: < means less than + T* p4 = template [:r:]<200>(); // OK, < starts template argument list }
Clarify that the
>
disambiguation in paragraph 4 also applies to the parsing of
splice-specialization-specifiers:
4 When parsing a
template-argument-list, the first non-nested>108 is taken as the ending delimiter rather than a greater-than operator. Similarly, the first non-nested>>is treated as two consecutive but distinct>tokens, the first of which is taken as the end of thetemplate-argument-listand completes thetemplate-idorsplice-specialization-specifier.[ Note 2: The second
>token produced by this replacement rule can terminate an enclosingtemplate-idorsplice-specialization-specifierconstruct or it can be part of a different construct (e.g., a cast). — end note ]
Extend the definition of a valid
template-id to also cover
splice-specialization-specifiers:
7 A
template-idorsplice-specialization-specifieris valid if
- (7.1) there are at most as many arguments as there are parameters or a parameter is a template parameter pack (13.7.4 [temp.variadic]),
- (7.2) there is an argument for each non-deducible non-pack parameter that does not have a default
template-argument,- (7.3) each
template-argumentmatches the correspondingtemplate-parameter(13.4 [temp.arg]),- (7.4) substitution of each template argument into the following template parameters (if any) succeeds, and
- (7.5) if the
template-idorsplice-specialization-specifieris non-dependent, the associated constraints are satisfied as specified in the next paragraph.A
simple-template-idorsplice-specialization-specifiershall be valid unless it names a function template specialization (13.10.3 [temp.deduct]).
Extend paragraph 8 to require constraints to also be satisfied by
splice-specialization-specifiers:
8 When the
template-nameof asimple-template-idor thesplice-specifierof asplice-specialization-specifierdesignatesnamesa constrained non-function template or a constrained templatetemplate-parameter, and alltemplate-argumentsin thesimple-template-idorsplice-specialization-specifierare non-dependent (13.8.3.5 [temp.dep.temp]), the associated constraints (13.5.3 [temp.constr.decl]) of the constrained template shall be satisfied (13.5.2 [temp.constr.constr]).
Modify footnote 108 to account for
splice-specialization-specifiers:
108) A
>that encloses thetype-idof adynamic_cast,static_cast,reinterpret_castorconst_cast, or which encloses thetemplate-arguments of a subsequenttemplate-idorsplice-specialization-specifier, is considered nested for the purpose of this description.
Modify paragraph 1; there are now four forms of
template-argument.
1 The type and form of each
template-argumentspecified in atemplate-idor in asplice-specialization-specifiershall match the type and form specified for the corresponding parameter declared by the template in itstemplate-parameter-list. Atemplate-argumentthat is a splice template argument is considered to match the form specified for the corresponding template parameter. When the parameter declared by the template is a template parameter pack, it will correspond to zero or moretemplate-arguments.
Clarify ambiguity between
splice-expressions and
splice-template-arguments
in paragraph 3:
3 A
template-argumentof the formsplice-specifieris interpreted as asplice-template-argument.In aFor any othertemplate-argument, an ambiguity between atype-idand an expression is resolved to atype-id, regardless of the form of the correspondingtemplate-parameter.[ Example 2:— end example ]template<class T> void f(); // #1 template<int I> void f(); // #2 void g() { f<int()>(); // int() is a type-id: calls (#1)+ constexpr int x = 42; + f<[:^^int:]>(); // splice-template-argument: calls (#1) + f<[:^^x:]>(); // splice-template-argument: calls (#2) }the first f()
Clarify in paragraph 9 that default template arguments also apply to
splice-specialization-specifiers:
9 When a
simple-template-idorsplice-specialization-specifierdoes notnamedesignate a function, a defaulttemplate-argumentis implicitly instantiated when the value of that default argument is needed.
Extend 13.4.2 [temp.arg.type]/1 to cover splice template arguments:
1 A
template-argumentfor a type template parameter shall either be atype-idor asplice-template-argumentwhosesplice-specifierdesignates a type.
[ Drafting note: We
don’t think we have to change anything here, since if
E is a
splice-specifier that can
be interpreted as a
splice-expression, the
requirements already fall out based on how paragraphs 1 and 3 are
already worded ]
1 If the type
Tof a template-parameter ([temp.param]) contains a placeholder type ([dcl.spec.auto]) or a placeholder for a deduced class type ([dcl.type.class.deduct]), the type of the parameter is the type deduced for the variable x in the invented declarationT x = E ;where
Eis the template argument provided for the parameter.2 The value of a constant template parameter
Pof (possibly deduced) typeT[…]3 Otherwise, a temporary variable
constexpr T v = A;is introduced.
Extend paragraph 1 to cover splice template arguments:
1 A
template-argumentfor a template template parameter shall either be the name of a template or asplice-template-argument. For atype-tt-parameter, the name orsplice-template-argumentshalldenotedesignate a class template or alias template. For avariable-tt-parameter, the name orsplice-template-argumentshalldenotedesignate a variable template. For aconcept-tt-parameter, the name orsplice-template-argumentshalldenotedesignate a concept. Only primary templates are considered when matching the template argument with the corresponding parameter; partial specializations are not considered even if their parameter lists match that of the template template parameter.
Extend paragraph 1 to also define the “sameness” of
splice-specialization-specifiers:
1 Two
template-ids orsplice-specialization-specifiers are the same if
- (1.1) their
template-names,operator-function-ids,orliteral-operator-ids, orsplice-specifiers refer to the same template, and- (1.2) their corresponding type
template-arguments are the same type, and- (1.3) the template parameter values determined by their corresponding constant template arguments (13.4.3 [temp.arg.nontype]) are template-argument-equivalent (see below), and
- (1.4) their corresponding template
template-arguments refer to the same template.Two
template-ids orsplice-specialization-specifiers that are the same refer to the same class, function, or variable.
Extend template-argument-equivalent in paragraph 2 to handle
std::meta::info:
2 Two values are template-argument-equivalent if they are of the same type and
- (2.1) they are of integral type and their values are the same, or
- (2.2) they are of floating-point type and their values are identical, or
- (2.3) they are of type
std::nullptr_t, or- (2.*) they are of type
std::meta::infoand their values are the same, or- (2.4) they are of enumeration type and their values are the same, or
- (2.5) […]
Extend paragraph 1 to clarify that
splice-type-specifiers can
also leverage deduction guides.
1 Deduction guides are used when a
template-nameorsplice-type-specifierappears as a type specifier for a deduced class type ([dcl.type.class.deduct]). Deduction guides are not found by name lookup. Instead, when performing class template argument deduction ([over.match.class.deduct]), all reachable deduction guides declared for the class template are considered.
Clarify in Note 1 that a specialization of a conversion function
template can be formed through a
splice-expression.
[ Note 1: A specialization of a conversion function template is referenced in the same way as a non-template conversion function that converts to the same type ([class.conv.fct]).
…
An expression designating a particular specialization of a conversion function template can only be formed with a
splice-expression. There is no analogous syntax to form atemplate-id([temp.names]) for such a function by providing an explicit template argument list ([temp.arg.explicit]). — end note ]
Extend paragraph 2 to enable reflection of alias template specializations.
2
When aAtemplate-idthat refers to the specialization of an alias template, it is equivalent tois atypedef-namefor a type alias whose underlying entity is the associated type obtained by substitution of itstemplate-argumentsfor thetemplate-parametersin thedefining-type-idof the alias template.
Extend paragraph 4 to define what it means for a
splice-specifier to appear
in a type-only context. Also
using-enum-declarators to
the list of type-only contexts, as it allows the
typename to
be elided from a
splice-type-specifier in
non-dependent contexts.
4 A qualified or unqualified name is said to be in a
type-only contextif it is the terminal name of
- (4.1) a
typename-specifier,type-requirement,nested-name-specifier,elaborated-type-specifier,class-or-decltype,using-enum-declaratoror- (4.2) […]
- (4.4.6)
parameter-declarationof atemplate-parameter(which necessarily declares a constant template parameter).A
splice-specifierorsplice-specialization-specifier([basic.splice]) is said to be in a type-only context if a hypothetical qualified name appearing in the same position would be in a type-only context.[ Example 5:— end example ]template<class T> T::R f(); template<class T> void f(T::R); // ill-formed, no diagnostic required: attempt to // declare a `void` variable templateenum class Enum { A, B, C };template<class T> struct S { using Ptr = PtrTraits<T>::Ptr; // OK, in a defining-type-idusing Alias = [:^^int:]; // OK, in a defining-type-idT::R f(T::P p) { // OK, class scope return static_cast<T::R>(p); // OK, type-id of a `static_cast` } auto g() -> S<T*>::Ptr; // OK, trailing-return-typeauto h() -> [:^^S:]<T*>; // OK, trailing-return-typeusing enum [:^^Enum:]; // OK, using-enum-declarator}; template<typename T> void f() { void (*pf)(T::X); // variable `pf` of type `void*` initialized // with `T::X` void g(T::X); // error: `T::X` at block scope does not denote // a type (attempt to declare a `void` variable) }
Account for dependent
splice-type-specifiers in
paragraph 10:
10 A type is dependent if it is
Add to the list of never-type-dependent expression forms in paragraph 4:
literal sizeof unary-expression sizeof ( type-id ) sizeof ... ( identifier ) alignof ( type-id ) typeid ( expression ) typeid ( type-id ) ::opt delete cast-expression ::opt delete [ ] cast-expression throw assignment-expressionopt noexcept ( expression ) requires-expression + reflect-expression
Add a new paragraph at the end of 13.8.3.3 [temp.dep.expr]:
9 A
primary-expressionof the formsplice-specifierortemplate splice-specialization-specifieris type-dependent if itssplice-specifierorsplice-specialization-specifieris dependent ([temp.dep.splice]).
Add at the end of 13.8.3.4 [temp.dep.constexpr]/2 (before the note):
2 An id-expression is value-dependent if:
- (2.1) […]
Expressions of the following form are value-dependent if the unary-expression or expression is type-dependent or the type-id is dependent:
sizeof unary-expression sizeof ( type-id ) typeid ( expression ) typeid ( type-id ) alignof ( type-id ) noexcept ( expression )A
reflect-expressionis value-dependent if it contains a dependentnested-name-specifier,type-id,namespace-name, ortemplate-name, if it names a dependent member of the current instantiation ([temp.dep.type]), or if it contains a value-dependent or type-dependentid-expression.
Add a new paragraph after 13.8.3.4 [temp.dep.constexpr]/6:
6+ A
primary-expressionof the formsplice-specifierortemplate splice-specialization-specifieris value-dependent if itssplice-specifierorsplice-specialization-specifieris dependent ([temp.dep.splice]).
Add a new subsection of 13.8.3 [temp.dep] following 13.8.3.4 [temp.dep.constexpr], and renumber accordingly.
Dependent splice specifiers [temp.dep.splice]
1 A
splice-specifieris dependent if its convertedconstant-expressionis value-dependent. Asplice-specialization-specifieris dependent if itssplice-specifieris dependent or if any of its arguments are dependent. Asplice-scope-specifieris dependent if itssplice-specifierorsplice-specialization-specifieris dependent.[ Example 1:— end example ]template <auto T, auto NS> void fn() { using a = [:T:]<1>; // [:T:] and [:T:]<1> are dependent static_assert([:NS:]::template TCls<1>::v == a::v); // [:NS:] is dependent } namespace NS { template <auto V> struct TCls { static constexpr int v = V; }; } int main() { fn<^^NS::TCls, ^^NS>(); }[ Example 2:— end example ]template<template<class> class X> struct S { typename [: ^^X :]<int, float> m; }; template<class> struct V1 {}; template<class, class = int> struct V2 {}; S<V1> s1; // error: V1<int, float> has too many template arguments S<V2> s2; // OK
Add a new paragraph to cover dependent splice template arguments.
5 A template
template-parameteris dependent if it names atemplate-parameteror if its terminal name is dependent.5+ A splice template argument is dependent if its
splice-specifieris dependent.
Add a new section to cover dependent namespace aliases.
Dependent namespaces [temp.dep.namespace]
1 A namespace alias is dependent if it is introduced by a
namespace-alias-definitionwhosenested-name-specifierorsplice-specifieris dependent. Anamespace-nameis dependent if it names a dependent namespace alias.[ Example 1:— end example ]template <std::meta::info R> int fn() { namespace Alias = [:R:]; // [:R:] is dependent return Alias::v; // Alias is dependent } namespace NS { int v = 1; } int a = fn<^^NS>();
Modify paragraph 9 to allow
splice-specialization-specifiers
to be used like incompletely-defined classes.
9 A
simple-template-idorsplice-specialization-specifierthatnamesdesignates a class template explicit specialization that has been declared but not defined can be used exactly like the names of other incompletely-defined classes (6.8 [basic.types]).
Cover
splice-specialization-specifiers
in paragraph 2:
2 When an explicit template argument list is specified, if the given
template-idorsplice-specialization-specifieris not valid (13.3 [temp.names]), type deduction fails. Otherwise, the specified template argument values are substituted for the corresponding template parameters as specified below.
Modify paragraph 4.3 to treat parameter types of function templates
that are specified using
splice-specialization-specifiers
the same as parameter types that are specified using
simple-template-ids.
- (4.3) If
Pis a class andPhas the formsimple-template-idortypenameopt splice-specialization-specifier, then the transformedAcan be a derived classDof the deducedA. Likewise, ifPis a pointer to a class of the formsimple-template-idortypenameopt splice-specialization-specifier, the transformedAcan be a pointer to a derived classDpointed to by the deducedA. However, if there is a classCthat is a (direct or indirect) base class ofDand derived (directly or indirectly) from a classBand that would be a valid deducedA, the deducedAcannot beB1or pointer toB, respectively.
Add the operand of a
splice-specifier to the
list of non-deduced contexts in paragraph 5:
5 The non-deduced contexts are:
- (5.1) The
nested-name-specifierof a type that was specified using aqualified-id.- (5.2) A
pack-index-specifieror apack-index-expression.- (5.3) The
expressionof adecltype-specifier.- (5.3+) The
constant-expressionof asplice-specifier.- (5.4) A constant template argument or an array bound in which a subexpression references a template parameter.
- (5.5) …
Modify paragraph 20 to clarify that the construct enclosing a
template argument might also be a
splice-specialization-specifier.
20 If
Phas a form that contains<i>, and if the type ofidiffers from the type of the corresponding template parameter of the template named by the enclosingsimple-template-idorsplice-specialization-specifier, deduction fails. IfPhas a form that contains[i], and if the type ofiis not an integral type, deduction fails.123 IfPhas a form that includesnoexcept(i)and the type ofiis notbool, deduction fails.
Extend paragraph 10 to clarify that
splice-specifiers may not
appear in preprocessor directives, while also applying a “drive-by fix”
to disallow lambdas in the same context.
10 Preprocessing directives of the forms
# if constant-expression new-line groupopt # elif constant-expression new-line groupoptcheck whether the controlling constant expression evaluates to nonzero. The program is ill-formed if a
splice-specifierorlambda-expressionappears in the controlling constant expression.
For convenience, we’re going to add a new library element to 16.3.2.4 [structure.specifications]/3:
3 Descriptions of function semantics contain the following elements (as appropriate):
(3.1) Constraints: […]
(3.2) Mandates: the conditions that, if not met, render the program ill-formed. […]
- (3.2+1) Constant When: the conditions that are required for a call to the function to be a constant subexpression ([defns.const.subexpr]).
4 […] Next, the semantics of the code sequence are determined by the Constraints, Mandates, Constant When, Preconditions, Effects, Synchronization, Postconditions, Returns, Throws, Complexity, Remarks, and Error conditions specified for the function invocations contained in the code sequence. […]
Add <meta>
to [tab:headers.cpp].
<algorithm><forward_list><meta><stack><any><fstream><mutex><stacktrace><array><functional><new><stdexcept><atomic><future><numbers><stdfloat><barrier><generator><numeric><stop_token><bit><hazard_pointer><optional><streambuf><bitset><initializer_list><ostream><string><charconv><inplace_vector><print><string_view><chrono><iomanip><queue><syncstream><compare><ios><random><system_error><complex><iosfwd><ranges><text_encoding><concepts><iostream><ratio><thread><condition_variable><istream><rcu><tuple><coroutine><iterator><regex><type_traits><debugging><latch><scoped_allocator><typeindex><deque><limits><semaphore><typeinfo><exception><linalg><set><unordered_map><execution><list><shared_mutex><unordered_set><expected><locale><simd><utility><filesystem><map><source_location><valarray><flat_map><mdspan><span><variant><flat_set><memory><spanstream><vector><format><memory_resource><sstream><version>
Insert before paragraph 7:
6 Let F denote a standard library function ([global.functions]), a standard library static member function, or an instantiation of a standard library function template. Unless F is designated an addressable function, the behavior of a C++ program is unspecified (possibly ill-formed) if it explicitly or implicitly attempts to form a pointer to F. […]
6a Let F denote a standard library function or function template. Unless F is designated an addressable function, it is unspecified if or how a reflection value designating the associated entity can be formed. [ Note 1: For example, it is possible that
std::meta::members_ofwill not return reflections of standard library functions that an implementation handles through an extra-linguistic mechanism. — end note ]6b Let
Cdenote a standard library class or class template specialization. It is unspecified if or how a reflection value can be formed to any private member ofC, or what the names of such members may be.7 A translation unit shall not declare namespace std to be an inline namespace ([namespace.def]).
<type_traits>
synopsisAdd a new primary type category type trait:
Header
<type_traits>synopsis…
// [meta.unary.cat], primary type categories template<class T> struct is_void; ... template<class T> struct is_function; + template<class T> struct is_reflection; ... // [meta.unary.prop], type properties template<class T> struct is_const; ... template<class T> struct is_aggregate; + template<class T> struct is_consteval_only; ... // [meta.unary.cat], primary type categories template<class T> constexpr bool is_void_v = is_void<T>::value; ... template<class T> constexpr bool is_function_v = is_function<T>::value; + template<class T> + constexpr bool is_reflection_v = is_reflection<T>::value; ... // [meta.unary.prop], type properties template<class T> constexpr bool is_const_v = is_const<T>::value; ... template<class T> constexpr bool is_aggregate_v = is_aggregate<T>::value; + template<class T> + constexpr bool is_consteval_only_v = is_consteval_only<T>::value; template<class T> constexpr bool is_signed_v = is_signed<T>::value; ...
Add the is_reflection primary
type category to the table in paragraph 3:
Template Condition Comments template<class T> struct is_void;Tisvoid… … … template<class T> struct is_reflection;
Tisstd::meta::info
Add the is_consteval_only type
trait to table 51 following paragraph 5:
Template Condition Preconditions template<class T> struct is_const;Tis const-qualified ([basic.type.qualifier])… … … template<class T> struct is_consteval_only;
Tis consteval-only ([basic.types.general])
<meta>
synopsisAdd a new subsection in 21 [meta] after 21.3 [type.traits]:
Header
<meta>synopsis#include <initializer_list> namespace std::meta { using info = decltype(^^::); // [meta.reflection.operators], operator representations enum class operators { see below; }; using enum operators; consteval operators operator_of(info r); consteval string_view symbol_of(operators op); consteval u8string_view u8symbol_of(operators op); // [meta.reflection.names], reflection names and locations consteval bool has_identifier(info r); consteval string_view identifier_of(info r); consteval u8string_view u8identifier_of(info r); consteval string_view display_string_of(info r); consteval u8string_view u8display_string_of(info r); consteval source_location source_location_of(info r); // [meta.reflection.queries], reflection queries consteval info type_of(info r); consteval info object_of(info r); consteval info constant_of(info r); consteval bool is_public(info r); consteval bool is_protected(info r); consteval bool is_private(info r); consteval bool is_virtual(info r); consteval bool is_pure_virtual(info r); consteval bool is_override(info r); consteval bool is_final(info r); consteval bool is_deleted(info r); consteval bool is_defaulted(info r); consteval bool is_user_provided(info r); consteval bool is_user_declared(info r); consteval bool is_explicit(info r); consteval bool is_noexcept(info r); consteval bool is_bit_field(info r); consteval bool is_enumerator(info r); consteval bool is_const(info r); consteval bool is_volatile(info r); consteval bool is_mutable_member(info r); consteval bool is_lvalue_reference_qualified(info r); consteval bool is_rvalue_reference_qualified(info r); consteval bool has_static_storage_duration(info r); consteval bool has_thread_storage_duration(info r); consteval bool has_automatic_storage_duration(info r); consteval bool has_internal_linkage(info r); consteval bool has_module_linkage(info r); consteval bool has_external_linkage(info r); consteval bool has_c_language_linkage(info r); consteval bool has_linkage(info r); consteval bool is_complete_type(info r); consteval bool is_enumerable_type(info r); consteval bool is_variable(info r); consteval bool is_type(info r); consteval bool is_namespace(info r); consteval bool is_type_alias(info r); consteval bool is_namespace_alias(info r); consteval bool is_function(info r); consteval bool is_conversion_function(info r); consteval bool is_operator_function(info r); consteval bool is_literal_operator(info r); consteval bool is_special_member_function(info r); consteval bool is_constructor(info r); consteval bool is_default_constructor(info r); consteval bool is_copy_constructor(info r); consteval bool is_move_constructor(info r); consteval bool is_assignment(info r); consteval bool is_copy_assignment(info r); consteval bool is_move_assignment(info r); consteval bool is_destructor(info r); consteval bool is_template(info r); consteval bool is_function_template(info r); consteval bool is_variable_template(info r); consteval bool is_class_template(info r); consteval bool is_alias_template(info r); consteval bool is_conversion_function_template(info r); consteval bool is_operator_function_template(info r); consteval bool is_literal_operator_template(info r); consteval bool is_constructor_template(info r); consteval bool is_concept(info r); consteval bool is_value(info r); consteval bool is_object(info r); consteval bool is_structured_binding(info r); consteval bool is_class_member(info r); consteval bool is_namespace_member(info r); consteval bool is_nonstatic_data_member(info r); consteval bool is_static_member(info r); consteval bool is_base(info r); consteval bool has_default_member_initializer(info r); consteval bool has_parent(info r); consteval info parent_of(info r); consteval info dealias(info r); consteval bool has_template_arguments(info r); consteval info template_of(info r); consteval vector<info> template_arguments_of(info r); // [meta.reflection.access.context], access control context struct access_context; // [meta.reflection.access.queries], member accessessibility queries consteval bool is_accessible(info r, access_context ctx); consteval bool has_inaccessible_nonstatic_data_members( info r, access_context ctx); consteval bool has_inaccessible_bases(info r, access_context ctx); // [meta.reflection.member.queries], reflection member queries consteval vector<info> members_of(info r, access_context ctx); consteval vector<info> bases_of(info type, access_context ctx); consteval vector<info> static_data_members_of(info type, access_context ctx); consteval vector<info> nonstatic_data_members_of(info type, access_context ctx); consteval vector<info> enumerators_of(info type_enum); // [meta.reflection.layout], reflection layout queries struct member_offset; consteval member_offset offset_of(info r); consteval size_t size_of(info r); consteval size_t alignment_of(info r); consteval size_t bit_size_of(info r); // [meta.reflection.extract], value extraction template<class T> consteval T extract(info); // [meta.reflection.substitute], reflection substitution template <class R> concept reflection_range = see below; template <reflection_range R = initializer_list<info>> consteval bool can_substitute(info templ, R&& arguments); template <reflection_range R = initializer_list<info>> consteval info substitute(info templ, R&& arguments); // [meta.reflection.result], expression result reflection template<class T> consteval info reflect_constant(const T& value); template<class T> consteval info reflect_object(T& object); template<class T> consteval info reflect_function(T& fn); // [meta.reflection.define.aggregate], class definition generation struct data_member_options; consteval info data_member_spec(info type, data_member_options options); consteval bool is_data_member_spec(info r); template <reflection_range R = initializer_list<info>> consteval info define_aggregate(info type_class, R&&); // associated with [meta.unary.cat], primary type categories consteval bool is_void_type(info type); consteval bool is_null_pointer_type(info type); consteval bool is_integral_type(info type); consteval bool is_floating_point_type(info type); consteval bool is_array_type(info type); consteval bool is_pointer_type(info type); consteval bool is_lvalue_reference_type(info type); consteval bool is_rvalue_reference_type(info type); consteval bool is_member_object_pointer_type(info type); consteval bool is_member_function_pointer_type(info type); consteval bool is_enum_type(info type); consteval bool is_union_type(info type); consteval bool is_class_type(info type); consteval bool is_function_type(info type); consteval bool is_reflection_type(info type); // associated with [meta.unary.comp], composite type categories consteval bool is_reference_type(info type); consteval bool is_arithmetic_type(info type); consteval bool is_fundamental_type(info type); consteval bool is_object_type(info type); consteval bool is_scalar_type(info type); consteval bool is_compound_type(info type); consteval bool is_member_pointer_type(info type); // associated with [meta.unary.prop], type properties consteval bool is_const_type(info type); consteval bool is_volatile_type(info type); consteval bool is_trivially_copyable_type(info type); consteval bool is_trivially_relocatable_type(info type); consteval bool is_replaceable_type(info type); consteval bool is_standard_layout_type(info type); consteval bool is_empty_type(info type); consteval bool is_polymorphic_type(info type); consteval bool is_abstract_type(info type); consteval bool is_final_type(info type); consteval bool is_aggregate_type(info type); consteval bool is_consteval_only_type(info type); consteval bool is_signed_type(info type); consteval bool is_unsigned_type(info type); consteval bool is_bounded_array_type(info type); consteval bool is_unbounded_array_type(info type); consteval bool is_scoped_enum_type(info type); template <reflection_range R = initializer_list<info>> consteval bool is_constructible_type(info type, R&& type_args); consteval bool is_default_constructible_type(info type); consteval bool is_copy_constructible_type(info type); consteval bool is_move_constructible_type(info type); consteval bool is_assignable_type(info type_dst, info type_src); consteval bool is_copy_assignable_type(info type); consteval bool is_move_assignable_type(info type); consteval bool is_swappable_with_type(info type_dst, info type_src); consteval bool is_swappable_type(info type); consteval bool is_destructible_type(info type); template <reflection_range R = initializer_list<info>> consteval bool is_trivially_constructible_type(info type, R&& type_args); consteval bool is_trivially_default_constructible_type(info type); consteval bool is_trivially_copy_constructible_type(info type); consteval bool is_trivially_move_constructible_type(info type); consteval bool is_trivially_assignable_type(info type_dst, info type_src); consteval bool is_trivially_copy_assignable_type(info type); consteval bool is_trivially_move_assignable_type(info type); consteval bool is_trivially_destructible_type(info type); template <reflection_range R = initializer_list<info>> consteval bool is_nothrow_constructible_type(info type, R&& type_args); consteval bool is_nothrow_default_constructible_type(info type); consteval bool is_nothrow_copy_constructible_type(info type); consteval bool is_nothrow_move_constructible_type(info type); consteval bool is_nothrow_assignable_type(info type_dst, info type_src); consteval bool is_nothrow_copy_assignable_type(info type); consteval bool is_nothrow_move_assignable_type(info type); consteval bool is_nothrow_swappable_with_type(info type_dst, info type_src); consteval bool is_nothrow_swappable_type(info type); consteval bool is_nothrow_destructible_type(info type); consteval bool is_nothrow_relocatable_type(info type); consteval bool is_implicit_lifetime_type(info type); consteval bool has_virtual_destructor(info type); consteval bool has_unique_object_representations(info type); consteval bool reference_constructs_from_temporary(info type_dst, info type_src); consteval bool reference_converts_from_temporary(info type_dst, info type_src); // associated with [meta.unary.prop.query], type property queries consteval size_t rank(info type); consteval size_t extent(info type, unsigned i = 0); // associated with [meta.rel], type relations consteval bool is_same_type(info type1, info type2); consteval bool is_base_of_type(info type_base, info type_derived); consteval bool is_virtual_base_of_type(info type_base, info type_derived); consteval bool is_convertible_type(info type_src, info type_dst); consteval bool is_nothrow_convertible_type(info type_src, info type_dst); consteval bool is_layout_compatible_type(info type1, info type2); consteval bool is_pointer_interconvertible_base_of_type(info type_base, info type_derived); template <reflection_range R = initializer_list<info>> consteval bool is_invocable_type(info type, R&& type_args); template <reflection_range R = initializer_list<info>> consteval bool is_invocable_r_type(info type_result, info type, R&& type_args); template <reflection_range R = initializer_list<info>> consteval bool is_nothrow_invocable_type(info type, R&& type_args); template <reflection_range R = initializer_list<info>> consteval bool is_nothrow_invocable_r_type(info type_result, info type, R&& type_args); // associated with [meta.trans.cv], const-volatile modifications consteval info remove_const(info type); consteval info remove_volatile(info type); consteval info remove_cv(info type); consteval info add_const(info type); consteval info add_volatile(info type); consteval info add_cv(info type); // associated with [meta.trans.ref], reference modifications consteval info remove_reference(info type); consteval info add_lvalue_reference(info type); consteval info add_rvalue_reference(info type); // associated with [meta.trans.sign], sign modifications consteval info make_signed(info type); consteval info make_unsigned(info type); // associated with [meta.trans.arr], array modifications consteval info remove_extent(info type); consteval info remove_all_extents(info type); // associated with [meta.trans.ptr], pointer modifications consteval info remove_pointer(info type); consteval info add_pointer(info type); // associated with [meta.trans.other], other transformations consteval info remove_cvref(info type); consteval info decay(info type); template <reflection_range R = initializer_list<info>> consteval info common_type(R&& type_args); template <reflection_range R = initializer_list<info>> consteval info common_reference(R&& type_args); consteval info type_underlying_type(info type); template <reflection_range R = initializer_list<info>> consteval info invoke_result(info type, R&& type_args); consteval info unwrap_reference(info type); consteval info unwrap_ref_decay(info type); consteval size_t tuple_size(info type); consteval info tuple_element(size_t index, info type); consteval size_t variant_size(info type); consteval info variant_alternative(size_t index, info type); consteval strong_ordering type_order(info type_a, info type_b); }1 Unless otherwise specified, each function, and each instantiation of any function template, specified in this header is a designated addressable function ([namespace.std]).
2 The behavior of any function specified in namespace
std::metais implementation-defined when a reflection of a construct not otherwise specified by this document is provided as an argument.[ Note 1: Values of type
std::meta::infocan represent implementation-specific constructs (6.8.2 [basic.fundamental]). — end note ][ Note 2: The behavior of many of the functions specified in namespace
std::metahave semantics that can be affected by the completeness of class types represented by reflection values. For such functions, for any reflectionrsuch thatdealias(r)represents a specialization of a templated class with a reachable definition, the specialization is implicitly instantiated ([temp.inst]).[ Example 1:— end note ]— end example ]template <class T> struct X { T mem; }; static_assert(size_of(^^X<int>) == sizeof(int)); // instantiates X<int>3 Any function in namespace
std::metawhose return type isstring_vieworu8string_viewreturns an objectVsuch thatV.data()[V.size()]equals'\0'.[ Example 2:struct C { }; constexpr string_view sv = identifier_of(^^C); static_assert(sv == "C"); static_assert(sv.data()[0] == 'C'); static_assert(sv.data()[1] == '\0');4 Throughout this clause, variables are introduced to designate various source constructs. For the purpose of exposition,
^^Eis used to a mean a reflection representingEwhenEis such an exposition-only variable. — end example ]
enum class operators { see below; }; using enum operators;1 This enum class specifies constants used to identify operators that can be overloaded, with the meanings listed in Table 1. The values of the constants are distinct.
[ Drafting note: The names here are chosen after the punctuation marks, not the semantic operation, and we are sticking with the Unicode names — or resorting to the secondary name when the primary name is not well known (e.g.
solidus->slash) ]Table 1: Enum class operators[meta.reflection.operators]
Constant Correspondingoperator-function-id Operator symbol nameop_newoperator newnewop_deleteoperator deletedeleteop_array_newoperator new[]new[]op_array_deleteoperator delete[]delete[]op_co_awaitoperator co_awaitco_awaitop_parenthesesoperator()()op_square_bracketsoperator[][]op_arrowoperator->->op_arrow_staroperator->*->*op_tildeoperator~~op_exclamationoperator!!op_plusoperator++op_minusoperator--op_staroperator**op_slashoperator//op_percentoperator%%op_caretoperator^^op_ampersandoperator&&op_pipeoperator||op_equalsoperator==op_plus_equalsoperator+=+=op_minus_equalsoperator-=-=op_star_equalsoperator*=*=op_slash_equalsoperator/=/=op_percent_equalsoperator%=%=op_caret_equalsoperator^=^=op_ampersand_equalsoperator&=&=op_pipe_equalsoperator|=|=op_equals_equalsoperator====op_exclamation_equalsoperator!=!=op_lessoperator<<op_greateroperator>>op_less_equalsoperator<=<=op_greater_equalsoperator>=>=op_spaceshipoperator<=><=>op_ampersand_ampersandoperator&&&&op_pipe_pipeoperator||||op_less_lessoperator<<<<op_greater_greateroperator>>>>op_less_less_equalsoperator<<=<<=op_greater_greater_equalsoperator>>=>>=op_plus_plusoperator++++op_minus_minusoperator----op_commaoperator,,consteval operators operator_of(info r);2 Constant When:
rrepresents an operator function or operator function template.3 Returns: The value of the enumerator from
operatorswhose correspondingoperator-function-idis the unqualified name of the entity represented byr.consteval string_view symbol_of(operators op); consteval u8string_view u8symbol_of(operators op);4 Constant When: The value of
opcorresponds to one of the enumerators inoperators.5 Returns:
string_vieworu8string_viewcontaining the characters of the operator symbol name corresponding toop, respectively encoded with the ordinary literal encoding or with UTF-8.
consteval bool has_identifier(info r);1 Returns:
- (1.1) If
rrepresents an entity that has a typedef name for linkage purposes ([dcl.typedef]), thentrue.- (1.2) Otherwise, if
rrepresents an unnamed entity, thenfalse.- (1.3) Otherwise, if
rrepresents a class type, then!has_template_arguments(r).- (1.4) Otherwise, if
rrepresents a function, thentrueif!has_template_arguments(r)and the function is not a constructor, destructor, operator function, or conversion function. Otherwise,false.- (1.5) Otherwise, if
rrepresents a template, thentrueifrdoes not represent a constructor template, operator function template, or conversion function template. Otherwise,false.- (1.6) Otherwise, if
rrepresents a variable, thenfalseif the declaration of that variable was instantiated from a function parameter pack. Otherwise,!has_template_arguments(r).- (1.7) Otherwise, if
rrepresents a structured binding, thenfalseif the declaration of that structured binding was instantiated from a structured binding pack. Otherwise,true.- (1.8) Otherwise, if
rrepresents a type alias, then!has_template_arguments(r).- (1.9) Otherwise, if
rrepresents an enumerator, non-static data member, namespace, or namespace alias, thentrue.- (1.10) Otherwise, if
rrepresents a direct base class relationship, thenhas_identifier(type_of(r)).- (1.11) Otherwise,
rrepresents a data member description (T,N,A,W,NUA) (11.4.1 [class.mem.general]);trueifNis not ⊥. Otherwise,false.consteval string_view identifier_of(info r); consteval u8string_view u8identifier_of(info r);2 Let E be UTF-8 if returning a
u8string_view, and otherwise the ordinary literal encoding.3 Constant When:
has_identifier(r)istrueand the identifier that would be returned (see below) is representable byE.4 Returns: An NTMBS, encoded with
E, determined as follows:
- (4.1) If
rrepresents an entity with a typedef name for linkage purposes, then that name.- (4.2) Otherwise, if
rrepresents a literal operator or literal operator template, then theud-suffixof the operator or operator template.- (4.3) Otherwise, if
rrepresents an entity, then the identifier introduced by the declaration of that entity.- (4.4) Otherwise, if
rrepresents a direct base class relationship, thenidentifier_of(type_of(r))oru8identifier_of(type_of(r)), respectively.- (4.5) Otherwise,
rrepresents a data member description (T,N,A,W,NUA) (11.4.1 [class.mem.general]); astring_vieworu8string_view, respectively, containing the identifierN.consteval string_view display_string_of(info r); consteval u8string_view u8display_string_of(info r);5 Returns: An implementation-defined
string_vieworu8string_view, respectively.6 Recommended practice: Where possible, implementations should return a string suitable for identifying the represented construct.
consteval source_location source_location_of(info r);7 Returns: If
rrepresents a value, a type other than a class type or an enumeration type, the global namespace, or a data member description, thensource_location{}. Otherwise, an implementation-definedsource_locationvalue.8 Recommended practice: If
rrepresents an entity with a definition that is reachable from the evaluation context, a value corresponding to a definition should be returned.
consteval bool has-type(info r); // exposition only1 Returns:
trueifrrepresents a value, object, variable, function that is not a constructor or destructor, enumerator, non-static data member, unnamed bit-field, direct base class relationship, or data member description. Otherwise,false.consteval info type_of(info r);2 Constant When:
has-type(r)istrue.3 Returns:
- (3.1) If
rrepresents a value, object, variable, function, non-static data member, or unnamed bit-field, then the type of what is represented byr.- (3.2) Otherwise, if
rrepresents an enumeratorNof an enumerationE, then:
- (3.2.1) If
Eis defined by a declarationDthat is reachable from a pointPin the evaluation context andPdoes not occur within anenum-specifierofD, then a reflection ofE.- (3.2.2) Otherwise, a reflection of the type of
Nprior to the closing brace of theenum-specifieras specified in [dcl.enum].- (3.3) Otherwise, if
rrepresents a direct base class relationship, then a reflection of the type of the direct base class.- (3.4) Otherwise, for a data member description (
T,N,A,W,NUA) ([class.mem.general]), a reflection of the typeT.consteval info object_of(info r);4 Constant When:
ris a reflection representing either
- (4.1) an object with static storage duration ([basic.stc.general]), or
- (4.2) a variable that either declares or refers to such an object, and if that variable is a reference
Rthen either5 Returns:
- (5.1) If
rrepresents an object, thenr.- (5.2) Otherwise, if
rrepresents a reference, then a reflection of the object referred to by that reference.- (5.3) Otherwise (if
rrepresents any other variable), a reflection of the object declared by that variable.[ Example 1:— end example ]int x; int& y = x; static_assert(^^x != ^^y); // OK, x and y are different variables so their // reflections compare different static_assert(object_of(^^x) == object_of(^^y)); // OK, because y is a reference // to x, their underlying objects are the sameconsteval info constant_of(info r);6 Let
Rbe a constant expression of typeinfosuch thatR == ristrue.7 Constant When:
[: R :]is a validsplice-expression([expr.prim.splice]).8 Effects: Equivalent to:
return reflect_constant([: R :]);[ Example 2:— end example ]constexpr int x = 0; constexpr int y = 0; static_assert(^^x != ^^y); // OK, x and y are different variables so their // reflections compare different static_assert(constant_of(^^x) == constant_of(^^y)); // OK, both constant_of(^^x) and constant_of(^^y) // represent the value 0 static_assert(constant_of(^^x) == reflect_constant(0)); // OK, likewise info fn() { constexpr int x = 42; return ^^x; } info r = constant_of(fn()); // error: x is outside its lifetimeconsteval bool is_public(info r); consteval bool is_protected(info r); consteval bool is_private(info r);9 Returns:
trueifrrepresents a class member or direct base class relationship that is public, protected, or private, respectively. Otherwise,false.consteval bool is_virtual(info r);10 Returns:
trueifrrepresents either a virtual member function or a direct base class relationship that is virtual. Otherwise,false.consteval bool is_pure_virtual(info r); consteval bool is_override(info r);11 Returns:
trueifrrepresents a member function that is pure virtual or overrides another member function, respectively. Otherwise,false.consteval bool is_final(info r);12 Returns:
trueifrrepresents a final class or a final member function. Otherwise,false.consteval bool is_deleted(info r); consteval bool is_defaulted(info r);13 Returns:
trueifrrepresents a function that is a deleted function ([dcl.fct.def.delete]) or defaulted function ([dcl.fct.def.default]), respectively. Otherwise,false.consteval bool is_user_provided(info r); consteval bool is_user_declared(info r);14 Returns:
trueifrrepresents a function that is user-provided or user-declared ([dcl.fct.def.default]), respectively. Otherwise,false.consteval bool is_explicit(info r);15 Returns:
trueifrrepresents a member function that is declared explicit. Otherwise,false. [ Note 1: Ifrrepresents a member function template that is declaredexplicit,is_explicit(r)is stillfalsebecause in general such queries for templates cannot be answered. — end note ]consteval bool is_noexcept(info r);16 Returns:
trueifrrepresents anoexceptfunction type or a function with a non-throwing exception specification ([except.spec]). Otherwise,false. [ Note 2: Ifrrepresents a function template that is declarednoexcept,is_noexcept(r)is stillfalsebecause in general such queries for templates cannot be answered. — end note ]consteval bool is_bit_field(info r);17 Returns:
trueifrrepresents a bit-field, or ifrrepresents a data member description (T,N,A,W,NUA) ([class.mem.general]) for whichWis not ⊥. Otherwise,false.consteval bool is_enumerator(info r);18 Returns:
trueifrrepresents an enumerator. Otherwise,false.consteval bool is_const(info r); consteval bool is_volatile(info r);19 Let
Tbetype_of(r)ifhas-type(r)istrue. Otherwise, letTbedealias(r).20 Returns:
trueifTrepresents a const or volatile type, respectively, or a const- or volatile-qualified function type, respectively. Otherwise,false.consteval bool is_mutable_member(info r);21 Returns:
trueifrrepresents amutablenon-static data member. Otherwise,false.consteval bool is_lvalue_reference_qualified(info r); consteval bool is_rvalue_reference_qualified(info r);22 Let
Tbetype_of(r)ifhas-type(r)istrue. Otherwise, letTbedealias(r).23 Returns:
trueifTrepresents a lvalue- or rvalue-reference qualified function type, respectively. Otherwise,false.consteval bool has_static_storage_duration(info r); consteval bool has_thread_storage_duration(info r); consteval bool has_automatic_storage_duration(info r);24 Returns:
trueifrrepresents an object or variable that has static, thread, or automatic storage duration, respectively ([basic.stc]). Otherwise,false. [ Note 3: It is not possible to have a reflection representing an object or variable having dynamic storage duration. — end note ]consteval bool has_internal_linkage(info r); consteval bool has_module_linkage(info r); consteval bool has_external_linkage(info r); consteval bool has_c_language_linkage(info r); consteval bool has_linkage(info r);25 Returns:
trueifrrepresents a variable, function, type, template, or namespace whose name has internal linkage, module linkage, external linkage, C language linkage, or any linkage, respectively ([basic.link]). Otherwise,false.consteval bool is_complete_type(info r);26 Returns:
trueifis_type(r)istrueand there is some point in the evaluation context from which the type represented bydealias(r)is not an incomplete type ([basic.types]). Otherwise,false.consteval bool is_enumerable_type(info r);27 A type
Tis enumerable from a pointPif either
- (27.1)
Tis a class type complete atPor- (27.2)
Tis an enumeration type defined by a declarationDsuch thatDis reachable fromPbutPdoes not occur within anenum-specifierofD([dcl.enum]).28 Returns:
trueifdealias(r)represents a type that is enumerable from some point in the evaluation context. Otherwise,false.[ Example 3:— end example ]class S; enum class E; static_assert(!is_enumerable_type(^^S)); static_assert(!is_enumerable_type(^^E)); class S { void mfn() { static_assert(is_enumerable_type(^^S)); } static_assert(!is_enumerable_type(^^S)); }; static_assert(is_enumerable_type(^^S)); enum class E { A = is_enumerable_type(^^E) ? 1 : 2 }; static_assert(is_enumerable_type(^^E)); static_assert(static_cast<int>(E::A) == 2);consteval bool is_variable(info r);29 Returns:
trueifrrepresents a variable. Otherwise,false.consteval bool is_type(info r); consteval bool is_namespace(info r);30 Returns:
trueifrrepresents an entity whose underlying entity is a type or namespace, respectively. Otherwise,false.consteval bool is_type_alias(info r); consteval bool is_namespace_alias(info r);31 Returns:
trueifrrepresents a type alias or namespace alias, respectively [ Note 4: A specialization of an alias template is a type alias — end note ]. Otherwise,false.consteval bool is_function(info r);32 Returns:
trueifrrepresents a function. Otherwise,false.consteval bool is_conversion_function(info r); consteval bool is_operator_function(info r); consteval bool is_literal_operator(info r);33 Returns:
trueifrrepresents a function that is a conversion function ([class.conv.fct]), operator function ([over.oper]), or literal operator ([over.literal]), respectively. Otherwise,false.consteval bool is_special_member_function(info r); consteval bool is_constructor(info r); consteval bool is_default_constructor(info r); consteval bool is_copy_constructor(info r); consteval bool is_move_constructor(info r); consteval bool is_assignment(info r); consteval bool is_copy_assignment(info r); consteval bool is_move_assignment(info r); consteval bool is_destructor(info r);34 Returns:
trueifrrepresents a function that is a special member function ([special]), a constructor, a default constructor, a copy constructor, a move constructor, an assignment operator, a copy assignment operator, a move assignment operator, or a destructor, respectively. Otherwise,false.consteval bool is_template(info r);35 Returns:
trueifrrepresents a function template, class template, variable template, alias template, or concept. Otherwise,false.36 [ Note 5: A template specialization is not a template.
is_template(^^std::vector)istruebutis_template(^^std::vector<int>)isfalse. — end note ]consteval bool is_function_template(info r); consteval bool is_variable_template(info r); consteval bool is_class_template(info r); consteval bool is_alias_template(info r); consteval bool is_conversion_function_template(info r); consteval bool is_operator_function_template(info r); consteval bool is_literal_operator_template(info r); consteval bool is_constructor_template(info r); consteval bool is_concept(info r);37 Returns:
trueifrrepresents a function template, variable template, class template, alias template, conversion function template, operator function template, literal operator template, constructor template, or concept respectively. Otherwise,false.consteval bool is_value(info r); consteval bool is_object(info r);38 Returns:
trueifrrepresents a value or object, respectively. Otherwise,false.consteval bool is_structured_binding(info r);39 Returns:
trueifrrepresents a structured binding. Otherwise,false.consteval bool is_class_member(info r); consteval bool is_namespace_member(info r); consteval bool is_nonstatic_data_member(info r); consteval bool is_static_member(info r); consteval bool is_base(info r);40 Returns:
trueifrrepresents a class member, namespace member, non-static data member, static member, or direct base class relationship, respectively. Otherwise,false.consteval bool has_default_member_initializer(info r);41 Returns:
trueifrrepresents a non-static data member that has a default member initializer. Otherwise,false.consteval bool has_parent(info r);42 Returns:
- (42.1) If
rrepresents the global namespace, thenfalse.- (42.2) Otherwise, if
rrepresents an entity that has C language linkage ([dcl.link]), thenfalse.- (42.3) Otherwise, if
rrepresents an entity that has a language linkage other than C++ language linkage, then an implementation-defined value.- (42.4) Otherwise, if
rrepresents a type that is neither a class nor enumeration type, thenfalse.- (42.5) Otherwise, if
rrepresents an entity or direct base class relationship, thentrue.- (42.6) Otherwise,
false.consteval info parent_of(info r);43 Constant When:
has_parent(r)istrue.44 Returns:
- (44.1) If
rrepresents a non-static data member that is a direct member of an anonymous union, or an unnamed bit-field declared within themember-specificationof such a union, then a reflection representing the innermost enclosing anonymous union.- (44.2) Otherwise, if
rrepresents an enumerator, then a reflection representing the corresponding enumeration type.- (44.3) Otherwise, if
rrepresents a direct base class relationship between a classDand a direct base classB, then a reflection representingD.- (44.4) Otherwise, let
Ebe the class, function, or namespace whose class scope, function parameter scope, or namespace scope is, respectively, the innermost such scope that either is, or encloses, the target scope of a declaration of what is represented byr.
- (44.5) If
Eis the function call operator of a closure type for aconsteval-block-declaration([dcl.pre]), thenparent_of(parent_of(^^E)). [ Note 6: In this case, the firstparent_ofwill be the closure type, so the secondparent_ofis necessary to give the parent of that closure type. — end note ]- (44.6) Otherwise,
^^E.[ Example 4:— end example ]struct I { }; struct F : I { union { int o; }; enum N { A } }; constexpr auto ctx = std::meta::access_context::current(); static_assert(parent_of(^^F) == ^^::); static_assert(parent_of(bases_of(^^F, ctx)[0]) == ^^F); static_assert(is_union_type(parent_of(^^F::o))); static_assert(parent_of(^^F::N) == ^^F); static_assert(parent_of(^^F::A) == ^^F::N);consteval info dealias(info r);45 Constant When:
rrepresents an entity.46 Returns: A reflection representing the underlying entity of what
rrepresents.[ Example 5:— end example ]using X = int; using Y = X; static_assert(dealias(^^int) == ^^int); static_assert(dealias(^^X) == ^^int); static_assert(dealias(^^Y) == ^^int);consteval bool has_template_arguments(info r);48 Returns:
trueifrrepresents a specialization of a function template, variable template, class template, or an alias template. Otherwise,false.consteval info template_of(info r);49 Constant When:
has_template_arguments(r)istrue.50 Returns: A reflection of the primary template of the specialization represented by
r.consteval vector<info> template_arguments_of(info r);51 Constant When:
has_template_arguments(r)istrue.52 Returns: A
vectorcontaining reflections of the template arguments of the template specialization represented byr, in the order they appear in the corresponding template argument list. For a given template argumentA, its corresponding reflectionRis determined as follows:
(52.1) If
Adenotes a type or a type alias, thenRis a reflection representing the underlying entity ofA. [ Note 7:Ralways represents a type, never a type alias. — end note ](52.2) Otherwise, if
Adenotes a class template, variable template, concept, or alias template, thenRis a reflection representingA.(52.3) Otherwise,
Ais a constant template argument ([temp.arg.nontype]). LetPbe the corresponding template parameter.[ Example 6:— end example ]template <class T, class U=T> struct Pair { }; template <class T> struct Pair<char, T> { }; template <class T> using PairPtr = Pair<T*>; static_assert(template_of(^^Pair<int>) == ^^Pair); static_assert(template_of(^^Pair<char, char>) == ^^Pair); static_assert(template_arguments_of(^^Pair<int>).size() == 2); static_assert(template_arguments_of(^^Pair<int>)[0] == ^^int); static_assert(template_of(^^PairPtr<int>) == ^^PairPtr); static_assert(template_arguments_of(^^PairPtr<int>).size() == 1); struct S { }; int i; template <int, int&, S, template <class> class> struct X { }; constexpr auto T = ^^X<1, i, S{}, PairPtr>; static_assert(is_value(template_arguments_of(T)[0])); static_assert(is_object(template_arguments_of(T)[1])); static_assert(is_object(template_arguments_of(T)[2])); static_assert(template_arguments_of(T)[3] == ^^PairPtr);
1 The
access_contextclass is a non-aggregate type that represents a namespace, class, or function from which queries pertaining to access rules may be performed, as well as the naming class ([class.access.base]), if any.2 An
access_contexthas an associated scope and naming class.struct access_context { access_context() = delete; consteval info scope() const; consteval info naming_class() const; static consteval access_context current() noexcept; static consteval access_context unprivileged() noexcept; static consteval access_context unchecked() noexcept; consteval access_context via(info cls) const; };3
access_contextis a structural type. Two valuesac1andac2of typeaccess_contextare template-argument-equivalent ([temp.type]) ifac1.scope()andac2.scope()are template-argument-equivalent andac1.naming_class()andac2.naming_class()are template-argument-equivalent.consteval info scope() const; consteval info naming_class() const;4 Returns: The
access_context’s associated scope and naming class, respectively.static consteval access_context current() noexcept;5
currentis not an addressable function ([namespace.std]).6 Given a program point
P, leteval-point(P)be the following program point:
- (6.1) If a potentially-evaluated subexpression ([intro.execution]) of a default member initializer
Ifor a member of a classC([class.mem.general]) appears atP, then a point determined as follows:
- (6.1.1) If an aggregate initialization is using
I,eval-point(Q), whereQis the point at which that aggregate initialization appears.- (6.1.2) Otherwise, if an initialization by an inherited constructor ([class.inhctor.init]) is using
I, a point whose immediate scope is the class scope corresponding toC.- (6.1.3) Otherwise, a point whose immediate scope is the function parameter scope corresponding to the constructor definition that is using
I.- (6.2) Otherwise, if a potentially-evaluated subexpression of a default argument ([dcl.fct.default]) appears at
P,eval-point(Q), whereQis the point at which the invocation of the function ([expr.call]) using that default argument appears.- (6.3) Otherwise, if the immediate scope of
Pis a function parameter scope introduced by a declarationD, andPappears either before the locus ofDor within the trailingrequires-clauseofD, a point whose immediate scope is the innermost scope enclosing the locus ofDthat is not a template parameter scope.- (6.4) Otherwise, if the immediate scope of
Pis a function parameter scope introduced by alambda-expressionLwhoselambda-introducerappears at pointQ, andPappears either within thetrailing-return-typeor the trailingrequires-clauseofL,eval-point(Q).- (6.5) Otherwise, if the immediate scope of
Pis a block scope and the innermost function parameter scope enclosingPis introduced by aconsteval-block-declaration([dcl.pre]), a point whose immediate scope is the scope inhabited by the outermostconsteval-block-declarationthat containsP.- (6.6) Otherwise,
P.7 Given a scope
S, letctx-scope(S)be the following scope:
- (7.1) If
Sis a class scope or a namespace scope,S.- (7.2) Otherwise, if
Sis a function parameter scope introduced by the declaration of a function,S.- (7.3) Otherwise, if
Sis a lambda scope introduced by alambda-expressionL, the function parameter scope corresponding to the call operator of the closure type forL.- (7.4) Otherwise,
ctx-scope(S')whereS'is the parent scope ofS.8 An invocation of
currentthat appears at a program pointPis value-dependent ([temp.dep.contexpr]) ifeval-point(P)is enclosed by a scope corresponding to a templated entity.9 Returns: An
access_contextwhose naming class is the null reflection and whose scope represents the function, class, or namespace whose corresponding function parameter scope, class scope, or namespace scope iseval-ctx(S), whereSis the immediate scope ofeval-point(P)andPis the point at which the invocation ofcurrentlexically appears.[ Example 1:— end example ]struct A { int a = 0; consteval A(int p) : a(p) {} }; struct B : A { using A::A; consteval B(int p, int q) : A(p * q) {} info s = access_context::current().scope(); }; struct Agg { consteval bool eq(info rhs = access_context::current().scope()) { return s == rhs; } info s = access_context::current().scope(); }; namespace NS { static_assert(Agg{}.s == access_context::current().scope()); // OK static_assert(Agg{}.eq()); // OK static_assert(B(1).s == ^^B); // OK static_assert(is_constructor(B{1, 2}.s) && parent_of(B{1, 2}.s) == ^^B); // OK auto fn() -> [:is_namespace(access_context::current().scope()) ? ^^int : ^^bool:]; static_assert(type_of(^^fn) == ^^auto()->int); // OK template <auto R> struct TCls { consteval bool fn() requires (is_type(access_context::current().scope())) { // OK, scope is 'TCls<R>'. return true; } }; static_assert(TCls<0>{}.fn()); // OK }static consteval access_context unprivileged() noexcept;10 Returns: An
access_contextwhose naming class is the null reflection and whose scope is the global namespace.static consteval access_context unchecked() noexcept;11 Returns: An
access_contextwhose naming class and scope are both the null reflection.consteval access_context via(info cls) const;12 Constant When:
clsis either the null reflection or a reflection of a complete class type.13 Returns: An
access_contextwhose scope isthis->scope()and whose naming class iscls.
consteval bool is_accessible(info r, access_context ctx);1 Let
PARENT-CLS(r)be:
- (1.1) If
parent_of(r)represents a classC, then the classC.- (1.2) Otherwise,
PARENT-CLS(parent_of(r)).2 Constant When:
- (2.1)
rdoes not represent a class member for whichPARENT-CLS(r)is an incomplete class and- (2.2)
rdoes not represent a direct base class relationship between a base class and an incomplete derived class.3 Let
DESIGNATING-CLS(r, ctx)be:4 Returns:
(4.1) If
rrepresents an unnamed bit-fieldF, thenis_accessible(rH, ctx)whererHrepresents a hypothetical non-static data member of the class represented byPARENT-CLS(r)with the same access asF. [ Note 1: Unnamed bit-fields are treated as class members for the purpose ofis_accessible. — end note ](4.2) Otherwise, if
rdoes not represent a class member or a direct base class relationship, thentrue.(4.3) Otherwise, if
rrepresents
- (4.3.1) a class member that is not a (possibly indirect or variant) member of
DESIGNATING-CLS(r, ctx)or- (4.3.2) a direct base class relationship such that
parent_of(r)does not representDESIGNATING-CLS(r, ctx)or a (direct or indirect) base class thereof,then
false.(4.4) Otherwise, if
ctx.scope()is the null reflection, thentrue.(4.5) Otherwise, letting
Pbe a program point whose immediate scope is the function parameter scope, class scope, or namespace scope corresponding to the function, class, or namespace represented byctx.scope():
- (4.5.1) If
rrepresents a direct base class relationship with base classB, thentrueif base classBofDESIGNATING-CLS(r, ctx)is accessible atP([class.access.base]); otherwise,false.- (4.5.2) Otherwise,
rrepresents a class memberM;trueifMwould be accessible atPwhen designated inDESIGNATING-CLS(r, ctx)([class.access.base]) if the effect of anyusing-declarations ([namespace.udecl]) were ignored. Otherwise,false.[ Note 2: The definitions of when a class member or base class is accessible from a point
Pdo not consider whether a declaration of that entity is reachable fromP. — end note ][ Example 1:— end example ]consteval access_context fn() { return access_context::current(); } class Cls { int mem; friend consteval access_context fn(); public: static constexpr auto r = ^^mem; }; static_assert(is_accessible(Cls::r, fn())); // OK static_assert(!is_accessible(Cls::r, access_context::current())); // OK static_assert(is_accessible(Cls::r, access_context::unchecked())); // OKconsteval bool has_inaccessible_nonstatic_data_members( info r, access_context ctx);5 Constant When:
- (5.1)
nonstatic_data_members_of(r, access_context::unchecked())is a constant subexpression and- (5.2)
rdoes not represent a closure type.6 Returns:
trueifis_accessible(R, ctx)isfalsefor anyRinnonstatic_data_members_of(r, access_context::unchecked()). Otherwise,false.consteval bool has_inaccessible_bases(info r, access_context ctx);7 Constant When:
bases_of(r, access_context::unchecked())is a constant subexpression.8 Returns:
trueifis_accessible(R, ctx)isfalsefor anyRinbases_of(r, access_context::unchecked()). Otherwise,false.
consteval vector<info> members_of(info r, access_context ctx);1 Constant When:
dealias(r)is a reflection representing either a class type that is complete from some point in the evaluation context or a namespace.2 A declaration
Dmembers-of-precedes a pointPifDprecedes eitherPor the point immediately following theclass-specifierof the outermost class for whichPis in a complete-class context.3 A declaration
Dof a memberMof a class or namespaceQisQ-members-of-eligible if
- (3.1)
Mis not a closure type ([expr.prim.lambda.closure]),- (3.2)
Mis not a specialization of a template ([temp.pre]),- (3.3) if
Qis a class that is not a closure type, thenMis a direct member ofQ([class.mem.general]) that is not a variant member of a nested anonymous union ofQ([class.union.anon]),- (3.4) if
Qis a namespace, thenDinhabits the namespace scope ofQ, and- (3.5) if
Qis a closure type, thenMis a function call operator or function call operator template.It is implementation-defined whether declarations of other members of a closure type
QareQ-members-of-eligible.4 A member
Mof a class or namespaceQisQ-members-of-representable from a pointPif aQ-members-of-eligible declaration ofMmembers-of-precedesPandMis
- (4.1) a class or enumeration type,
- (4.2) a type alias,
- (4.3) a primary class template, function template, primary variable template, alias template, or concept,
- (4.4) a variable or reference,
- (4.5) a function
Ffor which- (4.9) a non-static data member,
- (4.10) a namespace, or
- (4.11) a namespace alias.
[ Note 1: Examples of direct members that are not
Q-members-of-representable for any entityQinclude: unscoped enumerators ([enum]), partial specializations of templates ([temp.spec.partial]), and closure types ([expr.prim.lambda.closure]). — end note ]5 Returns: A
vectorcontaining reflections of all membersMof the entityQrepresented bydealias(r)for which
- (5.1)
MisQ-members-of-representable from some point in the evaluation context and- (5.2)
is_accessible(^^M, ctx)istrue.If
dealias(r)represents a classC, then thevectoralso contains reflections representing all unnamed bit-fieldsBwhose declarations inhabit the class scope corresponding toCfor whichis_accessible(^^B, ctx)istrue. Reflections of class members and unnamed bit-fields that are declared appear in the order in which they are declared. [ Note 2: Base classes are not members. Implicitly-declared special members appear after any user-declared members ([special]). — end note ][ Example 1:— end example ]class B {}; struct S : B { private: class I; public: int m; }; static_assert(members_of(^^S, access_context::current()).size() == 7); // 6 special members, 1 public member, does not include base static_assert(members_of(^^S, access_context::unchecked()).size() == 8); // all of the above, as well a reflection representing S::Iconsteval vector<info> bases_of(info type, access_context ctx);6 Constant When:
dealias(type)represents a class type that is complete from some point in the evaluation context.7 Returns: Let
Cbe the class represented bydealias(type). Avectorcontaining the reflections of all the direct base class relationshipsB, if any, ofCsuch thatis_accessible(^^B, ctx)istrue. The direct base class relationships appear in the order in which the corresponding base classes appear in thebase-specifier-listofC.consteval vector<info> static_data_members_of(info type, access_context ctx);8 Constant When:
dealias(type)represents a class type that is complete from some point in the evaluation context.9 Returns: A
vectorcontaining each elementeofmembers_of(type, ctx)such thatis_variable(e)istrue, preserving their order.consteval vector<info> nonstatic_data_members_of(info type, access_context ctx);10 Constant When:
dealias(type)represents a class type that is complete from some point in the evaluation context.11 Returns: A
vectorcontaining each elementeofmembers_of(type, ctx)such thatis_nonstatic_data_member(e)istrue, preserving their order.consteval vector<info> enumerators_of(info type_enum);12 Constant When:
dealias(type_enum)represents an enumeration type andis_enumerable_type(type_enum)istrue.13 Returns: A
vectorcontaining the reflections of each enumerator of the enumeration represented bydealias(type_enum), in the order in which they are declared.
struct member_offset { ptrdiff_t bytes; ptrdiff_t bits; constexpr ptrdiff_t total_bits() const; auto operator<=>(const member_offset&) const = default; }; constexpr ptrdiff_t member_offset::total_bits() const;1 Returns:
bytes * CHAR_BIT + bits.consteval member_offset offset_of(info r);2 Constant When:
rrepresents a non-static data member, unnamed bit-field, or direct base class relationship other than a virtual base class of an abstract class.3 Let
Vbe the offset in bits from the beginning of a complete object of typeparent_of(r)to the subobject associated with the entity represented byr.4 Returns:
{V / CHAR_BIT, V % CHAR_BIT}.consteval size_t size_of(info r);5 Constant When:
dealias(r)is a reflection of a type, object, value, variable of non-reference type, non-static data member that is not a bit-field, direct base class relationship, or data member description (T,N,A,W,NUA) ([class.mem.general]) whereWis not ⊥. Ifdealias(r)represents a type, thenis_complete_type(r)istrue.6 Returns: If
rrepresents a non-static data member of typeT, a data member description (T,N,A,W,NUA), ordealias(r)represents a typeT, thensizeof(LAT)whereLATis the layout-associated type ([class.member.general]) of a non-static data member of typeT. Otherwise,size_of(type_of(r)).[ Note 1: It is possible that while
sizeof(char) == size_of(^^char)thatsizeof(char&) != size_of(^^char&). Ifbrepresents a direct base class relationship of an empty base class, thensize_of(b) > 0. — end note ]consteval size_t alignment_of(info r);7 Constant When:
dealias(r)is a reflection of a type, object, variable of non-reference type, non-static data member that is not a bit-field, direct base class relationship, or data member description. Ifdealias(r)represents a type, thenis_complete_type(r)istrue.8 Returns:
- (8.1) If
dealias(r)represents a typeT, then the alignment requirement for the layout-associated type ([class.mem.general]) for a non-static data member of typeT.- (8.2) Otherwise, if
dealias(r)represents a variable or object, then the alignment requirement of the variable or object.- (8.3) Otherwise, if
rrepresents a direct base class relationship, thenalignment_of(type_of(r)).- (8.4) Otherwise, if
rrepresents a non-static data member, then the alignment requirement of the subobject associated with the represented entity within any object of typeparent_of(r).- (8.5) Otherwise,
rrepresents a data member description (TR,N,A,W,NUA) ([class.mem.general]). IfAis not ⊥, then the valueA. Otherwisealignof(T)where the corresponding subobject ofTRwould have typeT.consteval size_t bit_size_of(info r);9 Constant When:
dealias(r)is a reflection of a type, object, value, variable of non-reference type, non-static data member, unnamed bit-field, direct base class relationship, or data member description. Ifdealias(r)represents a typeT, there is a point within the evaluation context from whichTis not incomplete.10 Returns:
1 The
extractfunction template may be used to extract a value out of a reflection when its type is known.2 The following are defined for exposition only to aid in the specification of
extract:template <class T> consteval T extract-ref(info r); // exposition only3 [ Note 1:
Tis a reference type. — end note ]4 Constant When:
- (4.1)
rrepresents a variable or object of typeU,- (4.2)
is_convertible_v<remove_reference_t<U>(*)[], remove_reference_t<T>(*)[]>istrue, and [ Note 2: The intent is to allow only qualification conversions fromUtoT. — end note ]- (4.3) if
rrepresents a variable, then either that variable is usable in constant expressions or its lifetime began within the core constant expression currently under evaluation.5 Returns: If
rrepresents an objectO, then a reference toO. Otherwise, a reference to the object declared, or referred to, by the variable represented byr.template <class T> consteval T extract-member-or-function(info r); // exposition only6 Constant When:
- (6.1)
rrepresents a non-static data member with typeX, that is not a bit-field, that is a direct member of a classCandTisX C::*;- (6.2)
rrepresents an implicit object member function with typeForF noexceptthat is a direct member of a classCandTisF C::*; or- (6.3)
rrepresents a non-member function, static member function, or explicit object member function of function typeForF noexceptandTisF*.7 Returns:
- (7.1) If
Tis a pointer type, then a pointer value pointing to the function represented byr.- (7.2) Otherwise, a pointer-to-member value designating the non-static data member or function represented by
r.template <class T> consteval T extract-value(info r); // exposition only8 Let
Ube the type of the value or object thatrrepresents.9 Constant When:
- (9.1)
Uis a pointer type,TandUare similar types ([conv.qual]), andis_convertible_v<U, T>istrue,- (9.2)
Uis not a pointer type and the cv-unqualified types ofTandUare the same,- (9.3)
Uis an array type,Tis pointer type, and the valuerrepresents is convertible toT, or- (9.4)
Uis a closure type,Tis a function pointer type, and the value thatrrepresents is convertible toT.10 Returns:
static_cast<T>([:R:]), whereRis a constant expression of typeinfosuch thatR == ristrue.template <class T> consteval T extract(info r);11 Effects: Equivalent to:
if (is_reference_type(^^T)) { return extract-ref<T>(r); } else if (is_nonstatic_data_member(r) || is_function(r)) { return extract-member-or-function<T>(r); } else { return extract-value<T>(constant_of(r)); }
template <class R> concept reflection_range = ranges::input_range<R> && same_as<ranges::range_value_t<R>, info> && same_as<remove_cvref_t<ranges::range_reference_t<R>>, info>;template <reflection_range R = initializer_list<info>> consteval bool can_substitute(info templ, R&& arguments);1 Constant When:
templrepresents a template and every reflection inargumentsrepresents a construct usable as a template argument ([temp.arg]).2 Let
Zbe the template represented bytempland letArgs...be a sequence of prvalue constant expressions that compute the reflections held by the elements ofarguments.3 Returns:
trueifZ<[:Args:]...>is a validtemplate-id([temp.names]) that does not name a function whose type contains an undeduced placeholder type. Otherwise,false.4 [ Note 1: If forming
Z<[:Args:]...>leads to a failure outside of the immediate context, the program is ill-formed. — end note ]template <reflection_range R = initializer_list<info>> consteval info substitute(info templ, R&& arguments);5 Constant When:
can_substitute(templ, arguments)istrue.6 Let
Zbe the template represented bytempland letArgs...be a sequence of prvalue constant expressions that compute the reflections held by the elements ofarguments.7 Returns:
^^Z<[:Args:]...>.8 [ Note 2: If forming
Z<[:Args:]...>leads to a failure outside of the immediate context, the program is ill-formed. — end note ][ Example 1:— end example ]template <typename T> auto fn1(); static_assert(!can_substitute(^^fn1, {^^int})); // OK constexpr info r1 = substitute(^^fn1, {^^int}); // error: fn<int> contains an undeduced placeholder type template <typename T> auto fn2() { static_assert(^^T != ^^int); // static assertion failed during instantiation of fn<int> return 0; } constexpr bool r2 = can_substitute(^^fn2, {^^int}); // error: instantiation of body of fn<int> is needed to deduce return type[ Example 2:— end example ]consteval info to_integral_constant(unsigned i) { return substitute(^^integral_constant, {^^unsigned, reflect_constant(i)}); } constexpr info r = to_integral_constant(2); // OK, r represents the type integral_constant<unsigned, 2>
1 An object
Oof typeTis meta-reflectable if an lvalue expression denotingOis suitable for use as a constant template argument for a constant template parameter of typeT&([temp.arg.nontype]).2 The following are defined for exposition only to aid in the specification of
reflect_constant:template <typename T> consteval info reflect-constant-scalar(T expr); // exposition onlyLet
Vbe the value ofexpr.3 Constant When: If
Vis a pointer to an object, then that object is meta-reflectable.4 Returns: A reflection of a value of type
Twith the computed valueV.template <typename T> consteval info reflect-constant-class(T const& expr); // exposition only5 Mandates:
is_copy_constructible_v<T>istrueandTis structural ([temp.param]).6 Let
Vbe an invented variable that would be introduced by the declarationT V(expr);7 Constant When:
(7.1)
Vsatisfies the semantic constraints for the definition of a constexpr variable with static storage duration ([dcl.constexpr]) and(7.2) given the invented template
template <T P> struct TCls;the
template-idTCls<V>would be valid.8 Returns: A reflection of the template parameter object that is template-argument-equivalent to the object denoted by
V([temp.param]).template <typename T> consteval info reflect_constant(const T& expr);* Effects: Equivalent to:
if constexpr (is_class_type(^^T)) { return reflect-constant-class(expr); } else { return reflect-constant-scalar(expr); }[ Note 1: Array-to-pointer and function-to-function-pointer decay occur. — end note ]
[ Example 1:— end example ]template <auto D> struct A { }; struct N { int x; }; struct K { char const* p; }; constexpr auto r1 = reflect_constant(42); static_assert(is_value(r1)); static_assert(r1 == template_arguments_of(^^A<42>)[0]); constexpr auto r2 = reflect_constant(N{42}); static_assert(is_object(r2)); static_assert(r2 == template_arguments_of(^^A<N{42}>)[0]); constexpr auto r3 = reflect_constant(K{nullptr}); // ok constexpr auto r4 = reflect_constant(K{"ebab"}); // error: constituent pointer points to string literaltemplate <typename T> consteval info reflect_object(T& expr);9 Mandates:
Tis not a function type.10 Constant When:
exprdesignates a meta-reflectable object.11 Returns: A reflection of the object designated by
expr.template <typename T> consteval info reflect_function(T& fn);12 Mandates:
Tis a function type.13 Returns: A reflection of the function designated by
fn.
struct data_member_options { struct name-type { // exposition only template<class T> requires constructible_from<u8string, T> consteval name-type(T &&); template<class T> requires constructible_from<string, T> consteval name-type(T &&); private: variant<u8string, string> contents; // exposition only }; optional<name-type> name; optional<int> alignment; optional<int> bit_width; bool no_unique_address = false; };1 The classes
data_member_optionsanddata_member_options::name-typeare consteval-only types ([basic.types.general]), and are not structural types ([temp.param]).template <class T> requires constructible_from<u8string, T> consteval data_member_options::name-type(T&& value);2 Effects: Initializes
contentswithu8string(std::forward<T>(value)).template<class T> requires constructible_from<string, T> consteval data_member_options::name-type(T&& value);3 Effects: Initializes
contentswithstring(std::forward<T>(value)).[ Note 1: The class
name-typeallows the functiondata_member_specto accept an ordinary string literal (orstring_view,string, etc.) or a UTF-8 string literal (oru8string_view,u8string, etc.) equally well.[ Example 1:— end note ]— end example ]consteval void fn() { data_member_options o1 = {.name="ordinary_literal_encoding"}; data_member_options o2 = {.name=u8"utf8_encoding"}; }consteval info data_member_spec(info type, data_member_options options);4 Constant When:
- (4.1)
dealias(type)represents either an object type or a reference type;- (4.2) if
options.namecontains a value, then:[ Note 2: The name corresponds to the spelling of an identifier token after phase 6 of translation ([lex.phases]). Lexical constructs like
- (4.2.1)
holds_alternative<u8string>(options.name->contents)istrueandget<u8string>(options.name->contents)contains a valid identifier ([lex.name]) that is not a keyword ([lex.key]) when interpreted with UTF-8, or- (4.2.2)
holds_alternative<string>(options.name->contents)istrueandget<string>(options.name->contents)contains a valid identifier that is not a keyword when interpreted with the ordinary literal encoding;universal-character-names [lex.universal.char] are not processed and will cause evaluation to fail. For example,"\u03B1"is an invalid identifier and is not interpreted as"α". — end note ]- (4.3) if
options.namedoes not contain a value, thenoptions.bit_widthcontains a value;- (4.4) if
options.bit_widthcontains a valueV, then- (4.5) if
options.alignmentcontains a value, it is an alignment value ([basic.align]) not less thanalignment_of(type).5 Returns: A reflection of a data member description (
T,N,A,W,NUA) (11.4.1 [class.mem.general]) where
- (5.1)
Tis the type represented bydealias(type),- (5.2)
Nis either the identifier encoded byoptions.nameor ⊥ ifoptions.namedoes not contain a value,- (5.3)
Ais either the alignment value held byoptions.alignmentor ⊥ ifoptions.alignmentdoes not contain a value,- (5.4)
Wis either the value held byoptions.bit_widthor ⊥ ifoptions.bit_widthdoes not contain a value, and- (5.5)
NUAis the value held byoptions.no_unique_address.6 [ Note 3: The returned reflection value is primarily useful in conjunction with
define_aggregate; it can also be queried by certain other functions instd::meta(e.g.,type_of,identifier_of). — end note ]consteval bool is_data_member_spec(info r);7 Returns:
trueifrrepresents a data member description. Otherwise,false.template <reflection_range R = initializer_list<info>> consteval info define_aggregate(info class_type, R&& mdescrs);8 Let
Cbe the class represented byclass_typeandrKbe theKth reflection value inmdescrs. For everyrKinmdescrs, let (TK,NK,AK,WK,NUAK) be the corresponding data member description represented byrK.9 Constant When:
(9.1)
Cis incomplete from every point in the evaluation context; [ Note 4:Ccan be a class template specialization for which there is a reachable definition of the primary class template. In this case, the injected declaration is an explicit specialization. — end note ](9.2)
is_data_member_spec(rK)istruefor everyrK;(9.3)
is_complete_type(TK)istruefor everyrK; and(9.4) for every pair (
rK,rL) whereK < L, ifNKis not ⊥ andNLis not ⊥, then either:10 Effects: Produces an injected declaration
D([expr.const]) that definesCand has properties as follows:
(10.1) The target scope of
Dis the scope to whichCbelongs ([basic.scope.scope]).(10.2) The locus of
Dfollows immediately after the core constant expression currently under evaluation.(10.3) If
Cis a specialization of a templated classT, andCis not a local class, thenDis an explicit specialization ofT.(10.4) For each
rK, there is a corresponding entityMKbelonging to the class scope ofDwith the following properties:
- (10.4.1) If
NKis ⊥,MKis an unnamed bit-field. Otherwise,MKis a non-static data member whose name is the identifier determined by the character sequence encoded byNKin UTF-8.- (10.4.2) The type of
MKisTK.- (10.4.3)
MKis declared with the attribute[[no_unique_address]]if and only ifNUAKistrue.- (10.4.4) If
WKis not ⊥,MKis a bit-field whose width is that value. Otherwise,MKis not a bit-field.- (10.4.5) If
AKis not ⊥,MKhas thealignment-specifieralignas(AK). Otherwise,MKhas noalignment-specifier.For every
rLinmdescrssuch thatK < L, the declaration corresponding torKprecedes the declaration corresponding torL.11 Returns:
class_type.
1 Subclause [meta.reflection.traits] specifies consteval functions to query the properties of types ([meta.unary]), query the relationships between types ([meta.rel]), or transform types ([meta.trans]) at compile time. Each consteval function declared in this class has an associated class template declared elsewhere in this document.
2 Every function and function template declared in this subclause has the following conditions required for a call to that function or function template to be a constant subexpression ([defns.const.subexpr]):
- (2.1) For every parameter
pof typeinfo,is_type(p)istrue.- (2.2) For every parameter
rwhose type is constrained onreflection_range,ranges::all_of(r, is_type)istrue.3 Unless otherwise specified, each function and function template described in this subclause has the following behavior based on the signature of that function or function template. [ Note 1: The associated class template need not be instantiated. — end note ]
Signature Returns bool meta::UNARY(info type); bool meta::UNARY_type(info type);std::UNARY_v<T>, whereTis the type or type alias represented bytypebool meta::BINARY(info t1, info t2); bool meta::BINARY_type(info t1, info t2);std::BINARY_v<T1, T2>, whereT1andT2are the types or type aliases represented byt1andt2, respectivelytemplate <reflection_range R> bool meta::VARIADIC_type(info type, R&& args);std::VARIADIC_v<T, U...>whereTis the type or type alias represented bytypeandU...is the pack of types or type aliases whose elements are represented by the corresponding elements ofargstemplate <reflection_range R> bool meta::VARIADIC_type(info t1, info t2, R&& args);std::VARIADIC_v<T1, T2, U...>whereT1andT2are the types or type aliases represented byt1andt2, respectively, andU...is the pack of types or type aliases whose elements are represented by the corresponding elements ofargsinfo meta::UNARY(info type);A reflection representing the type denoted by std::UNARY_t<T>, whereTis the type or type alias represented bytypetemplate <reflection_range R> info meta::VARIADIC(R&& args);A reflection representing the type denoted by std::VARIADIC_t<T...>whereT...is the pack of types or type aliases whose elements are represented by the corresponding elements ofargstemplate <reflection_range R> info meta::VARIADIC(info type, R&& args);A reflection representing the type denoted by std::VARIADIC_t<T, U...>whereTis the type or type alias represented bytypeandU...is the pack of types or type aliases whose elements are represented by the corresponding elements ofargs[ Note 2: For those functions or function templates which return a reflection, that reflection always represents a type and never a type alias. — end note ]
// associated with [meta.unary.cat], primary type categories consteval bool is_void_type(info type); consteval bool is_null_pointer_type(info type); consteval bool is_integral_type(info type); consteval bool is_floating_point_type(info type); consteval bool is_array_type(info type); consteval bool is_pointer_type(info type); consteval bool is_lvalue_reference_type(info type); consteval bool is_rvalue_reference_type(info type); consteval bool is_member_object_pointer_type(info type); consteval bool is_member_function_pointer_type(info type); consteval bool is_enum_type(info type); consteval bool is_union_type(info type); consteval bool is_class_type(info type); consteval bool is_function_type(info type); consteval bool is_reflection_type(info type); // associated with [meta.unary.comp], composite type categories consteval bool is_reference_type(info type); consteval bool is_arithmetic_type(info type); consteval bool is_fundamental_type(info type); consteval bool is_object_type(info type); consteval bool is_scalar_type(info type); consteval bool is_compound_type(info type); consteval bool is_member_pointer_type(info type); // associated with [meta.unary.prop], type properties consteval bool is_const_type(info type); consteval bool is_volatile_type(info type); consteval bool is_trivially_copyable_type(info type); consteval bool is_trivially_relocatable_type(info type); consteval bool is_replaceable_type(info type); consteval bool is_standard_layout_type(info type); consteval bool is_empty_type(info type); consteval bool is_polymorphic_type(info type); consteval bool is_abstract_type(info type); consteval bool is_final_type(info type); consteval bool is_aggregate_type(info type); consteval bool is_consteval_only_type(info type); consteval bool is_signed_type(info type); consteval bool is_unsigned_type(info type); consteval bool is_bounded_array_type(info type); consteval bool is_unbounded_array_type(info type); consteval bool is_scoped_enum_type(info type); template <reflection_range R = initializer_list<info>> consteval bool is_constructible_type(info type, R&& type_args); consteval bool is_default_constructible_type(info type); consteval bool is_copy_constructible_type(info type); consteval bool is_move_constructible_type(info type); consteval bool is_assignable_type(info type_dst, info type_src); consteval bool is_copy_assignable_type(info type); consteval bool is_move_assignable_type(info type); consteval bool is_swappable_with_type(info type_dst, info type_src); consteval bool is_swappable_type(info type); consteval bool is_destructible_type(info type); template <reflection_range R = initializer_list<info>> consteval bool is_trivially_constructible_type(info type, R&& type_args); consteval bool is_trivially_default_constructible_type(info type); consteval bool is_trivially_copy_constructible_type(info type); consteval bool is_trivially_move_constructible_type(info type); consteval bool is_trivially_assignable_type(info type_dst, info type_src); consteval bool is_trivially_copy_assignable_type(info type); consteval bool is_trivially_move_assignable_type(info type); consteval bool is_trivially_destructible_type(info type); template <reflection_range R = initializer_list<info>> consteval bool is_nothrow_constructible_type(info type, R&& type_args); consteval bool is_nothrow_default_constructible_type(info type); consteval bool is_nothrow_copy_constructible_type(info type); consteval bool is_nothrow_move_constructible_type(info type); consteval bool is_nothrow_assignable_type(info type_dst, info type_src); consteval bool is_nothrow_copy_assignable_type(info type); consteval bool is_nothrow_move_assignable_type(info type); consteval bool is_nothrow_swappable_with_type(info type_dst, info type_src); consteval bool is_nothrow_swappable_type(info type); consteval bool is_nothrow_destructible_type(info type); consteval bool is_nothrow_relocatable_type(info type); consteval bool is_implicit_lifetime_type(info type); consteval bool has_virtual_destructor(info type); consteval bool has_unique_object_representations(info type); consteval bool reference_constructs_from_temporary(info type_dst, info type_src); consteval bool reference_converts_from_temporary(info type_dst, info type_src);consteval size_t rank(info type);4 Returns:
rank_v<T>, whereTis the type represented bydealias(type).consteval size_t extent(info type, unsigned i = 0);5 Returns:
extent_v<T, I>, whereTis the type represented bydealias(type)andIis a constant equal toi.// associated with [meta.rel], type relations consteval bool is_same_type(info type1, info type2); consteval bool is_base_of_type(info type_base, info type_derived); consteval bool is_virtual_base_of_type(info type_base, info type_derived); consteval bool is_convertible_type(info type_src, info type_dst); consteval bool is_nothrow_convertible_type(info type_src, info type_dst); consteval bool is_layout_compatible_type(info type1, info type2); consteval bool is_pointer_interconvertible_base_of_type(info type_base, info type_derived); template <reflection_range R = initializer_list<info>> consteval bool is_invocable_type(info type, R&& type_args); template <reflection_range R = initializer_list<info>> consteval bool is_invocable_r_type(info type_result, info type, R&& type_args); template <reflection_range R = initializer_list<info>> consteval bool is_nothrow_invocable_type(info type, R&& type_args); template <reflection_range R = initializer_list<info>> consteval bool is_nothrow_invocable_r_type(info type_result, info type, R&& type_args);6 [ Note 3: If
tis a reflection of the typeintanduis a reflection of an alias to the typeint, thent == uisfalsebutis_same_type(t, u)istrue. Also,t == dealias(u)istrue. — end note ].// associated with [meta.trans.cv], const-volatile modifications consteval info remove_const(info type); consteval info remove_volatile(info type); consteval info remove_cv(info type); consteval info add_const(info type); consteval info add_volatile(info type); consteval info add_cv(info type); // associated with [meta.trans.ref], reference modifications consteval info remove_reference(info type); consteval info add_lvalue_reference(info type); consteval info add_rvalue_reference(info type); // associated with [meta.trans.sign], sign modifications consteval info make_signed(info type); consteval info make_unsigned(info type); // associated with [meta.trans.arr], array modifications consteval info remove_extent(info type); consteval info remove_all_extents(info type); // associated with [meta.trans.ptr], pointer modifications consteval info remove_pointer(info type); consteval info add_pointer(info type);
[ Editor's note: There
are four transformations that are deliberately omitted here.
type_identity and
enable_if are not useful,
conditional(cond, t, f) would
just be a long way of writing
cond ? t : f, and
basic_common_reference is a
class template intended to be specialized and not directly invoked.
]
// associated with [meta.trans.other], other transformations consteval info remove_cvref(info type); consteval info decay(info type); template <reflection_range R = initializer_list<info>> consteval info common_type(R&& type_args); template <reflection_range R = initializer_list<info>> consteval info common_reference(R&& type_args); consteval info underlying_type(info type); template <reflection_range R = initializer_list<info>> consteval info invoke_result(info type, R&& type_args); consteval info unwrap_reference(info type); consteval info unwrap_ref_decay(info type);
[ Editor's note: The
below inclusion of
meta::type_order assumes the
acceptance of [P2830R10]. ]
consteval size_t tuple_size(info type);7 Returns:
tuple_size_v<T>whereTis the type represented bydealias(type).consteval info tuple_element(size_t index, info type);8 Returns: A reflection representing the type denoted by
tuple_element_t<I, T>whereTis the type represented bydealias(type)andIis a constant equal toindex.consteval size_t variant_size(info type);9 Returns:
variant_size_v<T>whereTis the type represented bydealias(type).consteval info variant_alternative(size_t index, info type);10 Returns: A reflection representing the type denoted by
variant_alterantive_t<I, T>whereTis the type represented bydealias(type)andIis a constant equal toindex.consteval strong_ordering type_order(info t1, info t2);11 Returns:
type_order_v<T1, T2>, whereT1andT2are the types represented bydealias(t1)anddealias(t2), respectively.
bit_castAnd we have adjust the requirements of
bit_cast to not allow casting to or
from
meta::info,
in 22.11.3 [bit.cast]/3, which we add
as a mandates (and then Constant When has to be before
Returns, but the Returns remains unchanged):
template<class To, class From> constexpr To bit_cast(const From& from) noexcept;1 Constraints:
- (1.1)
sizeof(To) == sizeof(From)istrue;- (1.2)
is_trivially_copyable_v<To>istrue; and- (1.3)
is_trivially_copyable_v<From>istrue.* Mandates: Neither
TonorFromare consteval-only types ([expr.const]).2 Returns: […]
3
RemarksConstant When:This function is constexpr if and only ifTo,From, and the types of all subobjects ofToandFromare typesTsuch that:
- (3.1)
is_union_v<T>isfalse;- (3.2)
is_pointer_v<T>isfalse;- (3.3)
is_member_pointer_v<T>isfalse;- (3.4)
is_volatile_v<T>isfalse; and- (3.5)
Thas no non-static data members of reference type.4 Returns: […]
Add a new Annex C entry:
Affected subclause: [lex.operators]
Change: New operator
^^.Rationale: Required for new features.
Effect on original feature: Valid C++23 code that contains two consecutive
^tokens may be ill-formed in this revision of C++.[ Example 1:— end example ]struct C { int operator^(int); }; int operator^(int (C::*p)(int), C); int i = &C::operator^^C{}; // ill-formed; previously well-formed
Modify [diff.cpp23.library]:
Affected subclause: [headers]
Change: New headers.
Rationale: New functionality.
Effect on original feature: The folowing C++ headers are new:
<debugging>,<hazard_pointer>,<inplace_vector>,<linalg>,<meta>,<rcu>,<simd>, and<text_encoding>. Valid C++ 2023 code that#includes headers with these names may be invalid in this revision of C++.
This is a feature with both a language and library component. Our
usual practice is to provide something like
__cpp_impl_reflection and
__cpp_lib_reflection for this. But
since the two pieces are so closely tied together, maybe it really only
makes sense to provide one?
For now, we’ll add both.
To 15.12 [cpp.predefined]:
__cpp_impl_coroutine 201902L __cpp_impl_destroying_delete 201806L __cpp_impl_three_way_comparison 201907L + __cpp_impl_reflection 2025XXL
and 17.3.2 [version.syn]:
+ #define __cpp_lib_reflection 2025XXL // also in <meta>
[P2996R4] was forwarded to CWG in St. Louis (June 2024). In the time after, some minor design changes were shown to be necessary. The following changes were confirmed by EWG during the Hagenberg 2025 meeting.
One small change was needed to the reflection operator.
^^ operator
to a non-type template parameter
^^ to a
non-type template parameter, or to a
pack-index-expression, gave
a reflection of the value or object computed or designated by the
operand ([expr.reflect]/10).^^ to such
expressions is ill-formed ([expr.reflect]/6.4).^^ of a
reflect-expression is an
unevaluated operand ([expr.context/1], [expr.reflect]/6.4). Supporting
this necessarily requires that we evaluate said operand, which is at
odds with its specification. Introducing some sort of “conditionally
evaluated” operand machinery would be novel and unnecessary.std::meta::reflect_value
or std::meta::reflect_object,
as appropriate.A few changes were needed to “consteval-only types” to ensure that objects of such types cannot reach runtime.
void *
pointer to a constexpr std::meta::info
cannot be a result of a constant expression.Two changes were needed for splicers.
splice-template-name
handled template splicers.
splice-type-specifier
([dcl.type.splice]). Simple rule:
typename [:R:].template [:R:].type-constraints and
concept-ids.concept-id,
substitute can still check whether a
concept is satisfied by a template argument list.Our framework for code injection as performed by
define_aggregate evolved quite a bit
after P2996R4. When the evaluation of an expression calls
define_aggregate, we say that the
evaluation produces an injected declaration of the completed
type (try it on godbolt).
define_aggregate can be called.
constexpr
variable initializers, immediate invocations,
constant-expressions, if constexpr
conditions ([expr.const]/21).consteval
blocks ([dcl.pre]). No other expressions that would evaluate
define_aggregate can qualify as core
constant expressions ([expr.const/10.27+]).consteval
block and the target scope of the injected declaration
([expr.const]/29).define_aggregate to observe
failed substitutions, overload resolution order, etc.