The simple type specifiers are
simple-type-specifier: nested-name-specifieropt type-name nested-name-specifier template simple-template-id 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 )
The auto specifier is a placeholder for a type to be deduced ([dcl.spec.auto]). The other simple-type-specifiers specify either a previously-declared user-defined type or one of the fundamental types ([basic.fundamental]). Table [tab:simple.type.specifiers] 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] |
| 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(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 and certain bit-fields ([class.bit]) are represented as signed or unsigned quantities. The signed specifier forces char objects and bit-fields to be signed; it is redundant in other contexts. — end note ]
The type denoted by decltype(e) is defined as follows:
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 (Clause [expr]).
[ Example:
const int&& foo();
int i;
struct A { double x; };
const A* a = new A();
decltype(foo()) x1 = i; // 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: in the case where the operand of a decltype-specifier is a function call and the return type of the function is a class type, a special rule ([expr.call]) ensures that the return type is not required to be complete (as it would be if the call appeared in a sub-expression or outside of a decltype-specifier). 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. [ 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 ] — end note ]