The simple type specifiers are
simple-type-specifier: nested-name-specifieropt type-name nested-name-specifier template simple-template-id nested-name-specifieropt template-name char char16_t char32_t wchar_t bool short int long signed unsigned float double void auto decltype-specifier
type-name: class-name enum-name typedef-name simple-template-id
decltype-specifier: decltype ( expression ) decltype ( auto )
The simple-type-specifier auto is a placeholder for a type to be deduced ([dcl.spec.auto]). A type-specifier of the form typenameopt nested-name-specifieropt template-name is a placeholder for a deduced class type ([dcl.type.class.deduct]). The template-name shall name a class template that is not an injected-class-name. The other simple-type-specifiers specify either a previously-declared type, a type determined from an expression, or one of the fundamental types. Table 11 summarizes the valid combinations of simple-type-specifiers and the types they specify.
| Specifier(s) | Type |
| type-name | the type named |
| simple-template-id | the type as defined in [temp.names] |
| template-name | placeholder for a type to be deduced |
| char | “char” |
| unsigned char | “unsigned char” |
| signed char | “signed char” |
| 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” |
| auto | placeholder for a type to be deduced |
| decltype(auto) | placeholder for a type to be deduced |
| decltype(expression) | the type as defined below |
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 ]
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 or an unparenthesized class member access, 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, the temporary materialization conversion is not applied and no result object is provided for the prvalue. The type of the prvalue may be incomplete. [ 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 T=int]”.
// The return type is A<int>, so a temporary is introduced and its
// destructor is used, so the program is ill-formed.
}— end example ]