7 Declarations [dcl.dcl]

7.1 Specifiers [dcl.spec]

7.1.6 Type specifiers [dcl.type]

7.1.6.2 Simple type specifiers [dcl.type.simple]

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 )
  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 10simple-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 ]