7 Expressions [expr]

7.5 Primary expressions [expr.prim]

7.5.5 Names [expr.prim.id]

7.5.5.1 General [expr.prim.id.general]

An id-expression is a restricted form of a primary-expression.
[Note 1:  — end note]
If an id-expression E denotes a non-static non-type member of some class C at a point where the current class ([expr.prim.this]) is X and the id-expression is transformed into a class member access expression using (*this) as the object expression.
If this transformation occurs in the predicate of a precondition assertion of a constructor of X or a postcondition assertion of a destructor of X, the expression is ill-formed.
[Note 2: 
If C is not X or a base class of X, the class member access expression is ill-formed.
Also, if the id-expression occurs within a static or explicit object member function, the class member access is ill-formed.
— end note]
This transformation does not apply in the template definition context ([temp.dep.type]).
[Example 1: struct C { bool b; C() pre(b) // error pre(&this->b) // OK pre(sizeof(b) > 0); // OK, b is not potentially evaluated. }; — end example]
If an id-expression E denotes a member M of an anonymous union ([class.union.anon]) U:
  • If U is a non-static data member, E refers to M as a member of the lookup context of the terminal name of E (after any implicit transformation to a class member access expression).
    [Example 2: 
    o.x is interpreted as o.u.x, where u names the anonymous union member.
    — end example]
  • Otherwise, E is interpreted as a class member access ([expr.ref]) that designates the member subobject M of the anonymous union variable for U.
    [Note 3: 
    Under this interpretation, E no longer denotes a non-static data member.
    — end note]
    [Example 3: 
    N​::​x is interpreted as N​::​u.x, where u names the anonymous union variable.
    — end example]
An id-expression that denotes a non-static data member or implicit object member function of a class can only be used:
  • 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
  • to form a pointer to member ([expr.unary.op]), or
  • if that id-expression denotes a non-static data member and it appears in an unevaluated operand.
    [Example 4: struct S { int m; }; int i = sizeof(S::m); // OK int j = sizeof(S::m + 42); // OK — end example]
For an id-expression that denotes an overload set, overload resolution is performed to select a unique function ([over.match], [over.over]).
[Note 4: 
A program cannot refer to a function with a trailing requires-clause whose constraint-expression is not satisfied, because such functions are never selected by overload resolution.
[Example 5: template<typename T> struct A { static void f(int) requires false; }; void g() { A<int>::f(0); // error: cannot call f void (*p1)(int) = A<int>::f; // error: cannot take the address of f decltype(A<int>::f)* p2 = nullptr; // error: the type decltype(A<int>​::​f) is invalid }
In each case, the constraints of f are not satisfied.
In the declaration of p2, those constraints need to be satisfied even though f is an unevaluated operand.
— end example]
— end note]

7.5.5.2 Unqualified names [expr.prim.id.unqual]

An identifier is only an id-expression if it has been suitably declared ([dcl]) or if it appears as part of a declarator-id ([dcl.decl]).
[Note 1: 
A type-name or computed-type-specifier prefixed by ~ denotes the destructor of the type so named; see [expr.prim.id.dtor].
— end note]
A component name of an unqualified-id U is
[Note 2: 
Other constructs that contain names to look up can have several component names ([expr.prim.id.qual], [dcl.type.simple], [dcl.type.elab], [dcl.mptr], [namespace.udecl], [temp.param], [temp.names], [temp.res]).
— end note]
The terminal name of a construct is the component name of that construct that appears lexically last.
The result is the entity denoted by the unqualified-id ([basic.lookup.unqual]).
If then the type of the expression is the type of a class member access expression ([expr.ref]) naming the non-static data member that would be declared for such a capture in the object parameter ([dcl.fct]) of the function call operator of E.
[Note 3: 
If E is not declared mutable, the type of such an identifier will typically be const qualified.
— end note]
Otherwise, if the unqualified-id names a coroutine parameter, the type of the expression is that of the copy of the parameter ([dcl.fct.def.coroutine]), and the result is that copy.
Otherwise, if the unqualified-id names a result binding ([dcl.contract.res]) attached to a function f with return type U,
  • if U is “reference to T”, then the type of the expression is const T;
  • otherwise, the type of the expression is const U.
Otherwise, if the unqualified-id appears in the predicate of a contract assertion C ([basic.contract]) and the entity is
  • a variable declared outside of C of object type T,
  • a variable or template parameter declared outside of C of type “reference to T”, or
  • a structured binding of type T whose corresponding variable is declared outside of C,
then the type of the expression is const T.
[Example 1: int n = 0; struct X { bool m(); }; struct Y { int z = 0; void f(int i, int* p, int& r, X x, X* px) pre (++n) // error: attempting to modify const lvalue pre (++i) // error: attempting to modify const lvalue pre (++(*p)) // OK pre (++r) // error: attempting to modify const lvalue pre (x.m()) // error: calling non-const member function pre (px->m()) // OK pre ([=,&i,*this] mutable { ++n; // error: attempting to modify const lvalue ++i; // error: attempting to modify const lvalue ++p; // OK, refers to member of closure type ++r; // OK, refers to non-reference member of closure type ++this->z; // OK, captured *this ++z; // OK, captured *this int j = 17; [&]{ int k = 34; ++i; // error: attempting to modify const lvalue ++j; // OK ++k; // OK }(); return true; }()); template <int N, int& R, int* P> void g() pre(++N) // error: attempting to modify prvalue pre(++R) // error: attempting to modify const lvalue pre(++(*P)); // OK int h() post(r : ++r) // error: attempting to modify const lvalue post(r: [=] mutable { ++r; // OK, refers to member of closure type return true; }()); int& k() post(r : ++r); // error: attempting to modify const lvalue }; — end example]
Otherwise, if the entity is a template parameter object for a template parameter of type T ([temp.param]), the type of the expression is const T.
In all other cases, the type of the expression is the type of the entity.
[Note 4: 
The type will be adjusted as described in [expr.type] if it is cv-qualified or is a reference type.
— end note]
The expression is an xvalue if it is move-eligible (see below); an lvalue if the entity is a function, variable, structured binding ([dcl.struct.bind]), result binding ([dcl.contract.res]), data member, or template parameter object; and a prvalue otherwise ([basic.lval]); it is a bit-field if the identifier designates a bit-field.
If an id-expression E appears in the predicate of a function contract assertion attached to a function f and denotes a function parameter of f and the implementation introduces any temporary objects to hold the value of that parameter as specified in [class.temporary],
  • if the contract assertion is a precondition assertion and the evaluation of the precondition assertion is sequenced before the initialization of the parameter object, E refers to the most recently initialized such temporary object, and
  • if the contract assertion is a postcondition assertion, it is unspecified whether E refers to one of the temporary objects or the parameter object; the choice is consistent within a single evaluation of a postcondition assertion.
If an id-expression E names a result binding in a postcondition assertion and the implementation introduces any temporary objects to hold the result object as specified in [class.temporary], and the postcondition assertion is sequenced before the initialization of the result object ([expr.call]), E refers to the most recently initialized such temporary object.
[Example 2: void f() { float x, &r = x; [=]() -> decltype((x)) { // lambda returns float const& because this lambda is not mutable and // x is an lvalue decltype(x) y1; // y1 has type float decltype((x)) y2 = y1; // y2 has type float const& decltype(r) r1 = y1; // r1 has type float& decltype((r)) r2 = y2; // r2 has type float const& return y2; }; [=](decltype((x)) y) { decltype((x)) z = x; // OK, y has type float&, z has type float const& }; [=] { [](decltype((x)) y) {}; // OK, lambda takes a parameter of type float const& [x=1](decltype((x)) y) { decltype((x)) z = x; // OK, y has type int&, z has type int const& }; }; } — end example]
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-expression is move-eligible if

7.5.5.3 Qualified names [expr.prim.id.qual]

The component names of a qualified-id are those of its nested-name-specifier and unqualified-id.
The component names of a nested-name-specifier are its identifier (if any) and those of its type-name, namespace-name, simple-template-id, and/or nested-name-specifier.
A declarative nested-name-specifier shall not have a computed-type-specifier.
A declaration that uses a declarative nested-name-specifier shall be a friend declaration or inhabit a scope that contains the entity being redeclared or specialized.
The nested-name-specifier ​::​ nominates the global namespace.
A nested-name-specifier with a computed-type-specifier nominates the type denoted by the computed-type-specifier, which shall be a class or enumeration type.
If a nested-name-specifier N is declarative and has a simple-template-id with a template argument list A that involves a template parameter, let T be the template nominated by N without A.
T shall be a class template.
Any other nested-name-specifier nominates the entity denoted by its type-name, namespace-name, identifier, or simple-template-id.
If the nested-name-specifier is not declarative, the entity shall not be a template.
A qualified-id shall not be of the form nested-name-specifier template ~ computed-type-specifier nor of the form computed-type-specifier ​::​ ~ type-name.
The result of a qualified-id Q is the entity it denotes ([basic.lookup.qual]).
If Q appears in the predicate of a contract assertion C ([basic.contract]) and the entity is
  • a variable declared outside of C of object type T,
  • a variable declared outside of C of type “reference to T”, or
  • a structured binding of type T whose corresponding variable is declared outside of C,
then the type of the expression is const T.
Otherwise, the type of the expression is the type of the result.
The result is an lvalue if the member is
  • a function other than a non-static member function,
  • a non-static member function if Q is the operand of a unary & operator,
  • a variable,
  • a structured binding ([dcl.struct.bind]), or
  • a data member,
and a prvalue otherwise.

7.5.5.4 Pack indexing expression [expr.prim.pack.index]

The id-expression P in a pack-index-expression shall be an identifier that denotes a pack.
The constant-expression shall be a converted constant expression ([expr.const]) of type std​::​size_t whose value V, termed the index, is such that .
[Note 1: 
A pack-index-expression denotes the element of the pack.
— end note]

7.5.5.5 Destruction [expr.prim.id.dtor]

An id-expression that denotes the destructor of a type T names the destructor of T if T is a class type ([class.dtor]), otherwise the id-expression is said to name a pseudo-destructor.
If the id-expression names a pseudo-destructor, T shall be a scalar type and the id-expression shall appear as the right operand of a class member access ([expr.ref]) that forms the postfix-expression of a function call ([expr.call]).
[Note 1: 
Such a call ends the lifetime of the object ([expr.call], [basic.life]).
— end note]
[Example 1: struct C { }; void f() { C * pc = new C; using C2 = C; pc->C::~C2(); // OK, destroys *pc C().C::~C(); // undefined behavior: temporary of type C destroyed twice using T = int; 0 .T::~T(); // OK, no effect 0.T::~T(); // error: 0.T is a user-defined-floating-point-literal ([lex.ext]) } — end example]