9 Declarations [dcl.dcl]

9.2 Specifiers [dcl.spec]

The specifiers that can be used in a declaration are
The optional attribute-specifier-seq in a decl-specifier-seq appertains to the type determined by the preceding decl-specifiers ([dcl.meaning]).
The attribute-specifier-seq affects the type only for the declaration it appears in, not other declarations involving the same type.
Each decl-specifier shall appear at most once in a complete decl-specifier-seq, except that long may appear twice.
At most one of the constexpr, consteval, and constinit keywords shall appear in a decl-specifier-seq.
If a type-name is encountered while parsing a decl-specifier-seq, it is interpreted as part of the decl-specifier-seq if and only if there is no previous defining-type-specifier other than a cv-qualifier in the decl-specifier-seq.
The sequence shall be self-consistent as described below.
[Example
:
typedef char* Pc;
static Pc;                      // error: name missing
Here, the declaration static Pc is ill-formed because no name was specified for the static variable of type Pc.
To get a variable called Pc, a type-specifier (other than const or volatile) has to be present to indicate that the typedef-name Pc is the name being (re)declared, rather than being part of the decl-specifier sequence.
For another example,
void f(const Pc);               // void f(char* const) (not const char*)
void g(const int Pc);           // void g(const int)
— end example
]
[Note
:
Since signed, unsigned, long, and short by default imply int, a type-name appearing after one of those specifiers is treated as the name being (re)declared.
[Example
:
void h(unsigned Pc);            // void h(unsigned int)
void k(unsigned int Pc);        // void k(unsigned int)
— end example
]
— end note
]

9.2.1 Storage class specifiers [dcl.stc]

The storage class specifiers are
storage-class-specifier:
static
thread_­local
extern
mutable
At most one storage-class-specifier shall appear in a given decl-specifier-seq, except that thread_­local may appear with static or extern.
If thread_­local appears in any declaration of a variable it shall be present in all declarations of that entity.
If a storage-class-specifier appears in a decl-specifier-seq, there can be no typedef specifier in the same decl-specifier-seq and the init-declarator-list or member-declarator-list of the declaration shall not be empty (except for an anonymous union declared in a named namespace or in the global namespace, which shall be declared static ([class.union.anon])).
The storage-class-specifier applies to the name declared by each init-declarator in the list and not to any names declared by other specifiers.
[Note
:
See [temp.expl.spec] and [temp.explicit] for restrictions in explicit specializations and explicit instantiations, respectively.
— end note
]
[Note
:
A variable declared without a storage-class-specifier at block scope or declared as a function parameter has automatic storage duration by default.
— end note
]
The thread_­local specifier indicates that the named entity has thread storage duration.
It shall be applied only to the declaration of a variable of namespace or block scope, to a structured binding declaration ([dcl.struct.bind]), or to the declaration of a static data member.
When thread_­local is applied to a variable of block scope the storage-class-specifier static is implied if no other storage-class-specifier appears in the decl-specifier-seq.
The static specifier shall be applied only to the declaration of a variable or function, to a structured binding declaration ([dcl.struct.bind]), or to the declaration of an anonymous union ([class.union.anon]).
There can be no static function declarations within a block, nor any static function parameters.
A static specifier used in the declaration of a variable declares the variable to have static storage duration, unless accompanied by the thread_­local specifier, which declares the variable to have thread storage duration.
A static specifier can be used in declarations of class members; [class.static] describes its effect.
For the linkage of a name declared with a static specifier, see [basic.link].
The extern specifier shall be applied only to the declaration of a variable or function.
The extern specifier shall not be used in the declaration of a class member or function parameter.
For the linkage of a name declared with an extern specifier, see [basic.link].
[Note
:
The extern keyword can also be used in explicit-instantiations and linkage-specifications, but it is not a storage-class-specifier in such contexts.
— end note
]
The linkages implied by successive declarations for a given entity shall agree.
That is, within a given scope, each declaration declaring the same variable name or the same overloading of a function name shall imply the same linkage.
[Example
:
static char* f();               // f() has internal linkage
char* f()                       // f() still has internal linkage
  { /* ... */ }

char* g();                      // g() has external linkage
static char* g()                // error: inconsistent linkage
  { /* ... */ }

void h();
inline void h();                // external linkage

inline void l();
void l();                       // external linkage

inline void m();
extern void m();                // external linkage

static void n();
inline void n();                // internal linkage

static int a;                   // a has internal linkage
int a;                          // error: two definitions

static int b;                   // b has internal linkage
extern int b;                   // b still has internal linkage

int c;                          // c has external linkage
static int c;                   // error: inconsistent linkage

extern int d;                   // d has external linkage
static int d;                   // error: inconsistent linkage
— end example
]
The name of a declared but undefined class can be used in an extern declaration.
Such a declaration can only be used in ways that do not require a complete class type.
[Example
:
struct S;
extern S a;
extern S f();
extern void g(S);

void h() {
  g(a);                         // error: S is incomplete
  f();                          // error: S is incomplete
}
— end example
]
The mutable specifier shall appear only in the declaration of a non-static data member whose type is neither const-qualified nor a reference type.
[Example
:
class X {
  mutable const int* p;         // OK
  mutable int* const q;         // error
};
— end example
]
[Note
:
The mutable specifier on a class data member nullifies a const specifier applied to the containing class object and permits modification of the mutable class member even though the rest of the object is const ([basic.type.qualifier], [dcl.type.cv]).
— end note
]

