| Document #: | P3687R1 [Latest] [Status] |
| Date: | 2025-06-18 |
| Project: | Programming Language C++ |
| Audience: |
EWG, LEWG |
| Reply-to: |
Wyatt Childers <wcc@edg.com> Dan Katz <dkatz85@bloomberg.net> Daveed Vandevoorde <daveed@edg.com> Ville Voutilainen <ville.voutilainen@gmail.com> |
splice-template-arguments
from P2996
using
declaration^^Using::Decl
ill-formed for
using-declarators
Since [P3687R0]:
We propose two minor design changes to [P2996R12] to ensure its readiness for C++26 and future-proof the evolution of the feature.
First, we propose that “splice template arguments” be removed from P2996 and deferred until C++29: Neither the wording nor the implementation are fully baked, CWG has not (as of this writing) dedicated sufficient time to its review, and Reflection suffers no loss of expressive power from its removal.
Second, in response to edge cases surfaced during CWG review, we
elaborate on the need to represent
using-declarations with
Reflection. At this late stage, we offer two means of ameliorating this
feature’s absence:
using-declarations to
C++26, or^^Cls::mem
ill-formed whenever mem names a
using-declaration, thus
preserving the syntactic space for C++29.P2996 proposes
splice-template-arguments
that can match any other kind of template argument. For instance,
given
template <class P1, auto P2, template<typename> class P3>
struct mytemplate;the template-id
mytemplate<[:R1:], [:R2:], [:R3:]>will be valid as long as the constructs represented by
R1,
R2, and
R3 match the form of the respective
template parameters.
No. An expression splice (i.e.,
splice-expression) can
appear as a constant template argument, and a type splice (i.e.,
splice-type-specifier) can
appear as a type template argument.
constexpr auto r1 = ^^int;
constexpr auto r2 = std::meta::reflect_value(42);
std::array<typename [:r1:], [:r2:]> arr; // OKSplice template arguments, the feature that we here
propose to remove from P2996, lets the syntax [:R:]
uniformly match any kind of template parameter when appearing in a
template argument list. So while the above example remains valid, the
following become disallowed because (in the absence of “splice template
argument” support) unqualified [:R:]
always parses as an expression:
std::array<typename [:r1:], [:r2:]> arr1;
// still OK
std::array<[:r1:], [:r2:]> arr2;
// error: reflection of a type 'r1' cannot splice as an expression; did you mean 'typename [:r1:]'?
// error: cannot splice 'r1' (a reflection of a type) as an expression
// error: expression '[:r1:]' does not match template type parameter
template <template <typename T, int V> class Tmp, typename T, int V>
auto make() { return Tmp<T, V>{}; }
constexpr std::meta::info rs[] = {^^std::array, ^^int, std::meta::reflect_value(42)};
auto arr = make<[:rs[0]:], [:rs[1]:], [:rs[2]:]>();
// error: expression '[:rs[0]:]' does not match template template parameter 'Tmp'
// error: expression '[:rs[1]:]' does not match type template parameter 'T'Consider the following example:
template <template <typename> class TCls>
auto tfn1() {
return TCls<int>{};
}
template <std::meta::info R>
template tfn2() {
return tfn1<[:R:]>();
}For the template-id
tfn1<[:R:]>
to be valid in the template definition of
tfn2(), we
must be able to substitute the dependent splice template argument [:R:]
into tfn1. In the parlance of P2996,
this entails replacing the
template-id “TCls<int>”
with the
splice-specialization-specifier
“[:R:]<int>”,
and replacing the
template-name
“TCls” with the
splice-specifier “[:R:]”.
Since Clang implements template substitution as a tree transformation
(i.e., “recursively substitute like things with like things”), the Clang
reference implementation of P2996 handles this by adding a new category
of DependentTemplateName in which a
TemplateName wraps a
SpliceSpecifier.
This implementation strategy mirrors the grammar proposed by [P2996R7], in which a
template-name might be a
splice-specifier.
Discussion within CWG, however, steered us away from this direction (for
good reason: a
splice-specifier is not a
“name”). The primary author of the Clang/P2996 reference implementation
has not identified a suitable alternative implementation strategy,
reinforcing the belief that splice template arguments, as currently
specified, require a tighter coupling of
template-name and
splice-specifier. As
tempting as it is to dismiss this as “just a wording problem”, the
wording strategy is not easily separable from the implementation
strategies that will be adopted by compilers.
Lastly, as of this writing, CWG is yet to invest meaningful time in
the review of
splice-template-arguments.
The combination of our belief that the wording is insufficient, together
with the scrutiny that the feature still requires, leaves us feeling
that dropping the feature from P2996 will strengthen P2996’s readiness
for C++26.
Absolutely none. Any
template-id written with
splice-template-arguments
as
template_name<[:R1:], [:R2:], [:Rs:]...>can be equivalently expressed using std::meta::substitute:
[:substitute(^^template_name, {R1, R2, Rs...}):]Splitting splice template arguments from the paper, therefore, represents a means of reducing the P2996 surface area that CWG still needs to review, while losing absolutely no expressive power from P2996.
Absolutely. For C++26, a template argument of the form [:R:]
will always be considered a
splice-expression, which
can only match a constant template parameter. In C++29, we can make it
well-formed for this syntax to also match type template parameters and
template template parameters.
That said, the change will not be entirely non-breaking; consider the following example provided by Tomasz Kamiński:
template <auto> int foo(); // A
template <typename> int foo(); // B
template <std::meta::info R>
auto bar(int) -> decltype(foo<[:R:]>()) { return 1; } // #1
template <std::meta::info R>
auto bar(...) -> int { return 0; } // #2
constexpr int v = bar<^^int>(1);In the initializer for v,
bar denotes an overload set
containing both #1 and #2. Function template argument deduction for #1
will fail, since the substitution of “^^int”
into “R” will form “[:^^int:]”
- an invalid
splice-expression. Since
this ill-formed expression is within the immediate context, the
specialization will merely be discarded from the set of candidate
functions. We are left with the specialization derived from #2, within
which no such ill-formed expression appears;
v is initialized to
0.
But in the presence of splice template arguments, “[:^^int:]”
is parsed as a
splice-template-argument
rather than an expression. The non-dependent
splice-template-argument is
interpreted as a
splice-type-specifier, so
the B overload is selected. Because
#1 is a better match than #2, v
would initialized to
1.
WG21 has historically found changes to overload resolution to be acceptable, even when it has meant breaking SFINAE-dependent programs. We therefore feel that the suggestion to adopt splice template arguments later rests on solid ground - but it’s certainly still worth pausing to understand the implications.
Risky, but also not the end of the world. The grammar for
template-name
([temp.names]) will likely have to be extended:
template-name: identifier + splice-specifier
Which is not only an invasive change, but one that CWG has judged to
be ill-advised. The P2996 authors will have to (again) comb through the
corpus of core wording to find what other sections must be modified to
account for the change, while adding various “if the
template-name is an
identifier” carve outs for
cases where the
template-name is more
“name”-like than “computed entity specifier”-like. It is the opinion of
the authors that further substantial core wording homework would neither
be beneficial to the prospects of Reflection making C++26 nor to their
emotional well-being.
using-declarationsConsider the following example:
struct Base { int member = 0; };
struct Derived : private Base { using Base::member; };
Derived d;
auto p1 = d.member; // OK
auto p2 = d.[:^^Derived::member:]; // errorConsider also the following assertions, which all hold with [P2996R12]:
#include <meta>
struct Base {
protected: int member;
friend void fn();
};
struct Derived : private Base {
using Base::member;
};
void fn() {
constexpr auto ctx = std::meta::access_context::unprivileged();
static_assert(!is_accessible(^^Base::member, ctx));
static_assert(!is_accessible(^^Derived::member, ctx));
static_assert(!is_accessible(^^Base::member, ctx.via(^^Derived)));
static_assert(!is_accessible(^^Derived::member, ctx.via(^^Derived)));
}
auto p = &Derived::member; // no problemIn particular, there is currently no way to observe
using-declaration naming
Base::member
exists in the scope of Derived
orBase::member
can be accessibly named in the scope of
Derived.The first point implies restrictions for e.g., debug tools that may wish to format a representation of a class type. The second implies a limited view of which members are accessible within a class.
In both examples above, the expression ^^Derived::member
represents the entity
Base::member
because using-declarations
are transparently replaced with the declarations that they name during
lookup ([basic.lookup]/3). In the first example, the expression
d.[:^^Derived::member:]is ill-formed because the type of the left-hand-side (i.e., Derived &)
cannot be converted to the “naming class” of the right-hand-side (i.e.,
Base) ([class.access.base]/6); since
^^Derived::member
represents
Base::member,
this is the moral equivalent of writing d.Base::member.
Similarly in the second example, the expression
is_accessible(^^Derived::member, ctx.via(^^Derived))asks whether the class member
Base::member
is accessible from a point in the global scope when named in the class
Derived. The algorithm from
[class.access.base]/5 tells us that this is not the case, which mirrors
the fact that writing Derived::Base::member
is ill-formed. If we want to instead determine whether Derived::member is
well-formed, we must instead ask whether the
using-declaration is
accessible in Derived, a question
which P2996 lacks the machinery to model.
During early revisions of P2996, the authors did not seek to integrate Reflection with Access Control. Around the time of the Wrocław meeting, it became more clear that a robust story for observing the accessibility of class members would be required to achieve consensus for the design. The direction proposed by [P3547R1] received strong support in Hagenberg, and was thereafter merged into [P2996R10].
using-declarators, as
currently specified, are not a natural target for Reflection: Since they
introduce only names (rather than entities or class members), there is
“nothing to reflect” (in this regard, they are more similar (pre-P2996)
to type aliases and namespace aliases than to e.g., non-static data
members). They nevertheless play an important role in Access Control: An
otherwise inaccessible member of a base class might be accessible when
named through a
using-declarator declared
in a derived class (see [class.access.general]/4).
The surprising behavior exhibited by the first example, in which the “surprising answer” is obtained using only the reflection and splicing operators, was only very recently uncovered by a careful review of [expr.ref] and [class.access] during CWG review.
What follows is a small extension on top of what is proposed by
P2996. We provide it as an elaboration of how we believe Reflection
should interact with
using-declarations in an
unspecified future delivery vehicle of the C++ Standard.
First, we propose modifying the core wording around
using-declarators such that
they do introduce entities (analogous to existing changes in
P2996 for type aliases and namespace aliases). In particular a
declaration
class Cls {
using Scope::id;
};introduces a class (or namespace) member known as an entity
proxy for each entity (including inherited constructors and
arbitrary enumerators) whose declaration is named by
Scope::id
(note: several such members may be introduced if
Scope::id
denotes an overload set). The “underlying entity” of each such entity
proxy is one of the entities whose declarations were named by
Scope::id.
Note that this is scarcely more than a technical wording change to make
sure there is “something to have a reflection of”.
Next, we propose that a
reflect-expression ^^Cls::id
whose operand names a unique entity proxy, represents said
entity proxy, and not its underlying entity. This gives a means
of observing the properties of the entity proxy (e.g., accessibility,
parent class) apart from the properties of its underlying
entity.
Since entity proxies (outside of functions) are members of
classes and namespaces, we propose that the reflections returned by
members_of also include entity
proxies (i.e., in formal P2996 parlance: entity proxies that are members
of a class or namespace Q are
Q-members-of-representable).
If lookup for
Cls::id
finds multiple declarations named id
in the scope of Cls, the
reflect-expression ^^Cls::id
is ambiguous and ill-formed, even if all such declarations denote the
same underlying entity.
class A { void fn(); };
class B : virtual A { using A::fn; };
class C : virtual A { using A::fn; };
class D : B, C {};
auto p = &D::fn; // OK
constexpr auto r = ^^D::fn; // ambiguous, ill-formedIn a more distant future, it may be possible to let such expressions represent all entities named by the ambiguous lookup; this direction would take advantage of how Reflection separates the lookup of a name from its point of use. Such “lookup sets” could be a powerful extension that facilitates (and generalizes) overload sets.
Existing P2996 metafunctions would be updated to observe
properties of entity proxies (e.g.,
is_public,
parent_of,
is_class_member), and a new
is_entity_proxy function would be
introduced to recognize such reflections.
Lastly, the P2996 metafunction std::meta::dealias
should map a reflection of an entity proxy to a reflection of its
underlying entity. Since the phrase “underlying entity” is a Word of
Power introduced by P2996 that applies equally well to aliases and
entity proxies, we propose renaming std::meta::dealias
to std::meta::underlying_entity_of.
A separate std::meta::proxied_entity_of
function returns a reflection of the entity directly named by an entity
proxy without “unwrapping all layers” to reach the underlying
entity.
class B { using Alias = int; };
class D : B { using B::Alias; };
static_assert(is_type_alias(^^B::Alias));
static_assert(is_entity_proxy(^^D::Alias));
static_assert(^^B::Alias != ^^D::Alias);
static_assert(proxied_entity_of(^^D::Alias) == ^^B::Alias);
static_assert(underlying_entity_of(^^D::Alias) == ^^int);
static_assert(underlying_entity_of(^^B::Alias) == underlying_entity_of(^^D::Alias));Given the following code:
struct A { protected: int m; };
struct B : private A { using A::m; };
constexpr auto rm = ^^B::m;We must decide now whether ^^B::m
A::m that is
a member of A (P2996 status
quo),B::m that is
a member of B and whose underlying
entity is
A::m (as
elaborated above), orThat decision will be observable to programs in several ways (e.g.,
whether parent_of(rm)
is ^^B or
^^A, the
result of is_public(rm),
etc). If we would prefer that ^^B::m
instead represent a member of B, we
should either adopt that change now or make the expression ill-formed
entirely to reserve space for that change in C++29. Either way, we
should make that change for C++26.
Although the “generative metaprogramming” facilities directly
provided by P2996 will be narrow (e.g.,
define_aggregate), it will be
possible to inspect the members and properties of a class, compute a
string containing a well-formed C++ class definition derived from that
class, render that string to stdout,
and write the resulting code to a file or a pipe to another process
(e.g., a C++ compiler). Such pipelines will benefit from being able to
inspect using-declarators,
as the transformed source code will more exactly correspond to the class
from which it was transformed.
As an aside, the proposed direction ought to make it possible to
implement a class_lookup library
function that accurately models the algorithm specified in
[class.member.lookup], which is pretty sweet. I’m sure we can think of
something fun to do with that.
Our proposal for “entity proxies” is entirely implemented in
Bloomberg’s Clang/P2996 fork. Passing either -fentity-proxy-reflection
or -freflection-latest
(the latter being a “catch all” for all implemented reflection and
reflection-adjacent papers) enables the feature. An example on Godbolt
can be found here.
Splice template arguments are implemented in Clang/P2996, but the effort to refactor and decouple their implementation from “template names” motivated this proposal’s recommendation that they for now be removed.
The authors submit the following polls to EWG for their consideration.
splice-template-arguments
from D2996R13.| SF | F | N | A | SA |
|
|
| SF | F | N | A | SA |
|
|
^^id
ill-formed when id names a
using-declaration and
forward to CWG for inclusion in C++26.(Only suggested if [2a] does not find consensus)
| SF | F | N | A | SA |
|
|
splice-template-arguments
from P2996[ Drafting note: All wording assumes D2996R13. ]
Strike the P2996 changes to the grammar at the beginning of [temp.param]:
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
Strike the P2996 changes to the grammar 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
Strike the sentence from paragraph 1 pertaining to splice template arguments from P2996:
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.AWhen the parameter declared by the template is a template parameter pack, it will correspond to zero or moretemplate-argumentthat is a splice template argument is considered to match the form specified for the corresponding template parameter.template-arguments.
Revert the changes to paragraph 3 from P2996:
3
AIn atemplate-argumentof the formsplice-specifieris interpreted as asplice-template-argument. For any othertemplate-argument, an ambiguity between atype-idand an expression is resolved to atype-id, regardless of the form of the correspondingtemplate-parameter.
Strike the changes to [temp.arg.type] from P2996:
1 A
template-argumentfor a type template parameter shalleitherbe atype-idor a.splice-template-argumentwhosesplice-specifierdesignates a type
Strike the changes to [temp.arg.template] from P2996:
1 A
template-argumentfor a template template parameter shalleitherbe the name of a templateor a. For asplice-template-argumenttype-tt-parameter, the nameorshall denotesplice-template-argumentdesignatea class template or alias template. For avariable-tt-parameter, the nameorshall denotesplice-template-argumentdesignatea variable template. For aconcept-tt-parameter, the nameorshall denotesplice-template-argumentdesignatea 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.
Strike the paragraph covering splice template arguments from P2996.
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 itssplice-specifieris dependent.
[ Drafting note: All wording assumes D2996R13.
The general approach starts from [namespace.udecl]: whereas a
using-declarator previously
“named” or “nominated” or “referred” to a set of declarations, it is now
said to proxy those declarations. A
using-declarator then
introduces an entity proxy for each entity introduced by one of
the declarations that it proxies. The
using-declarator names the
entity proxies, but can still be said to “proxy” the found
declarations.
Entity proxies are entities; they can be class members, so
using-declarations now
introduce class members. The existing [class.access.base] definition for
when a “member is accessible when designated in a class from a point”
now works much more cleanly for
using-declarations.
Most other core wording changes are just preferring the verb “proxies” to e.g., “names”. ]
Add “entity proxies” to the list of entities in pargraph 8.
8 An entity is a variable, structured binding, result binding, function, enumerator, type, type alias, non-static data member, bit-field, template, namespace, namespace alias, entity proxy, template parameter, function parameter, or
init-capture. 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.[ Note 1: Type aliases,
andnamespace aliases, and entity proxies have underying entities that are distinct from themselves. — end note ]
Change “named” to “denoted” in paragraph 3.
3 A single search in a scope
Sfor a nameNfrom a program pointPfinds all declarations that precedePto which any name that is the same asN([basic.pre]) is bound inS. If any such declaration is ausing-declaratorwhose terminal name ([expr.prim.id.unqual]) is not dependent ([temp.dep.type]), it is replaced by the declarations denotednamedby theusing-declarator([namespace.udecl]).
Change “names” to “denotes” in paragraph 1.2:
- (1.2) if
Nis dependent and is the terminal name of ausing-declarator([namespace.udecl]) thatnamesdenotes a constructor,
Add [namespace.udecl] to the note following paragraph 8:
[ Note 3: There are other circumstances in which declarations declare the same entity ([namespace.udecl], [dcl.link], [temp.type], [temp.spec.partial]). — end note ]
Extend p16.1+.2 to also include entity proxies:
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,
- (16.1+) it is a reflection representing either
- (16.1+.1) an entity, value, or object that is TU-local, or
- (16.1+.2) a type alias,
ora namespace alias, or an entity proxy or- (16.1+.3) a direct base class relationship ([class.derived.general]) for which the base class or the derived class is TU-local, or
- (16.1+.4) a data member description (
T,N,A,W,NUA) ([class.mem.general]) for whichTis a type alias or a TU-local type, or- (16.2) it is an object of class or array type and any of its subobjects 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.
Values that are TU-local to different translation units are never considered equivalent.
Add a new bullet to the list of constructs that a reflection can represent.
16 The types denoted by
cv std::nullptr_tare distrinct 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) […]
- (x.16) a namespaace alias ([namespace.alias]),
- (x.17) a namespace ([basic.namespace.general]),
- (x.17+) an entity proxy ([namespace.udecl]),
- (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. […]
Extend paragraph 5 to allow a
reflect-expression to
represent an entity proxy.
5 If a
reflect-expressionRmatches the form^^ qualified-reflection-name, it is interpreted as such and its representation is determined as follows:
- (5.*) If the
identifiernames a unique entity proxy ([namespace.udecl]),Rrepresents that entity proxy. If theidentifiernames multiple entity proxies,Ris ill-formed.- (5.1) Otherwise,
Iif theidentifieris anamespace-namethat names a namespace alias ([namespace.alias]),Rrepresents that namespace alias. For any othernamespace-name,Rrepresents the denoted namespace.- (5.2) […]
using
declarationModify paragraph 1 such that a
using-declarator introduces
an entity called an “entity proxy”.
1 The component names of a
using-declaratorare those of itsnested-name-specifierandunqualified-id. Eachusing-declaratorin ausing-declaration84 proxiesnamesthe set of declarations found by lookup ([basic.lookup.qual]) for theusing-declarator, except the class and enumeration declarations that would be discarded are merely ignored when checking for ambiguity ([basic.lookup]), conversion function templates with a dependent return type are ignored, and certain functions are hidden as described below. Eachusing-declaratordeclares an entity proxy corresponding to each unique entity introduced by any of the declarations proxied by theusing-declarator. An entity proxy is said to proxy the entityEto which it corresponds, and has the same underlying entity asE. If the terminal name of theusing-declaratoris dependent ([temp.dep.type]), theusing-declaratoris considered tonamedenote a constructor if and only if thenested-name-specifierhas a terminal name that is the same as theunqualified-id. If the lookup in any instantiation finds that ausing-declaratorthat is not considered tonamedenote a constructor does do so, or that ausing-declaratorthat is considered tonamedenote a constructor does not, the program is ill-formed.If a
using-declaratorintroduces an entity proxyP, any otherusing-declaratorinhabiting the same scope that proxies a declaration of the entity proxied byPalso declaresP.
Use “denotes” instead of “names” in paragraphs 2 and 3 to look through other entity proxies.
2 If the
using-declaratornamesdenotes a constructor, it declares that the class inherits thenameddenoted set of constructorsconstructor declarationsfrom the nominated base class.3 In a
using-declarationused as amember-declaration, eachusing-declaratorshall eithernamedenote an enumerator or have anested-name-specifiernaming a base class of the current class ([expr.prim.this]).[ Example 1: … — end example ]
If a
using-declaratornamesdenotes a constructor, itsnested-name-specifiershall name a direct base class of the current class. If the immediate (class) scope is associated with a class template, it shall derive from the specified base class or have at least one dependent base class.
Modify paragraph 7 to implement the same rule in the presence of entity proxies.
7 A
using-declarationthatnamesproxies a class memberother than an enumeratorshall either be amember-declarationor denote an enumerator.
Use “denotes” instead of “names” in paragraphs 12-14 to look through other entity proxies.
12 [ Note 5: For the purpose of forming a set of candidates during overload resolution, the functions
nameddenoted by ausing-declarationin a derived class are treated as though they were direct members of the derived class. […] — end note ]13 Constructors that are
nameddenoted by ausing-declarationare treated as though they were constructors of the derived class when looking up constructors of the derived class ([class.qual]) or forming a set of overload candidates ([over.match.ctor], [over.match.copy], [over.match.list]).14 In a
using-declaratorthat does notnamedenote a constructor, every declaration named shall be accessible. In ausing-declaratorthatnamesdenotes a constructor, no access check is performed.
Strike paragraph 16:
using-declarators declare
members now and replace it with a note; fold the base-class constructor
provision into [class.access.general]/4.
16 Ausing-declarationhas the usual accessibility for amember-declaration. Base-class constructors considered because of ausing-declaratorare accessible if they would be accessible when used to construct an object of the base class; the accessibility of theusing-declarationis ignored.[ Note 8: An entity proxy introduced by a
using-declarationis subject to the usual access rules unless its underlying entity is a base-class constructor ([class.access.general]). — end note ]
Adopt the language of “proxying” in paragraph 5.
5 If an exported declaration is a
using-declaration([namespace.udecl]) and is not within a header unit, the underlying entity of each entity proxy thereby introducedto which all of the(if any) shall have been introduced with a name having external linkage.using-declarators ultimately refer
Remove using-declarators
from the list of declarations that do not introduce a class member in
paragraph 4.
4 A
member-declarationdoes not declare new members of the class if it is
Use “denotes” in the note that follows paragraph 8.
[ Note 5: A
using-declarationin a derived classCthatnamesdenotes an assignment operator from a base class never suppressess the implicit declaration of an assignment operator ofC, even if the base class assignment oeprator would be a copy or move assignmentoperator if declared as a member ofC. — end note ]
Refer to the declarations “denoted” by
using-declarators, rather
than those “named” (i.e., the entity proxies), in paragraph 4. Also fold
the base-class accessibility provision from (now deleted)
[namespace.udecl]/16 into this paragraph.
Access control is applied uniformly to declarations and expressions.
[ Note 2: Access control applies to members nominated by friend declarations ([class.friend]) and
using-declarations ([namespace.udecl]). — end note ]When a
using-declaratorthat does not denote a base-class constructor is named, access control applies to the entity proxies nominated by it, not to the entities whose declarations are denoted bythat replaceit.
Change “nominated” to “denoted” in paragraph 4.
4 For implicit object member functions, the type of the implicit object parameter is […].
[ Example 1: For a
constmember function of classX, the extra parameter is assumed to have type “lvalue reference toconst X”. — end example ]For conversion functions that are implicit object member functions, […]. For non-conversion functions that are implicit object member functions
nominateddenoted by ausing-declarationin a derived class, the function is considered to be a member of the derived class for the purpose of defining the type of the implicit object parameter. For static member functions, […].
Change “refers” to “denotes” in the note that follows paragraph 7.
[ Note 1: One consequence is that a
using-declarationwhichrefers todenotes a class template does not restrict the set of partial specializations that are found through theusing-declaration. — end note ]
Add the is_entity_proxy and
proxied_entity_of functions.
Header
<meta>synopsis#include <initializer_list> namespace std::meta { [...] 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_entity_proxy(info r);consteval bool is_function(info r); [...] consteval bool has_default_member_initializer(info r); consteval bool has_parent(info r); consteval info parent_of(info r); consteval infodealiasunderlying_entity_of(info r);consteval info proxied_entity_of(info r);consteval bool has_template_arguments(info r); [...] }
Change dealias to
underlying_entity_of in the second
note that follows paragraph 2.
[ Note 2: The behavior of many of the functions specified in namespace
std::metahave semantics that can be affected by the completness of class types represented by reflection values. For such functions, for any reflectionrsuch thatdealiasunderlying_entity_of(r)represents a specialization of a templated class with a reachable definition, the specialization is implicitly instantiated ([temp.inst]). — end note ]
Modify operator_of to account for
entity proxies:
consteval operators operator_of(info r);2 Constant When:
underlying_entity_of(r)represents an operator function or operator function template.
Modify has_identifier to account
for entity proxies:
consteval bool has_identifier(info r);1 Returns:
[ Drafting note: For several metafunctions, we do not want entity
proxies to “act like” their underlying entity (e.g.,
members_of should not enumerate the
members of the underlying entity of an entity proxy). For that reason,
underlying_entity_of(r)
is too blunt of a tool for specifying such functions.
We instead introduce an exposition-only
DEALIAS function, which
maps reflections of entity proxies to the null reflection. This causes
entity proxies to fail the preconditions for functions that should not
apply to them, yielding a useful wrapper around
underlying_entity_of for purposes of
specification.
Other than adding new functions and renaming
dealias to
underlying_entity_of, the only other
noteworthy change is that members_of
also returns entity proxies. ]
Introduce a metasyntactic function following
has-type to “unwrap” a
reflection to its underlying entity, while excluding entity proxies.
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 DEALIAS(info r); // exposition only* Returns:
underlying_entity_of(r)ifrrepresents an entity that is not an entity proxy. Otherwise, the null reflection.
Disallow entity proxies from being used with
value_of.
consteval info value_of(info r);7 Let
Rbe a constant expression of typeinfosuch thatR ==DEALIAS(r)istrue.
Specify is_const and
is_volatile in terms of
DEALIAS.
consteval bool is_const(info r); consteval bool is_volatile(info r);19 Let
Tbetype_of(r)ifhas-type(r)istrue. Otherwise, letTbedealiasDEALIAS(r).
Also specify
is_lvalue_reference_qualified and
is_rvalue_reference_qualified in
terms of DEALIAS.
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, letTbedealiasDEALIAS(r).
Change dealias to
DEALIAS for
is_complete_type:
26 Returns:
trueifis_type(r)istrueand there is some point in the evaluation context from which the type represented bydealiasDEALIAS(r)is not an incomplete type ([basic.types]). Otherwise,false.
Account for entity proxies in the specification of
is_enumerable_type.
consteval bool is_enumerable_type(info r);27 A type
Tis enumerable from a pointPif either […]28 Returns:
trueifdealiasDEALIAS(r)represents a type that is enumerable from some point in the evaluation context. Otherwise,false.
Use DEALIAS in
is_type and
is_namespace. Specify the behavior
of is_entity_proxy following the
specification of
is_namespace_alias:
consteval bool is_type(info r); consteval bool is_namespace(info r);30 Returns:
trueifdealiasDEALIAS(r)represents 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 a namespace alias, respectively [ Note 4: A specialization of an alias template is a type alias — end note ]. Otherwise,false.consteval bool is_entity_proxy(info r);31+ Returns:
trueifrrepresents an entity proxy. Otherwise,false.
Change dealias to
underlying_entity_of and add
proxied_entity_of following the
specification of
underlying_entity_of:
consteval infodealiasunderlying_entity_of(info r);45 Constant When:
rrepresents an entity.46 Returns: A reflection representing the underlying entity of what
rrepresents.[ Example 1:— end example ]using X = int; using Y = X; static_assert(dealiasunderlying_entity_of(^^int) == ^^int); static_assert(dealiasunderlying_entity_of(^^X) == ^^int); static_assert(dealiasunderlying_entity_of(^^Y) == ^^int);
:::
Specify in terms of
DEALIAS to disallow entity
proxies from several functions. Make entity proxies
Q-members-of-representable.
consteval vector<info> members_of(info r, access_context ctx);1 Constant When:
dealiasDEALIAS(r)is a reflection representing either a class type that is complete from some point in the evaluation context or a namespace.[…]
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) […]
- (4.10) a namespace,
or- (4.11) a namespace alias, or
- (4.12) an entity proxy.
5 Returns: […]
consteval vector<info> bases_of(info type, access_context ctx);6 Constant When:
dealiasDEALIAS(type) represents a class type that is complete from some point in the evaluation context.7 Returns: […]
consteval vector<info> static_data_members_of(info type, access_context ctx);8 Constant When:
dealiasDEALIAS(type)represents a class type that is complete from some point in the evaluation context.9 Returns: […]
consteval vector<info> static_data_members_of(info type, access_context ctx);10 Constant When:
dealiasDEALIAS(type)represents a class type that is complete from some point in the evaluation context.11 Returns: […]
consteval vector<info> enumerators_of(info type_enum);8 Constant When:
dealiasDEALIAS(type_enum)represents an enumeration type andis_enumerable_type(type_enum)istrue.9 Returns: […]
Specify in terms of
DEALIAS instead of
dealias to disallow entity proxies
from several functions.
consteval size_t size_of(info r);5 Constant When:
dealiasDEALIAS(r)is a reflection of a type, object, value, variable of non-reference type, […][…]
consteval size_t alignment_of(info r);7 Constant When:
dealiasDEALIAS(r)is a reflection of a type, object, variable of non-reference type, […]8 Returns: […]
consteval size_t bit_size_of(info r);9 Constant When:
dealiasDEALIAS(r)is a reflection of a type, object, value, variable of non-reference type, […]
Specify in terms of
DEALIAS instead of
dealias to disallow entity proxies
from several functions.
consteval info data_member_spec(info type, data_member_options options);4 Constant When:
^^Using::Decl
ill-formed for
using-declarators[ Drafting note: All wording assumes D2996R13. ]
Extend paragraph 5 to state that a
reflect-expression is
ill-formed when it names a
using-declarator.
5 If a
reflect-expressionRmatches the form^^ qualified-reflection-name, it is interpreted as such and its representation is determined as follows:
- (5.*) If lookup for
identifierfinds a declaration that replaced ausing-declaratorduring a single search ([basic.lookup.general], [namespace.udecl]),Ris ill-formed.- (5.1) Otherwise,
Iif theidentifieris anamespace-namethat names a namespace alias ([namespace.alias]),Rrepresents that namespace alias. For any othernamespace-name,Rrepresents the denoted namespace.- (5.2) […]