The simple type specifiers are

simple-type-specifier:nested-name-specifier_{opt}type-namenested-name-specifiertemplatesimple-template-idchar char16_t char32_t wchar_t bool short int long signed unsigned float double void autodecltype-specifier

type-name:class-nameenum-nametypedef-namesimple-template-id

decltype-specifier: decltype (expression) decltype ( auto )

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 type, a type determined from an
expression, 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.

Table 10 — 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 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*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 = 0; // 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* ]

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