9.2.2 Function specifiers [dcl.fct.spec]

A function-specifier can be used only in a function declaration.
The virtual specifier shall be used only in the initial declaration of a non-static class member function; see [class.virtual].
An explicit-specifier shall be used only in the declaration of a constructor or conversion function within its class definition; see [class.conv.ctor] and [class.conv.fct].
In an explicit-specifier, the constant-expression, if supplied, shall be a contextually converted constant expression of type bool ([expr.const]).
The explicit-specifier explicit without a constant-expression is equivalent to the explicit-specifier explicit(true).
If the constant expression evaluates to true, the function is explicit.
Otherwise, the function is not explicit.
A ( token that follows explicit is parsed as part of the explicit-specifier.

9.2.3 The typedef specifier [dcl.typedef]

Declarations containing the decl-specifier typedef declare identifiers that can be used later for naming fundamental or compound types.
The typedef specifier shall not be combined in a decl-specifier-seq with any other kind of specifier except a defining-type-specifier, and it shall not be used in the decl-specifier-seq of a parameter-declaration nor in the decl-specifier-seq of a function-definition ([dcl.fct.def]).
If a typedef specifier appears in a declaration without a declarator, the program is ill-formed.
A name declared with the typedef specifier becomes a typedef-name.
A typedef-name names the type associated with the identifier ([dcl.decl]) or simple-template-id ([temp.pre]); a typedef-name is thus a synonym for another type.
A typedef-name does not introduce a new type the way a class declaration ([class.name]) or enum declaration ([dcl.enum]) does.
[Example
:
After
typedef int MILES, *KLICKSP;
the constructions
MILES distance;
extern KLICKSP metricp;
are all correct declarations; the type of distance is int and that of metricp is “pointer to int.
— end example
]
A typedef-name can also be introduced by an alias-declaration.
The identifier following the using keyword becomes a typedef-name and the optional attribute-specifier-seq following the identifier appertains to that typedef-name.
Such a typedef-name has the same semantics as if it were introduced by the typedef specifier.
In particular, it does not define a new type.
[Example
:
using handler_t = void (*)(int);
extern handler_t ignore;
extern void (*ignore)(int);         // redeclare ignore
using cell = pair<void*, cell*>;    // error
— end example
]
The defining-type-specifier-seq of the defining-type-id shall not define a class or enumeration if the alias-declaration is the declaration of a template-declaration.
In a given non-class scope, a typedef specifier can be used to redeclare the name of any type declared in that scope to refer to the type to which it already refers.
[Example
:
typedef struct s { /* ... */ } s;
typedef int I;
typedef int I;
typedef I I;
— end example
]
In a given class scope, a typedef specifier can be used to redeclare any class-name declared in that scope that is not also a typedef-name to refer to the type to which it already refers.
[Example
:
struct S {
  typedef struct A { } A;       // OK
  typedef struct B B;           // OK
  typedef A A;                  // error
};
— end example
]
If a typedef specifier is used to redeclare in a given scope an entity that can be referenced using an elaborated-type-specifier, the entity can continue to be referenced by an elaborated-type-specifier or as an enumeration or class name in an enumeration or class definition respectively.
[Example
:
struct S;
typedef struct S S;
int main() {
  struct S* p;                  // OK
}
struct S { };                   // OK
— end example
]
In a given scope, a typedef specifier shall not be used to redeclare the name of any type declared in that scope to refer to a different type.
[Example
:
class complex { /* ... */ };
typedef int complex;            // error: redefinition
— end example
]
Similarly, in a given scope, a class or enumeration shall not be declared with the same name as a typedef-name that is declared in that scope and refers to a type other than the class or enumeration itself.
[Example
:
typedef int complex;
class complex { /* ... */ };    // error: redefinition
— end example
]
A simple-template-id is only a typedef-name if its template-name names an alias template or a template template-parameter.
[Note
:
A simple-template-id that names a class template specialization is a class-name ([class.name]).
If a typedef-name is used to identify the subject of an elaborated-type-specifier, a class definition, a constructor declaration, or a destructor declaration, the program is ill-formed.
— end note
]
[Example
:
struct S {
  S();
  ~S();
};

typedef struct S T;

S a = T();                      // OK
struct T * p;                   // error
— end example
]
If the typedef declaration defines an unnamed class or enumeration, the first typedef-name declared by the declaration to be that type is used to denote the type for linkage purposes only ([basic.link]).
[Note
:
A typedef declaration involving a lambda-expression does not itself define the associated closure type, and so the closure type is not given a name for linkage purposes.
— end note
]
[Example
:
typedef struct { } *ps, S;      // S is the class name for linkage purposes
typedef decltype([]{}) C;       // the closure type has no name for linkage purposes
— end example
]
An unnamed class with a typedef name for linkage purposes shall not
  • declare any members other than non-static data members, member enumerations, or member classes,
  • have any base classes or default member initializers, or
  • contain a lambda-expression,
and all member classes shall also satisfy these requirements (recursively).
[Example
:
typedef struct {
  int f() {}
} X;                            // error: struct with typedef name for linkage has member functions
— end example
]

9.2.4 The friend specifier [dcl.friend]

The friend specifier is used to specify access to class members; see [class.friend].

9.2.5 The constexpr and consteval specifiers [dcl.constexpr]

The constexpr specifier shall be applied only to the definition of a variable or variable template or the declaration of a function or function template.
The consteval specifier shall be applied only to the declaration of a function or function template.
A function or static data member declared with the constexpr or consteval specifier is implicitly an inline function or variable.
If any declaration of a function or function template has a constexpr or consteval specifier, then all its declarations shall contain the same specifier.
[Note
:
An explicit specialization can differ from the template declaration with respect to the constexpr or consteval specifier.
— end note
]
[Note
:
Function parameters cannot be declared constexpr.
— end note
]
[Example
:
constexpr void square(int &x);  // OK: declaration
constexpr int bufsz = 1024;     // OK: definition
constexpr struct pixel {        // error: pixel is a type
  int x;
  int y;
  constexpr pixel(int);         // OK: declaration
};
constexpr pixel::pixel(int a)
  : x(a), y(x)                  // OK: definition
  { square(x); }
constexpr pixel small(2);       // error: square not defined, so small(2)
                                // not constant ([expr.const]) so constexpr not satisfied

constexpr void square(int &x) { // OK: definition
  x *= x;
}
constexpr pixel large(4);       // OK: square defined
int next(constexpr int x) {     // error: not for parameters
     return x + 1;
}
extern constexpr int memsz;     // error: not a definition
— end example
]
A constexpr or consteval specifier used in the declaration of a function declares that function to be a constexpr function.
A function or constructor declared with the consteval specifier is called an immediate function.
A destructor, an allocation function, or a deallocation function shall not be declared with the consteval specifier.
The definition of a constexpr function shall satisfy the following requirements:
[Example
:
constexpr int square(int x)
  { return x * x; }             // OK
constexpr long long_max()
  { return 2147483647; }        // OK
constexpr int abs(int x) {
  if (x < 0)
    x = -x;
  return x;                     // OK
}
constexpr int first(int n) {
  static int value = n;         // error: variable has static storage duration
  return value;
}
constexpr int uninit() {
  struct { int a; } s;
  return s.a;                   // error: uninitialized read of s.a
}
constexpr int prev(int x)
  { return --x; }               // OK
constexpr int g(int x, int n) { // OK
  int r = 1;
  while (--n > 0) r *= x;
  return r;
}
— end example
]
The definition of a constexpr constructor whose function-body is not = delete shall additionally satisfy the following requirements:
  • for a non-delegating constructor, every constructor selected to initialize non-static data members and base class subobjects shall be a constexpr constructor;
  • for a delegating constructor, the target constructor shall be a constexpr constructor.
[Example
:
struct Length {
  constexpr explicit Length(int i = 0) : val(i) { }
private:
  int val;
};
— end example
]
The definition of a constexpr destructor whose function-body is not = delete shall additionally satisfy the following requirement:
  • for every subobject of class type or (possibly multi-dimensional) array thereof, that class type shall have a constexpr destructor.
For a constexpr function or constexpr constructor that is neither defaulted nor a template, if no argument values exist such that an invocation of the function or constructor could be an evaluated subexpression of a core constant expression, or, for a constructor, an evaluated subexpression of the initialization full-expression of some constant-initialized object ([basic.start.static]), the program is ill-formed, no diagnostic required.
[Example
:
constexpr int f(bool b)
  { return b ? throw 0 : 0; }           // OK
constexpr int f() { return f(true); }   // ill-formed, no diagnostic required

struct B {
  constexpr B(int x) : i(0) { }         // x is unused
  int i;
};

int global;

struct D : B {
  constexpr D() : B(global) { }         // ill-formed, no diagnostic required
                                        // lvalue-to-rvalue conversion on non-constant global
};
— end example
]
If the instantiated template specialization of a constexpr function template or member function of a class template would fail to satisfy the requirements for a constexpr function, that specialization is still a constexpr function, even though a call to such a function cannot appear in a constant expression.
If no specialization of the template would satisfy the requirements for a constexpr function when considered as a non-template function, the template is ill-formed, no diagnostic required.
An invocation of a constexpr function in a given context produces the same result as an invocation of an equivalent non-constexpr function in the same context in all respects except that
[Note
:
Declaring a function constexpr can change whether an expression is a constant expression.
This can indirectly cause calls to std​::​is_­constant_­evaluated within an invocation of the function to produce a different value.
— end note
]
The constexpr and consteval specifiers have no effect on the type of a constexpr function.
[Example
:
constexpr int bar(int x, int y)         // OK
    { return x + y + x*y; }
// ...
int bar(int x, int y)                   // error: redefinition of bar
    { return x * 2 + 3 * y; }
— end example
]
A constexpr specifier used in an object declaration declares the object as const.
Such an object shall have literal type and shall be initialized.
In any constexpr variable declaration, the full-expression of the initialization shall be a constant expression.
A constexpr variable shall have constant destruction.
[Example
:
struct pixel {
  int x, y;
};
constexpr pixel ur = { 1294, 1024 };    // OK
constexpr pixel origin;                 // error: initializer missing
— end example
]

9.2.6 The constinit specifier [dcl.constinit]

The constinit specifier shall be applied only to a declaration of a variable with static or thread storage duration.
If the specifier is applied to any declaration of a variable, it shall be applied to the initializing declaration.
No diagnostic is required if no constinit declaration is reachable at the point of the initializing declaration.
If a variable declared with the constinit specifier has dynamic initialization ([basic.start.dynamic]), the program is ill-formed.
[Note
:
The constinit specifier ensures that the variable is initialized during static initialization ([basic.start.static]).
— end note
]
[Example
:
const char * g() { return "dynamic initialization"; }
constexpr const char * f(bool p) { return p ? "constant initializer" : g(); }
constinit const char * c = f(true);     // OK
constinit const char * d = f(false);    // error
— end example
]

9.2.7 The inline specifier [dcl.inline]

The inline specifier shall be applied only to the declaration of a variable or function.
A function declaration ([dcl.fct], [class.mfct], [class.friend]) with an inline specifier declares an inline function.
The inline specifier indicates to the implementation that inline substitution of the function body at the point of call is to be preferred to the usual function call mechanism.
An implementation is not required to perform this inline substitution at the point of call; however, even if this inline substitution is omitted, the other rules for inline functions specified in this subclause shall still be respected.
[Note
:
The inline keyword has no effect on the linkage of a function.
In certain cases, an inline function cannot use names with internal linkage; see [basic.link].
— end note
]
A variable declaration with an inline specifier declares an inline variable.
The inline specifier shall not appear on a block scope declaration or on the declaration of a function parameter.
If the inline specifier is used in a friend function declaration, that declaration shall be a definition or the function shall have previously been declared inline.
If a definition of a function or variable is reachable at the point of its first declaration as inline, the program is ill-formed.
If a function or variable with external or module linkage is declared inline in one definition domain, an inline declaration of it shall be reachable from the end of every definition domain in which it is declared; no diagnostic is required.
[Note
:
A call to an inline function or a use of an inline variable may be encountered before its definition becomes reachable in a translation unit.
— end note
]
[Note
:
An inline function or variable with external or module linkage has the same address in all translation units.
A static local variable in an inline function with external or module linkage always refers to the same object.
A type defined within the body of an inline function with external or module linkage is the same type in every translation unit.
— end note
]
If an inline function or variable that is attached to a named module is declared in a definition domain, it shall be defined in that domain.
[Note
:
A constexpr function is implicitly inline.
In the global module, a function defined within a class definition is implicitly inline ([class.mfct], [class.friend]).
— end note
]

9.2.8 Type specifiers [dcl.type]

As a general rule, at most one defining-type-specifier is allowed in the complete decl-specifier-seq of a declaration or in a defining-type-specifier-seq, and at most one type-specifier is allowed in a type-specifier-seq.
The only exceptions to this rule are the following:
  • const can be combined with any type specifier except itself.
  • volatile can be combined with any type specifier except itself.
  • signed or unsigned can be combined with char, long, short, or int.
  • short or long can be combined with int.
  • long can be combined with double.
  • long can be combined with long.
Except in a declaration of a constructor, destructor, or conversion function, at least one defining-type-specifier that is not a cv-qualifier shall appear in a complete type-specifier-seq or a complete decl-specifier-seq.86
[Note
:
enum-specifiers, class-specifiers, and typename-specifiers are discussed in [dcl.enum], [class], and [temp.res], respectively.
The remaining type-specifiers are discussed in the rest of this subclause.
— end note
]
There is no special provision for a decl-specifier-seq that lacks a type-specifier or that has a type-specifier that only specifies cv-qualifiers.
The “implicit int” rule of C is no longer supported.

9.2.8.1 The cv-qualifiers [dcl.type.cv]

There are two cv-qualifiers, const and volatile.
Each cv-qualifier shall appear at most once in a cv-qualifier-seq.
If a cv-qualifier appears in a decl-specifier-seq, the init-declarator-list or member-declarator-list of the declaration shall not be empty.
[Note
:
[basic.type.qualifier] and [dcl.fct] describe how cv-qualifiers affect object and function types.
— end note
]
Redundant cv-qualifications are ignored.
[Note
:
For example, these could be introduced by typedefs.
— end note
]
[Note
:
Declaring a variable const can affect its linkage ([dcl.stc]) and its usability in constant expressions.
As described in [dcl.init], the definition of an object or subobject of const-qualified type must specify an initializer or be subject to default-initialization.
— end note
]
A pointer or reference to a cv-qualified type need not actually point or refer to a cv-qualified object, but it is treated as if it does; a const-qualified access path cannot be used to modify an object even if the object referenced is a non-const object and can be modified through some other access path.
[Note
:
Cv-qualifiers are supported by the type system so that they cannot be subverted without casting.
— end note
]
Any attempt to modify ([expr.ass], [expr.post.incr], [expr.pre.incr]) a const object ([basic.type.qualifier]) during its lifetime ([basic.life]) results in undefined behavior.
[Example
:
const int ci = 3;                       // cv-qualified (initialized as required)
ci = 4;                                 // error: attempt to modify const

int i = 2;                              // not cv-qualified
const int* cip;                         // pointer to const int
cip = &i;                               // OK: cv-qualified access path to unqualified
*cip = 4;                               // error: attempt to modify through ptr to const

int* ip;
ip = const_cast<int*>(cip);             // cast needed to convert const int* to int*
*ip = 4;                                // defined: *ip points to i, a non-const object

const int* ciq = new const int (3);     // initialized as required
int* iq = const_cast<int*>(ciq);        // cast required
*iq = 4;                                // undefined behavior: modifies a const object
For another example,
struct X {
  mutable int i;
  int j;
};
struct Y {
  X x;
  Y();
};

const Y y;
y.x.i++;                                // well-formed: mutable member can be modified
y.x.j++;                                // error: const-qualified member modified
Y* p = const_cast<Y*>(&y);              // cast away const-ness of y
p->x.i = 99;                            // well-formed: mutable member can be modified
p->x.j = 99;                            // undefined behavior: modifies a const subobject
— end example
]
The semantics of an access through a volatile glvalue are implementation-defined.
If an attempt is made to access an object defined with a volatile-qualified type through the use of a non-volatile glvalue, the behavior is undefined.
[Note
:
volatile is a hint to the implementation to avoid aggressive optimization involving the object because the value of the object might be changed by means undetectable by an implementation.
Furthermore, for some implementations, volatile might indicate that special hardware instructions are required to access the object.
See [intro.execution] for detailed semantics.
In general, the semantics of volatile are intended to be the same in C++ as they are in C.
— end note
]

9.2.8.2 Simple type specifiers [dcl.type.simple]

The simple type specifiers are
A placeholder-type-specifier is a placeholder for a type to be deduced ([dcl.spec.auto]).
A type-specifier of the form typename nested-name-specifier template-name is a placeholder for a deduced class type ([dcl.type.class.deduct]).
The nested-name-specifier, if any, shall be non-dependent and the template-name shall name a deducible template.
A deducible template is either a class template or is an alias template whose defining-type-id is of the form
typename nested-name-specifier template simple-template-id
where the nested-name-specifier (if any) is non-dependent and the template-name of the simple-template-id names a deducible template.
[Note
:
An injected-class-name is never interpreted as a template-name in contexts where class template argument deduction would be performed ([temp.local]).
— end note
]
The other simple-type-specifiers specify either a previously-declared type, a type determined from an expression, or one of the fundamental types ([basic.fundamental]).
Table 14 summarizes the valid combinations of simple-type-specifiers and the types they specify.
Table 14: simple-type-specifiers and the types they specify   [tab:dcl.type.simple]
Specifier(s)
Type
type-name
the type named
simple-template-id
the type as defined in [temp.names]
decltype-specifier
the type as defined in [dcl.type.decltype]
placeholder-type-specifier
the type as defined in [dcl.spec.auto]
template-name
the type as defined in [dcl.type.class.deduct]
char
char
unsigned char
unsigned char
signed char
signed char
char8_­t
char8_­t
char16_­t
char16_­t
char32_­t
char32_­t
bool
bool
unsigned
unsigned int
unsigned int
unsigned int
signed
int
signed int
int
int
int
unsigned short int
unsigned short int
unsigned short
unsigned short int
unsigned long int
unsigned long int
unsigned long
unsigned long int
unsigned long long int
unsigned long long int
unsigned long long
unsigned long long int
signed long int
long int
signed long
long int
signed long long int
long long int
signed long long
long long int
long long int
long long int
long long
long long int
long int
long int
long
long int
signed short int
short int
signed short
short int
short int
short int
short
short int
wchar_­t
wchar_­t
float
float
double
double
long double
long double
void
void
When multiple simple-type-specifiers are allowed, they can be freely intermixed with other decl-specifiers in any order.
[Note
:
It is implementation-defined whether objects of char type are represented as signed or unsigned quantities.
The signed specifier forces char objects to be signed; it is redundant in other contexts.
— end note
]

9.2.8.3 Elaborated type specifiers [dcl.type.elab]

An attribute-specifier-seq shall not appear in an elaborated-type-specifier unless the latter is the sole constituent of a declaration.
If an elaborated-type-specifier is the sole constituent of a declaration, the declaration is ill-formed unless it is an explicit specialization ([temp.expl.spec]), an explicit instantiation ([temp.explicit]) or it has one of the following forms:
In the first case, the attribute-specifier-seq, if any, appertains to the class being declared; the attributes in the attribute-specifier-seq are thereafter considered attributes of the class whenever it is named.
[Note
:
[basic.lookup.elab] describes how name lookup proceeds for the identifier in an elaborated-type-specifier.
— end note
]
If the identifier or simple-template-id resolves to a class-name or enum-name, the elaborated-type-specifier introduces it into the declaration the same way a simple-type-specifier introduces its type-name.
[Note
:
This implies that, within a class template with a template type-parameter T, the declaration
friend class T;
is ill-formed.
However, the similar declaration friend T; is allowed ([class.friend]).
— end note
]
The class-key or enum keyword present in the elaborated-type-specifier shall agree in kind with the declaration to which the name in the elaborated-type-specifier refers.
This rule also applies to the form of elaborated-type-specifier that declares a class-name or friend class since it can be construed as referring to the definition of the class.
Thus, in any elaborated-type-specifier, the enum keyword shall be used to refer to an enumeration ([dcl.enum]), the union class-key shall be used to refer to a union ([class.union]), and either the class or struct class-key shall be used to refer to a non-union class ([class.pre]).
[Example
:
enum class E { a, b };
enum E x = E::a;                // OK
struct S { } s;
class S* p = &s;                // OK
— end example
]

9.2.8.4 Decltype specifiers [dcl.type.decltype]

For an expression E, the type denoted by decltype(E) is defined as follows:
  • if E is an unparenthesized id-expression naming a structured binding ([dcl.struct.bind]), decltype(E) is the referenced type as given in the specification of the structured binding declaration;
  • otherwise, if E is an unparenthesized id-expression naming a non-type template-parameter, decltype(E) is the type of the template-parameter after performing any necessary type deduction ([dcl.spec.auto], [dcl.type.class.deduct]);
  • otherwise, if E is an unparenthesized id-expression or an unparenthesized class member access ([expr.ref]), decltype(E) is the type of the entity named by E. If there is no such entity, or if E names a set of overloaded functions, the program is ill-formed;
  • otherwise, if E is an xvalue, decltype(E) is T&&, where T is the type of E;
  • otherwise, if E is an lvalue, decltype(E) is T&, where T is the type of E;
  • otherwise, decltype(E) is the type of E.
The operand of the decltype specifier is an unevaluated operand.
[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&
— end example
]
[Note
:
The rules for determining types involving decltype(auto) are specified in [dcl.spec.auto].
— end note
]
If the operand of a decltype-specifier is a prvalue and is not a (possibly parenthesized) immediate invocation ([expr.const]), the temporary materialization conversion is not applied ([conv.rval]) and no result object is provided for the prvalue.
The type of the prvalue may be incomplete or an abstract class type.
[Note
:
As a result, storage is not allocated for the prvalue and it is not destroyed.
Thus, a class type is not instantiated as a result of being the type of a function call in this context.
In this context, the common purpose of writing the expression is merely to refer to its type.
In that sense, a decltype-specifier is analogous to a use of a typedef-name, so the usual reasons for requiring a complete type do not apply.
In particular, it is not necessary to allocate storage for a temporary object or to enforce the semantic constraints associated with invoking the type's destructor.
— end note
]
[Note
:
Unlike the preceding rule, parentheses have no special meaning in this context.
— end note
]
[Example
:
template<class T> struct A { ~A() = delete; };
template<class T> auto h()
  -> A<T>;
template<class T> auto i(T)     // identity
  -> T;
template<class T> auto f(T)     // #1
  -> decltype(i(h<T>()));       // forces completion of A<T> and implicitly uses A<T>​::​~A()
                                // for the temporary introduced by the use of h().
                                // (A temporary is not introduced as a result of the use of i().)
template<class T> auto f(T)     // #2
  -> void;
auto g() -> void {
  f(42);                        // OK: calls #2. (#1 is not a viable candidate: type deduction
                                // fails ([temp.deduct]) because A<int>​::​~A() is implicitly used in its
                                // decltype-specifier)
}
template<class T> auto q(T)
  -> decltype((h<T>()));        // does not force completion of A<T>; A<T>​::​~A() is not implicitly
                                // used within the context of this decltype-specifier
void r() {
  q(42);                        // error: deduction against q succeeds, so overload resolution selects
                                // the specialization “q(T) -> decltype((h<T>()))” with Tint;
                                // the return type is A<int>, so a temporary is introduced and its
                                // destructor is used, so the program is ill-formed
}
— end example
]

9.2.8.5 Placeholder type specifiers [dcl.spec.auto]

A placeholder-type-specifier designates a placeholder type that will be replaced later by deduction from an initializer.
A placeholder-type-specifier of the form type-constraint auto can be used as a decl-specifier of the decl-specifier-seq of a parameter-declaration of a function declaration or lambda-expression and, if it is not the auto type-specifier introducing a trailing-return-type (see below), is a generic parameter type placeholder of the function declaration or lambda-expression.
[Note
:
Having a generic parameter type placeholder signifies that the function is an abbreviated function template ([dcl.fct]) or the lambda is a generic lambda ([expr.prim.lambda]).
— end note
]
The placeholder type can appear with a function declarator in the decl-specifier-seq, type-specifier-seq, conversion-function-id, or trailing-return-type, in any context where such a declarator is valid.
If the function declarator includes a trailing-return-type ([dcl.fct]), that trailing-return-type specifies the declared return type of the function.
Otherwise, the function declarator shall declare a function.
If the declared return type of the function contains a placeholder type, the return type of the function is deduced from non-discarded return statements, if any, in the body of the function ([stmt.if]).
The type of a variable declared using a placeholder type is deduced from its initializer.
This use is allowed in an initializing declaration ([dcl.init]) of a variable.
The placeholder type shall appear as one of the decl-specifiers in the decl-specifier-seq and the decl-specifier-seq shall be followed by one or more declarators, each of which shall be followed by a non-empty initializer.
In an initializer of the form
( expression-list )
the expression-list shall be a single assignment-expression.
[Example
:
auto x = 5;                     // OK: x has type int
const auto *v = &x, u = 6;      // OK: v has type const int*, u has type const int
static auto y = 0.0;            // OK: y has type double
auto int r;                     // error: auto is not a storage-class-specifier
auto f() -> int;                // OK: f returns int
auto g() { return 0.0; }        // OK: g returns double
auto h();                       // OK: h's return type will be deduced when it is defined
— end example
]
The auto type-specifier can also be used to introduce a structured binding declaration ([dcl.struct.bind]).
A placeholder type can also be used in the type-specifier-seq in the new-type-id or type-id of a new-expression and as a decl-specifier of the parameter-declaration's decl-specifier-seq in a template-parameter.
A program that uses a placeholder type in a context not explicitly allowed in this subclause is ill-formed.
If the init-declarator-list contains more than one init-declarator, they shall all form declarations of variables.
The type of each declared variable is determined by placeholder type deduction, and if the type that replaces the placeholder type is not the same in each deduction, the program is ill-formed.
[Example
:
auto x = 5, *y = &x;            // OK: auto is int
auto a = 5, b = { 1, 2 };       // error: different types for auto
— end example
]
If a function with a declared return type that contains a placeholder type has multiple non-discarded return statements, the return type is deduced for each such return statement.
If the type deduced is not the same in each deduction, the program is ill-formed.
If a function with a declared return type that uses a placeholder type has no non-discarded return statements, the return type is deduced as though from a return statement with no operand at the closing brace of the function body.
[Example
:
auto  f() { }                   // OK, return type is void
auto* g() { }                   // error: cannot deduce auto* from void()
— end example
]
An exported function with a declared return type that uses a placeholder type shall be defined in the translation unit containing its exported declaration, outside the private-module-fragment (if any).
[Note
:
The deduced return type cannot have a name with internal linkage ([basic.link]).
— end note
]
If the name of an entity with an undeduced placeholder type appears in an expression, the program is ill-formed.
Once a non-discarded return statement 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 other return statements.
[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
}
— end example
]
Return type deduction for a templated entity that is a function or function template with a placeholder in its declared type occurs when the definition is instantiated even if the function body contains a return statement with a non-type-dependent operand.
[Note
:
Therefore, any use of a specialization of the function template will cause an implicit instantiation.
Any errors that arise from this instantiation are not in the immediate context of the function type and can result in the program being ill-formed ([temp.deduct]).
— end note
]
[Example
:
template <class T> auto f(T t) { return t; }    // return type deduced at instantiation time
typedef decltype(f(1)) fint_t;                  // instantiates f<int> to deduce return type
template<class T> auto f(T* t) { return *t; }
void g() { int (*p)(int*) = &f; }               // instantiates both fs to determine return types,
                                                // chooses second
— end example
]
Redeclarations or specializations of a function or function template with a declared return type that uses a placeholder type shall also use that placeholder, not a deduced type.
Similarly, redeclarations or specializations of a function or function template with a declared return type that does not use a placeholder type shall not use a placeholder.
[Example
:
auto f();
auto f() { return 42; }                         // return type is int
auto f();                                       // OK
int f();                                        // error: cannot be overloaded with auto f()
decltype(auto) f();                             // error: auto and decltype(auto) don't match

template <typename T> auto g(T t) { return t; } // #1
template auto g(int);                           // OK, return type is int
template char g(char);                          // error: no matching template
template<> auto g(double);                      // OK, forward declaration with unknown return type

template <class T> T g(T t) { return t; }       // OK, not functionally equivalent to #1
template char g(char);                          // OK, now there is a matching template
template auto g(float);                         // still matches #1

void h() { return g(42); }                      // error: ambiguous

template <typename T> struct A {
  friend T frf(T);
};
auto frf(int i) { return i; }                   // not a friend of A<int>
extern int v;
auto v = 17;                                    // OK, redeclares v
struct S {
  static int i;
};
auto S::i = 23;                                 // OK
— end example
]
A function declared with a return type that uses a placeholder type shall not be virtual.
A function declared with a return type that uses a placeholder type shall not be a coroutine ([dcl.fct.def.coroutine]).
An explicit instantiation declaration does not cause the instantiation of an entity declared using a placeholder type, but it also does not prevent that entity from being instantiated as needed to determine its type.
[Example
:
template <typename T> auto f(T t) { return t; }
extern template auto f(int);    // does not instantiate f<int>
int (*p)(int) = f;              // instantiates f<int> to determine its return type, but an explicit
                                // instantiation definition is still required somewhere in the program
— end example
]

