Files
2025-10-25 03:02:53 +03:00

118 KiB
Raw Permalink Blame History

[dcl.spec]

9 Declarations [dcl]

9.2 Specifiers [dcl.spec]

9.2.1 General [dcl.spec.general]

1

#

The specifiers that can be used in a declaration are

decl-specifier:
storage-class-specifier
defining-type-specifier
function-specifier
friend
typedef
constexpr
consteval
constinit
inline

decl-specifier-seq:
decl-specifier attribute-specifier-seqopt
decl-specifier decl-specifier-seq

The optional attribute-specifier-seq in a decl-specifier-seq appertains to the type determined by the precedingdecl-specifiers ([dcl.meaning]).

The attribute-specifier-seq affects the type only for the declaration it appears in, not other declarations involving the same type.

2

#

At most one of each of the decl-specifiersfriend, typedef, or inline shall appear in a decl-specifier-seq.

At most one of the constexpr, consteval, and constinit keywords shall appear in a decl-specifier-seq.

3

#

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 thedecl-specifier-seq.

The sequence shall be self-consistent as described below.

[Example 1: 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 thanconst 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]

4

#

[Note 1:

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 2: void h(unsigned Pc); // void h(unsigned int)void k(unsigned int Pc); // void k(unsigned int) — end example]

— end note]

9.2.2 Storage class specifiers [dcl.stc]

1

#

The storage class specifiers are

storage-class-specifier:
static
thread_local
extern
mutable

At most one storage-class-specifier shall appear in a givendecl-specifier-seq, except that thread_local may appear with static orextern.

If thread_local appears in any declaration of a variable it shall be present in all declarations of that entity.

If astorage-class-specifier appears in a decl-specifier-seq, there can be notypedef 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 namespace scope ([class.union.anon])).

Thestorage-class-specifier applies to the name declared by eachinit-declarator in the list and not to any names declared by other specifiers.

[Note 1:

See [temp.expl.spec] and [temp.explicit] for restrictions in explicit specializations and explicit instantiations, respectively.

— end note]

2

#

[Note 2:

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]

3

#

The thread_local specifier indicates that the named entity has thread storage duration ([basic.stc.thread]).

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 thestorage-class-specifier static is implied if no otherstorage-class-specifier appears in thedecl-specifier-seq.

4

#

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 nostatic function declarations within a block, nor anystatic function parameters.

A static specifier used in the declaration of a variable declares the variable to have static storage duration ([basic.stc.static]), unless accompanied by thethread_local specifier, which declares the variable to have thread storage duration ([basic.stc.thread]).

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].

5

#

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 3:

The extern keyword can also be used inexplicit-instantiations andlinkage-specifications, but it is not astorage-class-specifier in such contexts.

— end note]

6

#

All declarations for a given entity shall give its name the same linkage.

[Note 4:

The linkage given by some declarations is affected by previous declarations.

Overloads are distinct entities.

— end note]

