[dcl.type] (original) (raw)

9 Declarations [dcl]

9.2 Specifiers [dcl.spec]

9.2.9 Type specifiers [dcl.type]

9.2.9.1 General [dcl.type.general]

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

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

The only exceptions to this rule are the following:

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

[Note 1:

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

— _end note_]

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

Redundant cv-qualifications are ignored.

[Note 2:

For example, these could be introduced by typedefs.

— _end note_]

[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_]

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

[Example 1: const int ci = 3; ci = 4; int i = 2; const int* cip; cip = &i; *cip = 4; int* ip; ip = const_cast<int*>(cip); *ip = 4; const int* ciq = new const int (3); int* iq = const_cast<int*>(ciq); *iq = 4;

For another example,struct X { mutable int i;int j;};struct Y { X x; Y();};const Y y; y.x.i++; y.x.j++; Y* p = const_cast<Y*>(&y); p->x.i = 99; p->x.j = 99;

— _end example_]

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.

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

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]

The simple type specifiers are

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

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

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

[Note 1:

An injected-class-name is never interpreted as a template-namein contexts where class template argument deduction would be performed ([temp.local]).

— _end note_]

The othersimple-type-specifier_s_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-specifier_s_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]
🔗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 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.5 Elaborated type specifiers [dcl.type.elab]

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:

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

A friend-type-specifierthat is an elaborated-type-specifiershall have one of the following forms:

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

[Note 2:

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

— _end note_]

[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_]

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 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 1: enum class E { a, b };enum E x = E::a; struct S { } s;class S* p = &s; — _end example_]

9.2.9.6 Decltype specifiers [dcl.type.decltype]

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

[Example 1: const int&& foo();int i;struct A { double x; };const A* a = new A();decltype(foo()) x1 = 17; decltype(i) x2; decltype(a->x) x3; decltype((a->x)) x4 = x3; void f() { [](auto ...pack) { decltype(pack...[0]) x5; decltype((pack...[0])) x6; }(0);} — _end example_]

[Note 1:

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 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<class T> struct A { ~A() = delete; };template<class T> auto h() -> A<T>;template<class T> auto i(T) -> T;template<class T> auto f(T) -> decltype(i(h<T>())); template<class T> auto f(T) -> void;auto g() -> void { f(42); } template<class T> auto q(T) -> decltype((h<T>())); void r() { q(42); } — _end example_]

9.2.9.7 Placeholder type specifiers [dcl.spec.auto]

9.2.9.7.1 General [dcl.spec.auto.general]

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

[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_]

A placeholder type can appear in the decl-specifier-seq or type-specifier-seqin 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]).

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.

[Example 1: auto x = 5; const auto *v = &x, u = 6; static auto y = 0.0; auto int r; auto f() -> int; auto g() { return 0.0; } auto (*fp)() -> auto = f; auto h(); — _end example_]

The auto type-specifiercan also be used to introduce a structured binding declaration ([dcl.struct.bind]).

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-typethat specifies the type that replaces such a type-specifier.

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

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

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; auto a = 5, b = { 1, 2 }; — _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 areturn statement with no operand at the closing brace of the function body.

[Example 3: auto f() { } auto* g() { } — _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 2:

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

— _end note_]

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; auto f();void g() { &f; } auto sum(int i) { if (i == 1) return i; else return sum(i-1)+i; } — _end example_]

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

[Example 5: auto f() post(r : r == 7) { return 7;} — _end example_]

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 <class T> auto f(T t) { return t; } typedef decltype(f(1)) fint_t; template<class T> auto f(T* t) { return *t; } void g() { int (*p)(int*) = &f; } — _end example_]

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; } auto f(); int f(); decltype(auto) f(); template <typename T> auto g(T t) { return t; } template auto g(int); template char g(char); template<> auto g(double); template <class T> T g(T t) { return t; } template char g(char); template auto g(float); void h() { return g(42); } template <typename T> struct A { friend T frf(T);};auto frf(int i) { return i; } extern int v;auto v = 17; struct S { static int i;};auto S::i = 23; — _end example_]

A function declared with a return type that uses a placeholder type shall not be virtual ([class.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 8: template <typename T> auto f(T t) { return t; } extern template auto f(int); int (*p)(int) = f; — _end example_]

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

Placeholder type deductionis 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-clause E, are determined as follows:

T shall not be an array type.

If the placeholder-type-specifier is of the formtype-constraint auto, the deduced type replacing Tis determined using the rules for template argument deduction.

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

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

If the deduction fails, the declaration is ill-formed.

Otherwise, is obtained by substituting the deduced U into P.

[Example 1: auto x1 = { 1, 2 }; auto x2 = { 1, 2.0 }; auto x3{ 1, 2 }; auto x4 = { 3 }; auto x5{ 3 }; — _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 <class U> void f(const U& u);

— _end example_]

If the placeholder-type-specifier is of the formtype-constraint 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(auto) x2d(i); auto x3a = i; decltype(auto) x3d = i; auto x4a = (i); decltype(auto) x4d = (i); auto x5a = f(); decltype(auto) x5d = f(); auto x6a = { 1, 2 }; decltype(auto) x6d = { 1, 2 }; auto *x7a = &i; decltype(auto)*x7d = &i; auto f1(int x) -> decltype((x)) { return (x); } auto f2(int x) -> decltype(auto) { return (x); } — _end example_]

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

If a placeholder for a deduced class type appears as a decl-specifierin the decl-specifier-seqof 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]{}; const A& y{}; — _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]).

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

[Example 2: 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); auto d = container(v.begin(), v.end()); container e{5, 6}; — _end example_]