9.2.8.5.1 Placeholder type deduction [dcl.type.auto.deduct]

Placeholder type deduction is the process by which a type containing a placeholder type is replaced by a deduced type.
A type T containing a placeholder type, and a corresponding initializer E, are determined as follows:
  • for a non-discarded return statement that occurs in a function declared with a return type that contains a placeholder type, T is the declared return type and E is the operand of the return statement. If the return statement has no operand, then E is void();
  • for a variable declared with a type that contains a placeholder type, T is the declared type of the variable and E is the initializer. If the initialization is direct-list-initialization, the initializer shall be a braced-init-list containing only a single assignment-expression and E is the assignment-expression;
  • for a non-type template parameter declared with a type that contains a placeholder type, T is the declared type of the non-type template parameter and E is the corresponding template argument.
In the case of a return statement with no operand or with an operand of type void, T shall be either type-constraint decltype(auto) or cv type-constraint auto.
If the deduction is for a return statement and E is a braced-init-list ([dcl.init.list]), the program is ill-formed.
If the placeholder-type-specifier is of the form type-constraint auto, the deduced type replacing T is determined using the rules for template argument deduction.
Obtain P from T by replacing the occurrences of type-constraint auto either with a new invented type template parameter U or, if the initialization is copy-list-initialization, with std​::​initializer_­list<U>.
Deduce a value for U using the rules of template argument deduction from a function call, where P is a function template parameter type and the corresponding argument is E.
If the deduction fails, the declaration is ill-formed.
Otherwise, is obtained by substituting the deduced U into P.
[Example
:
auto x1 = { 1, 2 };             // decltype(x1) is std​::​initializer_­list<int>
auto x2 = { 1, 2.0 };           // error: cannot deduce element type
auto x3{ 1, 2 };                // error: not a single element
auto x4 = { 3 };                // decltype(x4) is std​::​initializer_­list<int>
auto x5{ 3 };                   // decltype(x5) is int
— end example
]
[Example
:
const auto &i = expr;
The type of i is the deduced type of the parameter u in the call f(expr) of the following invented function template:
template <class U> void f(const U& u);
— end example
]
If the placeholder-type-specifier is of the form type-constraint decltype(auto), T shall be the placeholder alone.
The type deduced for T is determined as described in [dcl.type.decltype], as though E had been the operand of the decltype.
[Example
:
int i;
int&& f();
auto           x2a(i);          // decltype(x2a) is int
decltype(auto) x2d(i);          // decltype(x2d) is int
auto           x3a = i;         // decltype(x3a) is int
decltype(auto) x3d = i;         // decltype(x3d) is int
auto           x4a = (i);       // decltype(x4a) is int
decltype(auto) x4d = (i);       // decltype(x4d) is int&
auto           x5a = f();       // decltype(x5a) is int
decltype(auto) x5d = f();       // decltype(x5d) is int&&
auto           x6a = { 1, 2 };  // decltype(x6a) is std​::​initializer_­list<int>
decltype(auto) x6d = { 1, 2 };  // error: { 1, 2 } is not an expression
auto          *x7a = &i;        // decltype(x7a) is int*
decltype(auto)*x7d = &i;        // error: declared type is not plain decltype(auto)
— end example
]
For a placeholder-type-specifier with a type-constraint, the immediately-declared constraint ([temp.param]) of the type-constraint for the type deduced for the placeholder shall be satisfied.