[Example 1: static char* f(); // f() has internal linkagechar* f() // f() still has internal linkage{ /* ... / }char g(); // g() has external linkagestatic char* g() // error: inconsistent linkage{ /* ... */ }void h();inline void h(); // external linkageinline void l();void l(); // external linkageinline void m();extern void m(); // external linkagestatic void n();inline void n(); // internal linkagestatic int a; // a has internal linkageint a; // error: two definitionsstatic int b; // b has internal linkageextern int b; // b still has internal linkageint c; // c has external linkagestatic int c; // error: inconsistent linkageextern int d; // d has external linkagestatic int d; // error: inconsistent linkage — end example]

7

#

The name of a declared but undefined class can be used in anextern declaration.

Such a declaration can only be used in ways that do not require a complete class type.

[Example 2: 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]

8

#

The mutable specifier shall appear only in the declaration of a non-static data member ([class.mem]) whose type is neither const-qualified nor a reference type.

[Example 3: class X {mutable const int* p; // OKmutable int* const q; // error}; — end example]

9

#

[Note 5:

The mutable specifier on a class data member nullifies aconst 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.3 Function specifiers [dcl.fct.spec]

1

#

Afunction-specifier can be used only in a function declaration.

At most one explicit-specifier and at most one virtual keyword shall appear in a decl-specifier-seq.

function-specifier:
virtual
explicit-specifier

explicit-specifier:
explicit ( constant-expression )
explicit

2

#

The virtual specifier shall be used only in the initial declaration of a non-static member function; see [class.virtual].

3

#

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].

4

#

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.

[Example 1: struct S {explicit(sizeof(char[2])) S(char); // error: narrowing conversion of value 2 to type boolexplicit(sizeof(char)) S(bool); // OK, conversion of value 1 to type bool is non-narrowing}; — end example]

9.2.4 The typedef specifier [dcl.typedef]

1

#

Declarations containing the decl-specifier typedef declare type aliases.

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 thedecl-specifier-seq of aparameter-declaration ([dcl.fct]) nor in thedecl-specifier-seq of afunction-definition ([dcl.fct.def]).

If a typedef specifier appears in a declaration without a declarator, the program is ill-formed.

typedef-name:
identifier
simple-template-id

A name declared with the typedef specifier becomes atypedef-name.

The underlying entity of the type alias is the type associated with the identifier ([dcl.decl]) or simple-template-id ([temp.pre]).

A typedef-name does not introduce a new type the way a class declaration ([class.name]) or enum declaration ([dcl.enum]) does.

[Example 1:

Aftertypedef int MILES, *KLICKSP; the constructionsMILES distance;extern KLICKSP metricp; are all correct declarations; the type of distance isint and that of metricp is “pointer to int”.

— end example]

2

#

A type alias can also be declared by analias-declaration.

The identifier following theusing keyword is not looked up; it becomes the typedef-name of a type alias and the optional attribute-specifier-seq following theidentifier appertains to that type alias.

Such a type alias has the same semantics as if it were introduced by the typedef specifier.

[Example 2: using handler_t = void (*)(int);extern handler_t ignore;extern void (ignore)(int); // redeclare ignoretemplate struct P { };using cell = P<cell>; // error: cell not found ([basic.scope.pdecl]) — 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.

3

#

A simple-template-id is only a typedef-name if its template-name names an alias template or a type template template parameter.

[Note 1:

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 anelaborated-type-specifier ([dcl.type.elab]), a class definition, a constructor declaration, or a destructor declaration, the program is ill-formed.

— end note]

[Example 3: struct S { S(); ~S();};

typedef struct S T;

S a = T(); // OKstruct T * p; // error — end example]

4

#

An unnamed class or enumeration C defined in a typedef declaration has the first typedef-name declared by the declaration to be of type C as its typedef name for linkage purposes ([basic.link]).

[Note 2:

A typedef declaration involving a lambda-expression does not itself define the associated closure type, and so the closure type is not given a typedef name for linkage purposes.

— end note]

[Example 4: typedef struct { } *ps, S; // S is the typedef name for linkage purposestypedef decltype([]{}) C; // the closure type has no typedef name for linkage purposes — end example]

5

#

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 5: typedef struct {int f() {}} X; // error: struct with typedef name for linkage has member functions — end example]

9.2.5 The friend specifier [dcl.friend]

1

#

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

9.2.6 The constexpr and consteval specifiers [dcl.constexpr]

1

#

The constexpr specifier shall be applied only to the definition of a variable or variable template, a structured binding declaration, 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 on its first declaration is implicitly an inline function or variable ([dcl.inline]).

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 1:

An explicit specialization can differ from the template declaration with respect to the constexpr or consteval specifier.

— end note]

[Note 2:

Function parameters cannot be declared constexpr.

— end note]

[Example 1: constexpr void square(int &x); // OK, declarationconstexpr int bufsz = 1024; // OK, definitionconstexpr struct pixel { // error: pixel is a typeint 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 satisfiedconstexpr void square(int &x) { // OK, definition x *= x;}constexpr pixel large(4); // OK, square definedint next(constexpr int x) { // error: not for parametersreturn x + 1;}extern constexpr int memsz; // error: not a definition — end example]

2

#

A constexpr or consteval specifier used in the declaration of a function declares that function to be a constexpr function.

[Note 3:

A function or constructor declared with the consteval specifier is an immediate function ([expr.const]).

— end note]

A destructor, an allocation function, or a deallocation function shall not be declared with the consteval specifier.

3

#

A function is constexpr-suitable if it is not a coroutine ([dcl.fct.def.coroutine]).

Except for instantiated constexpr functions, non-templated constexpr functions shall be constexpr-suitable.

[Example 2: constexpr int square(int x){ return x * x; } // OKconstexpr long long_max(){ return 2147483647; } // OKconstexpr int abs(int x) {if (x < 0) x = -x; return x; // OK}constexpr int constant_non_42(int n) { // OKif (n == 42) {static int value = n; return value; }return n;}constexpr int uninit() {struct { int a; } s; return s.a; // error: uninitialized read of s.a}constexpr int prev(int x){ return --x; } // OKconstexpr int g(int x, int n) { // OKint r = 1; while (--n > 0) r *= x; return r;} — end example]

4

#

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

an invocation of a constexpr function can appear in a constant expression ([expr.const]) and

copy elision is not performed in a constant expression ([class.copy.elision]).

[Note 4:

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]

[Note 5:

It is possible to write a constexpr function for which no invocation satisfies the requirements of a core constant expression.

— end note]

5

#

The constexpr and consteval specifiers have no effect on the type of a constexpr function.

[Example 3: 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]

6

#

A constexpr specifier used in an object declaration declares the object as const.

Such an object shall have literal type and shall be initialized.

A constexpr variable shall be constant-initializable ([expr.const]).

A constexpr variable that is an object, as well as any temporary to which a constexpr reference is bound, shall have constant destruction.

[Example 4: struct pixel {int x, y;};constexpr pixel ur = { 1294, 1024 }; // OKconstexpr pixel origin; // error: initializer missingnamespace N {void f() {int x; constexpr int& ar = x; // OKstatic constexpr int& sr = x; // error: x is not constexpr-representable// at the point indicated below}// immediate scope here is that of N} — end example]

9.2.7 The constinit specifier [dcl.constinit]

1

#

The constinit specifier shall be applied only to a declaration of a variable with static or thread storage duration or to a structured binding declaration ([dcl.struct.bind]).

[Note 1:

A structured binding declaration introduces a uniquely named variable, to which the constinit specifier applies.

— end note]

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.

2

#

If a variable declared with the constinit specifier has dynamic initialization ([basic.start.dynamic]), the program is ill-formed, even if the implementation would perform that initialization as a static initialization ([basic.start.static]).

[Note 2:

The constinit specifier ensures that the variable is initialized during static initialization.

— end note]

3

#

[Example 1: const char * g() { return "dynamic initialization"; }constexpr const char * f(bool p) { return p ? "constant initializer" : g(); }constinit const char * c = f(true); // OKconstinit const char * d = f(false); // error — end example]

9.2.8 The inline specifier [dcl.inline]

1

#

The inline specifier shall be applied only to the declaration of a variable or function.

2

#

A function declaration ([dcl.fct], [class.mfct], [class.friend]) with an inline specifier declares aninline 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 1:

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]

3

#

A variable declaration with an inline specifier declares aninline variable.

4

#

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.

5

#

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 2:

A call to an inline function or a use of an inline variable can be encountered before its definition becomes reachable in a translation unit.

— end note]

6

#

[Note 3:

An inline function or variable with external or module linkage can be defined in multiple translation units ([basic.def.odr]), but is one entity with one address.

A type or static variable defined in the body of such a function is therefore a single entity.

— end note]

7

#

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 4:

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.9 Type specifiers [dcl.type]

9.2.9.1 General [dcl.type.general]

1

#

The type-specifiers are

type-specifier:
simple-type-specifier
elaborated-type-specifier
typename-specifier
cv-qualifier

type-specifier-seq:
type-specifier attribute-specifier-seqopt
type-specifier type-specifier-seq

defining-type-specifier:
type-specifier
class-specifier
enum-specifier

defining-type-specifier-seq:
defining-type-specifier attribute-specifier-seqopt
defining-type-specifier defining-type-specifier-seq

The optional attribute-specifier-seq in a type-specifier-seq or a defining-type-specifier-seq appertains to the type denoted by the preceding type-specifiers or defining-type-specifiers ([dcl.meaning]).

Theattribute-specifier-seq affects the type only for the declaration it appears in, not other declarations involving the same type.

2

#

As a general rule, at most onedefining-type-specifier is allowed in the completedecl-specifier-seq of a declaration or in adefining-type-specifier-seq, and at most onetype-specifier is allowed in atype-specifier-seq.

The only exceptions to this rule are the following:

  • (2.1)

    const can be combined with any type specifier except itself.

  • (2.2)

    volatile can be combined with any type specifier except itself.

  • (2.3)

    signed or unsigned can be combined withchar, long, short, or int.

  • (2.4)

    short or long can be combined with int.

  • (2.5)

    long can be combined with double.

  • (2.6)

    long can be combined with long.

3

#

Except in a declaration of a constructor, destructor, or conversion function, at least one defining-type-specifier that is not acv-qualifier shall appear in a completetype-specifier-seq or a completedecl-specifier-seq.74

4

#

[Note 1:

enum-specifiers,class-specifiers, andtypename-specifiers are discussed in[dcl.enum],[class], and[temp.res], respectively.

The remainingtype-specifiers are discussed in the rest of [dcl.type].

— end note]

74)74)

There is no special provision for a decl-specifier-seq that lacks a type-specifier or that has atype-specifier that only specifies cv-qualifiers.

The “implicit int” rule of C is no longer supported.

9.2.9.2 The cv-qualifier**s [dcl.type.cv]

1

#

There are two cv-qualifiers, const andvolatile.

Each cv-qualifier shall appear at most once in a cv-qualifier-seq.

If a cv-qualifier appears in adecl-specifier-seq, the init-declarator-list or member-declarator-list of the declaration shall not be empty.

[Note 1:

[basic.type.qualifier] and [dcl.fct] describe how cv-qualifiers affect object and function types.

— end note]

Redundant cv-qualifications are ignored.

[Note 2:

For example, these could be introduced by typedefs.

— end note]

2

#

[Note 3:

Declaring a variable const can affect its linkage ([dcl.stc]) and its usability in constant expressions ([expr.const]).

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]

3

#

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 4:

Cv-qualifiers are supported by the type system so that they cannot be subverted without casting.

— end note]

4

#

Any attempt to modify ([expr.assign], [expr.post.incr], [expr.pre.incr]) a const object ([basic.type.qualifier]) during its lifetime ([basic.life]) results in undefined behavior.

[Example 1: const int ci = 3; // cv-qualified (initialized as required) ci = 4; // error: attempt to modify constint i = 2; // not cv-qualifiedconst int* cip; // pointer to const int cip = &i; // OK, cv-qualified access path to unqualifiedcip = 4; // error: attempt to modify through ptr to constint ip; ip = const_cast<int*>(cip); // cast needed to convert const int* to int**ip = 4; // defined: ip points to i, a non-const objectconst int ciq = new const int (3); // initialized as requiredint* 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]

5

#

The semantics of an access through a volatile glvalue areimplementation-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.

6

#

[Note 5:

volatile is a hint to the implementation to avoid aggressive optimization involving the object because it is possible for the value of the object to change by means undetectable by an implementation.

Furthermore, for some implementations, volatile can indicate that special hardware instructions are needed 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.9.3 Simple type specifiers [dcl.type.simple]

1

#

The simple type specifiers are

simple-type-specifier:
nested-name-specifieropt type-name
nested-name-specifier template simple-template-id
computed-type-specifier
placeholder-type-specifier
nested-name-specifieropt template-name
char
char8_t
char16_t
char32_t
wchar_t
bool
short
int
long
signed
unsigned
float
double
void

type-name:
class-name
enum-name
typedef-name

computed-type-specifier:
decltype-specifier
pack-index-specifier
splice-type-specifier

2

#

The component names of a simple-type-specifier are those of itsnested-name-specifier,type-name,simple-template-id,template-name, and/ortype-constraint (if it is a placeholder-type-specifier).

The component name of a type-name is the first name in it.

3

#

A placeholder-type-specifier is a placeholder for a type to be deduced ([dcl.spec.auto]).

A type-specifier is a placeholder for a deduced class type ([dcl.type.class.deduct]) if either

it is of the formtypenameopt nested-name-specifieropt template-name or

it is of the form typenameopt splice-specifier and the splice-specifier designates a class template or alias template.

The nested-name-specifier or splice-specifier, if any, shall be non-dependent and the template-name or splice-specifier shall designate a deducible template.

A deducible template is either a class template or is an alias template whose defining-type-id is of the form

typenameopt nested-name-specifieropt templateopt 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 1:

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 othersimple-type-specifiers specify either a previously-declared type, a type determined from an expression, or one of the fundamental types ([basic.fundamental]).

Table 17 summarizes the valid combinations ofsimple-type-specifiers and the types they specify.

Table 17simple-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]
🔗
pack-index-specifier
the type as defined in [dcl.type.pack.index]
🔗
placeholder-type-specifier
the type as defined in [dcl.spec.auto]
🔗
template-name
the type as defined in [dcl.type.class.deduct]
🔗
splice-type-specifier
the type as defined in [dcl.type.splice]
🔗
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”

4

#

When multiple simple-type-specifiers are allowed, they can be freely intermixed with other decl-specifiers in any order.

[Note 2:

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.9.4 Pack indexing specifier [dcl.type.pack.index]

pack-index-specifier:
typedef-name ... [ constant-expression ]

1

#

The typedef-name P in a pack-index-specifier shall denote a pack.

2

#

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 0≤V<sizeof...(P).

3

#

A pack-index-specifier is a pack expansion ([temp.variadic]).

4

#

[Note 1:

The pack-index-specifier denotes the type of the Vth element of the pack.

— end note]

9.2.9.5 Elaborated type specifiers [dcl.type.elab]

elaborated-type-specifier:
class-key attribute-specifier-seqopt nested-name-specifieropt identifier
class-key simple-template-id
class-key nested-name-specifier templateopt simple-template-id
enum nested-name-specifieropt identifier

1

#

The component names of an elaborated-type-specifier are its identifier (if any) and those of its nested-name-specifier andsimple-template-id (if any).

2

#

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]), a partial specialization ([temp.spec.partial]), an explicit instantiation ([temp.explicit]), or it has one of the following forms:

class-key attribute-specifier-seqopt identifier ;
class-key attribute-specifier-seqopt simple-template-id ;

In the first case, the elaborated-type-specifier declares the identifier as a class-name.

The second case shall appear only in an explicit-specialization ([temp.expl.spec]) or in a template-declaration (where it declares a partial specialization).

The attribute-specifier-seq, if any, appertains to the class or template being declared.

3

#

Otherwise, an elaborated-type-specifier E shall not have an attribute-specifier-seq.

If E contains an identifier but no nested-name-specifier and (unqualified) lookup for the identifier finds nothing,E shall not be introduced by the enum keyword and declares the identifier as a class-name.

The target scope of E is the nearest enclosing namespace or block scope.

4

#

A friend-type-specifier that is an elaborated-type-specifier shall have one of the following forms:

class-key nested-name-specifieropt identifier
class-key simple-template-id
class-key nested-name-specifier templateopt simple-template-id

Any unqualified lookup for the identifier (in the first case) does not consider scopes that contain the nearest enclosing namespace or block scope; no name is bound.

[Note 1:

A using-directive in the target scope is ignored if it refers to a namespace not contained by that scope.

— end note]

5

#

[Note 2:

[basic.lookup.elab] describes how name lookup proceeds in an elaborated-type-specifier.

An elaborated-type-specifier can be used to refer to a previously declared class-name or enum-name even if the name has been hidden by a non-type declaration.

— end note]

6

#

If the identifier or simple-template-id in an elaborated-type-specifier resolves to a class-name orenum-name, the elaborated-type-specifier introduces it into the declaration the same way asimple-type-specifier introduces its type-name ([dcl.type.simple]).

If the identifier or simple-template-id resolves to atypedef-name ([dcl.typedef], [temp.names]), the elaborated-type-specifier is ill-formed.

[Note 3:

This implies that, within a class template with a templatetype-parameter T, the declarationfriend class T; is ill-formed.

However, the similar declaration friend T; is well-formed ([class.friend]).

— end note]

7

#

The class-key or enum keyword present in anelaborated-type-specifier shall agree in kind with the declaration to which the name in theelaborated-type-specifier refers.

This rule also applies to the form of elaborated-type-specifier that declares aclass-name or friend class since it can be construed as referring to the definition of the class.

Thus, in anyelaborated-type-specifier, the enum keyword shall be used to refer to an enumeration ([dcl.enum]), the unionclass-key shall be used to refer to a union ([class.union]), and either the class or structclass-key shall be used to refer to a non-union class ([class.pre]).

[Example 1: enum class E { a, b };enum E x = E::a; // OKstruct S { } s;class S* p = &s; // OK — end example]

9.2.9.6 Decltype specifiers [dcl.type.decltype]

decltype-specifier:
decltype ( expression )

1

#

For an expression E, the type denoted by decltype(E) is defined as follows:

  • (1.1)

    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;

  • (1.2)

    otherwise, if E is an unparenthesized id-expression naming a constant template parameter ([temp.param]),decltype(E) is the type of the template parameter after performing any necessary type deduction ([dcl.spec.auto], [dcl.type.class.deduct]);

  • (1.3)

    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, the program is ill-formed;

  • (1.4)

    otherwise, if E is an unparenthesized splice-expression,decltype(E) is the type of the entity, object, or value designated by the splice-specifier of E;

  • (1.5)

    otherwise, if E is an xvalue, decltype(E) is T&&, where T is the type of E;

  • (1.6)

    otherwise, if E is an lvalue, decltype(E) is T&, where T is the type of E;

  • (1.7)

    otherwise, decltype(E) is the type of E.

The operand of the decltype specifier is an unevaluated operand.

[Example 1: 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 intdecltype(a->x) x3; // type is doubledecltype((a->x)) x4 = x3; // type is const double&decltype([:^^x1:]) x5 = 18; // type is const int&&decltype(([:^^x1:])) x6 = 19; // type is const int&void f() {[](auto ...pack) {decltype(pack...[0]) x7; // type is intdecltype((pack...[0])) x8; // type is int&}(0);} — end example]

[Note 1:

The rules for determining types involving decltype(auto) are specified in [dcl.spec.auto].

— end note]

2

#

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 2:

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, adecltype-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 3:

Unlike the preceding rule, parentheses have no special meaning in this context.

— end note]

[Example 2: template struct A { A() = delete; };template auto h()-> A;template auto i(T) // identity-> T;template auto f(T) // #1-> decltype(i(h())); // forces completion of A and implicitly uses A::A()// for the temporary introduced by the use of h().// (A temporary is not introduced as a result of the use of i().)template 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::A() is implicitly used in its// decltype-specifier)}template auto q(T)-> decltype((h())); // does not force completion of A; A::A() is not implicitly// used within the context of this decltype-specifiervoid r() { q(42); // error: deduction against q succeeds, so overload resolution selects// the specialization “q(T) -> decltype((h()))'' with T=int;// the return type is A, so a temporary is introduced and its// destructor is used, so the program is ill-formed} — end example]

9.2.9.7 Placeholder type specifiers [dcl.spec.auto]

9.2.9.7.1 General [dcl.spec.auto.general]

placeholder-type-specifier:
type-constraintopt auto
type-constraintopt decltype ( auto )

1

#

A placeholder-type-specifier designates a placeholder type that will be replaced later, typically by deduction from an initializer.

2

#

The type of a parameter-declaration of a

function declaration ([dcl.fct]),

lambda-expression ([expr.prim.lambda]), or

template-parameter ([temp.param])

can be declared using a placeholder-type-specifier of the formtype-constraintopt auto.

The placeholder type shall appear as one of the decl-specifiers in the decl-specifier-seq or as one of the type-specifiers in a trailing-return-type that specifies the type that replaces such a decl-specifier (see below); the placeholder type is a generic parameter type placeholder of the function declaration,lambda-expression, ortemplate-parameter, respectively.

[Note 1:

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]

3

#

A placeholder type can appear in the decl-specifier-seq for a function declarator that includes a trailing-return-type ([dcl.fct]).

4

#

A placeholder type can appear in the decl-specifier-seq or type-specifier-seq in the declared return type of a function declarator that declares a function; the return type of the function is deduced from non-discarded return statements, if any, in the body of the function ([stmt.if]).

5

#

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 thedecl-specifiers in the decl-specifier-seq or as one of thetype-specifiers in a trailing-return-type that specifies the type that replaces such a decl-specifier; the decl-specifier-seq shall be followed by one or moredeclarators, each of which shall be followed by a non-emptyinitializer.

[Example 1: auto x = 5; // OK, x has type intconst auto v = &x, u = 6; // OK, v has type const int, u has type const intstatic auto y = 0.0; // OK, y has type doubleauto int r; // error: auto is not a storage-class-specifierauto f() -> int; // OK, f returns intauto g() { return 0.0; } // OK, g returns doubleauto (*fp)() -> auto = f; // OKauto 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]).

6

#

A placeholder type can also be used in the type-specifier-seq of the new-type-id or in the type-id of anew-expression ([expr.new]).

In such a type-id, the placeholder type shall appear as one of the type-specifiers in the type-specifier-seq or as one of the type-specifiers in a trailing-return-type that specifies the type that replaces such a type-specifier.

7

#

The auto type-specifier can also be used as the simple-type-specifier in an explicit type conversion (functional notation) ([expr.type.conv]).

8

#

A program that uses a placeholder type in a context not explicitly allowed in [dcl.spec.auto] is ill-formed.

9

#

If the init-declarator-list contains more than oneinit-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 2: auto x = 5, *y = &x; // OK, auto is intauto a = 5, b = { 1, 2 }; // error: different types for auto — end example]

10

#

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.

11

#

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 areturn statement with no operand at the closing brace of the function body.

[Example 3: auto f() { } // OK, return type is voidauto* g() { } // error: cannot deduce auto* from void() — end example]

12

#

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 2:

The deduced return type cannot have a name with internal linkage ([basic.link]).

— end note]

13

#

If a variable or function with an undeduced placeholder type is named by an expression ([basic.def.odr]), 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 otherreturn statements.

[Example 4: auto n = n; // error: n's initializer refers to nauto f();void g() { &f; } // error: f's return type is unknownauto sum(int i) {if (i == 1)return i; // sum's return type is intelsereturn sum(i-1)+i; // OK, sum's return type has been deduced} — end example]

14

#

A result binding never has an undeduced placeholder type ([dcl.contract.res]).

[Example 5: auto f() post(r : r == 7) // OK{return 7;} — end example]

15

#

Return type deduction for a templated function 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 3:

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 6: template auto f(T t) { return t; } // return type deduced at instantiation timetypedef decltype(f(1)) fint_t; // instantiates f to deduce return typetemplate auto f(T* t) { return *t; }void g() { int (p)(int) = &f; } // instantiates both fs to determine return types,// chooses second — end example]

16

#

If a function or function template F has a declared return type that uses a placeholder type, redeclarations or specializations of F shall use that placeholder type, not a deduced type; otherwise, they shall not use a placeholder type.

[Example 7: auto f();auto f() { return 42; } // return type is intauto f(); // OKint f(); // error: auto and int don't matchdecltype(auto) f(); // error: auto and decltype(auto) don't matchtemplate auto g(T t) { return t; } // #1template auto g(int); // OK, return type is inttemplate char g(char); // error: no matching templatetemplate<> auto g(double); // OK, forward declaration with unknown return typetemplate T g(T t) { return t; } // OK, not functionally equivalent to #1template char g(char); // OK, now there is a matching templatetemplate auto g(float); // still matches #1void h() { return g(42); } // error: ambiguoustemplate struct A {friend T frf(T);};auto frf(int i) { return i; } // not a friend of Aextern int v;auto v = 17; // OK, redeclares vstruct S {static int i;};auto S::i = 23; // OK — end example]

17

#

A function declared with a return type that uses a placeholder type shall not be virtual ([class.virtual]).

18

#

A function declared with a return type that uses a placeholder type shall not be a coroutine ([dcl.fct.def.coroutine]).

19

#

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 8: template auto f(T t) { return t; }extern template auto f(int); // does not instantiate fint (*p)(int) = f; // instantiates f to determine its return type, but an explicit// instantiation definition is still required somewhere in the program — end example]

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

1

#

Placeholder type deduction is the process by which a type containing a placeholder type is replaced by a deduced type.

2

#

A type T containing a placeholder type, and a corresponding initializer-clause E, are determined as follows:

T shall not be an array type.

3

#

If the placeholder-type-specifier is of the formtype-constraintopt auto, the deduced typeT′ replacing T is determined using the rules for template argument deduction.

If the initialization is copy-list-initialization, a declaration of std::initializer_list shall precede ([basic.lookup.general]) the placeholder-type-specifier.

Obtain P fromT by replacing the occurrence oftype-constraintopt auto either with a new invented type template parameter U or, if the initialization is copy-list-initialization, withstd::initializer_list.

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, T′ is obtained by substituting the deduced U into P.

[Example 1: auto x1 = { 1, 2 }; // decltype(x1) is std::initializer_listauto x2 = { 1, 2.0 }; // error: cannot deduce element typeauto x3{ 1, 2 }; // error: not a single elementauto x4 = { 3 }; // decltype(x4) is std::initializer_listauto x5{ 3 }; // decltype(x5) is int — end example]

[Example 2: 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 void f(const U& u);

— end example]

4

#

If the placeholder-type-specifier is of the formtype-constraintopt decltype(auto),T shall be the placeholder alone.

The type deduced for T is determined as described in [dcl.type.decltype], as thoughE had been the operand of the decltype.

[Example 3: int i;int&& f();auto x2a(i); // decltype(x2a) is intdecltype(auto) x2d(i); // decltype(x2d) is intauto x3a = i; // decltype(x3a) is intdecltype(auto) x3d = i; // decltype(x3d) is intauto x4a = (i); // decltype(x4a) is intdecltype(auto) x4d = (i); // decltype(x4d) is int&auto x5a = f(); // decltype(x5a) is intdecltype(auto) x5d = f(); // decltype(x5d) is int&&auto x6a = { 1, 2 }; // decltype(x6a) is std::initializer_listdecltype(auto) x6d = { 1, 2 }; // error: { 1, 2 } is not an expressionauto x7a = &i; // decltype(x7a) is intdecltype(auto)*x7d = &i; // error: declared type is not plain decltype(auto)auto f1(int x) -> decltype((x)) { return (x); } // return type is int&auto f2(int x) -> decltype(auto) { return (x); } // return type is int&& — end example]

5

#

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.9.8 Deduced class template specialization types [dcl.type.class.deduct]

1

#

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 1: template <class ...T> struct A { A(T...) {}}; A x[29]{}; // error: no declarator operators allowedconst 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.

2

#

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 ([expr.new]), 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 ([temp.param]).

A placeholder for a deduced class type shall not appear in any other context.

3

#

[Example 2: template struct container { container(T t) {}template container(Iter beg, Iter end);};template container(Iter b, Iter e) -> container<typename std::iterator_traits::value_type>; std::vector v = { /* ... */ };

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

9.2.9.9 Type splicing [dcl.type.splice]

splice-type-specifier:
typenameopt splice-specifier
typenameopt splice-specialization-specifier

1

#

A splice-specifier orsplice-specialization-specifier immediately followed by :: is never interpreted as part of a splice-type-specifier.

A splice-specifier orsplice-specialization-specifier not preceded by typename is only interpreted as a splice-type-specifier within a type-only context ([temp.res.general]).

[Example 1: template<std::meta::info R> void tfn() {typename [:R:]::type m; // OK, typename applies to the qualified name}struct S { using type = int; };void fn() {[:^^S::type:] *var; // error: [:^^S::type:] is an expressiontypename [:^^S::type:] var; // OK, declares variable with type int}using alias = [:^^S::type:]; // OK, type-only context — end example]

2

#

For a splice-type-specifier of the formtypenameopt splice-specifier, the splice-specifier shall designate a type, a class template, or an alias template.

The splice-type-specifier designates the same entity as the splice-specifier.

3

#

For a splice-type-specifier of the formtypenameopt splice-specialization-specifier, the splice-specifier of the splice-specialization-specifier shall designate a template T that is either a class template or an alias template.

The splice-type-specifier designates the specialization of T corresponding to the template argument list of the splice-specialization-specifier.