9.2.8.6 Deduced class template specialization types [dcl.type.class.deduct]

If a placeholder for a deduced class type appears as a decl-specifier in the decl-specifier-seq of an initializing declaration ([dcl.init]) of a variable, the declared type of the variable shall be cv T, where T is the placeholder.
[Example
:
template <class ...T> struct A {
  A(T...) {}
};
A x[29]{};          // error: no declarator operators allowed
const A& y{};       // error: no declarator operators allowed
— end example
]
The placeholder is replaced by the return type of the function selected by overload resolution for class template deduction ([over.match.class.deduct]).
If the decl-specifier-seq is followed by an init-declarator-list or member-declarator-list containing more than one declarator, the type that replaces the placeholder shall be the same in each deduction.
A placeholder for a deduced class type can also be used in the type-specifier-seq in the new-type-id or type-id of a new-expression, as the simple-type-specifier in an explicit type conversion (functional notation), or as the type-specifier in the parameter-declaration of a template-parameter.
A placeholder for a deduced class type shall not appear in any other context.
[Example
:
template<class T> struct container {
    container(T t) {}
    template<class Iter> container(Iter beg, Iter end);
};
template<class Iter>
container(Iter b, Iter e) -> container<typename std::iterator_traits<Iter>::value_type>;
std::vector<double> v = { /* ... */ };

container c(7);                         // OK, deduces int for T
auto d = container(v.begin(), v.end()); // OK, deduces double for T
container e{5, 6};                      // error: int is not an iterator
— end example
]