The decltype(expression) type specifier (C++11)

Note: IBM® supports selected features of C++11, known as C++0x before its ratification. IBM will continue to develop and implement the features of the this standard. The implementation of the language level is based on IBM's interpretation of the standard. Until IBM's implementation of all the C++11 features are complete, including the support of a new C++ standard library, the implementation may change from release to release. IBM makes no attempt to maintain compatibility, in source, binary, or listings and other compiler interfaces, with earlier releases of IBM's implementation of the new C++11 features and therefore they should not be relied on as a stable programming interface.

The decltype(expression) specifier is a type specifier introduced in C++11. With this type specifier, you can get a type that is based on the resultant type of a possibly type-dependent expression.

decltype(expression) takes expression as an operand. When you define a variable by using decltype(expression), it can be thought of as being replaced by the compiler with the type or the derived type of expression. Consider the following example:
int i;
static const decltype(i) j = 4;

In this example, decltype(i) is equivalent to the type name int.

General rules for using decltype

When you use decltype(expression) to get a type, the following rules are applicable:
  • If expression is an unparenthesized id-expression or class member, decltype(expression) is the type of the entity named by expression. If there is no such entity, or if expression names a set of overloaded functions, the program is ill formed.
  • Otherwise, if expression is a function call or an invocation of an overloaded operator (parentheses around expression are ignored), decltype(expression) is the return type of the statically chosen function.
  • Otherwise, if expression is an lvalue, decltype(expression) is T&, where T is the type of expression.
  • Otherwise, decltype(expression) is the type of expression.
The following example illustrates how these rules are used:
const int* bar(){
    return new int[0];
}
struct A{
    double x;
};
template <class T> T tFoo(const T& t){
    return t;
}
bool func(){
    return false;
}
struct Foo{
    template <typename T, typename U>
    static decltype((*(T*)0) * (*(U*)0)) foo(const U& arg1, const T& arg2){
    return arg1 * arg2;
    }
};
template <typename T, typename U> struct Bar{
    typedef decltype((*(T*)0) + (*(U*)0)) btype;
    static btype bar(T t, U u);
};
int main(){
    int i = 4;
    const int j = 6;
    const int& k = i;
    int a[5];
    int *p;
    decltype(i) var1; 		// int
    decltype(1) var2; 		// int
    decltype(2+3) var3; 	// int(+ operator returns an rvalue)
    decltype(i=1) var4 = i; // int&, because assignment to int
                            // returns an lvalue
    decltype((i)) var5 = i; // int&
    decltype(j) var6 = 1; // const int
    decltype(k) var7 = j; // const int&
    decltype("decltype") var10 = "decltype"; // const char(&)[9]
    decltype(a) var8; // int[5]
    decltype(a[3]) var9 = i; // int&([] returns an lvalue)
    decltype(*p) var11 = i; // int&(*operator returns an lvalue)
    decltype(tFoo(A())) var12; // A
    decltype(func()) var13; // bool
    decltype((func())) var14; // bool, parentheses around f() are ignored
    decltype(func) var15; // bool()
    decltype(&func) var16; // bool(*)() 
    decltype(&A::x) var17; // double A::* 
    decltype(Foo::foo(3.0, 4u)) var18; // double 
    decltype(Bar<float, short>::bar(1,3)) var19; // float 
    return 0; 
}

In this example, the comment after each decltype statement explains the type of the defined variable.

The following example illustrates an incorrect usage of decltype(expression):
int func(){
    return 0;
}
int func(int a){
    return 0;
}
int main(){
    int i = 4;
    // Incorrect usage. func names an overload function
    decltype(func) var1;
    // Correct usage. The overload operation is not ambiguous
    decltype(func(i)) var2;
    return 0;
}

In this example, the compiler issues an error message because it does not know which func function to match.

Rules for using decltype with structure member variables

When you use decltype(expression) to get a type, and expression is an unparenthesized member variable of an object expression (with a . operator) or a pointer expression (with a -> operator), the following rules apply:
  • If the object expression or the pointer expression is specified with a constant or volatile qualifier, the type qualifier does not contribute to the result of decltype(expression).
  • The lvalueness or rvalueness of the object expression or the pointer expression does not affect whether decltype(expression) is a reference type or not.
Example:
struct Foo{
    int x;
};
int main(){
    struct Foo f;
    const struct Foo g = {0};
    volatile struct Foo* h = &f;
    struct Foo func(); 
    decltype(g.x) var1; 		// int 
    decltype(h->x) var2; 		// int 
    decltype(func().x) var3; 	// int 
    return 0; 
}

In this example, the constant qualifier of the object expression g is not desired in the result of decltype(g.x). Similarly, the volatile qualifier of the pointer expression h is not desired in the result of decltype(h->x). The object expression g and the pointer expression h are lvalues, and the object expression func() is a rvalue, but they do not affect whether the decltype results of their unparenthesized member variables are reference types or not.

If expression declared in decltype(expression) is a parenthesized structure member variable, the constant or volatile type qualifier of the parent object expression or pointer expression of expression contributes to the result of decltype(expression). Similarly, the lvalueness or rvalueness of the object expression or the pointer expression affects the result of decltype(expression).

Example:
struct Foo{
    int x;
};
int main(){
    int i = 1;
    struct Foo f;
    const struct Foo g = {0};
    volatile struct Foo* h = &f; 
    struct Foo func(); 
    decltype((g.x)) var1 = i; 			// const int& 
    decltype((h->x)) var2 = i; 		// volatile int& 
    decltype((func().x)) var3 = 1; 	// int 
    return 0; 
}

In this example, the result of decltype((g.x)) inherits the constant qualifier of the object expression g. Similarly, the result of decltype((h->x)) inherits the volatile qualifier of the pointer expression h. The object expression g and the pointer expression h are lvalues, so decltype((g.x)) and decltype((h->x)) are reference types. The object expression func() is a rvalue, so decltype((func().x)) is a nonreference type.

If you use the built-in operators .* or ->* within a decltype(expression), the constant or volatile type qualifier of the parent object expression or pointer expression of expression contributes to the result of decltype(expression), regardless of whether expression is a parenthesized or an unparenthesized structure member variable. Similarly, the lvalueness or rvalueness of the object expression or the pointer expression affects the result of decltype(expression).

Example:
class Foo{
    int x;
};
int main(){
    int i = 0;
    Foo f;
    const Foo & g = f;
    volatile Foo* h = &f; 
    const Foo func(); 
    decltype(f.*&Foo::x) var1 = i; // int&, f is an lvalue 
    decltype(g.*&Foo::x) var2 = i; // const int&, g is an lvalue 
    decltype(h->*&Foo::x) var3 = i; // volatile int&, h is an lvalue 
    decltype((h->*&Foo::x)) var4 = i; // volatile int&, h is an lvalue 
    decltype(func().*&Foo::x) var5 = 1; // const int, func() is an rvalue 
    decltype((func().*&Foo::x)) var6 = 1; // const int, func() is an rvalue 
    return 0; 
}

Side effects and decltype

If you use decltype(expression) to get a type, additional operations in the decltype parenthetical context can be performed, but they do not have side effects outside of the decltype context.

Consider the following example:
int i = 5;
static const decltype(i++) j = 4; // i is still 5

The variable i is not increased by 1 outside of the decltype context.

There are exceptions to this rule. In the following example, because the expression given to decltype must be valid, the compiler has to perform a template instantiation:
template <int N>
struct Foo{ 
static const int n=N; 
}; 
int i; 
decltype(Foo<101>::n,i) var = i; // int&

In this example, Foo template instantiation occurs, even though var is only determined by the type of the variable i.

Redundant qualifiers and specifiers with decltype

Because decltype(expression) is considered syntactically to be a type specifier, the following redundant qualifiers or specifiers are ignored:
  • constant qualifiers
  • volatile qualifiers
  • & specifiers
The following example demonstrates this case:
int main(){
    int i = 5;
    int& j = i;
    const int k = 1;
    volatile int m = 1;
    // int&, the redundant & specifier is ignored
    decltype(j)& var1 = i;
    // const int, the redundant const qualifier is ignored
    const decltype(k) var2 = 1;
    // volatile int, the redundant volatile qualifer is ignored
    volatile decltype(m) var3;
    return 0;
}
Note: The functionality of ignoring the redundant & specifiers in decltype(expression) is not supported in the current C++11 standard, but it is implemented in this compiler release.

IBM Extension Beginning of IBM Extension.

__ptr64 and __ptr128are pointer attribute specifiers which are used to specify the size of pointer type. If the expression declared in decltype(expression) is pointer type, its pointer attribute specifier is not ignored in the result of decltype(expression). If any pointer attribute is specified to decltype(expression), it results in duplicated pointer attributes on the same declaration and is diagnosed as an error.

Example:
int * ptr;
decltype(ptr) ptr1;		//ptr1 has the type "int *"
decltype(ptr) __ptr64 ptr2;	//error, __ptr64 is unexpected
int * __ptr64 ptr3;
decltype(ptr3) ptr4;		//ptr4 has the type "int * __ptr64"
decltype(ptr3) __ptr128 ptr5;	//error, __ptr128 is unexpected

IBM Extension End of IBM Extension.

Template dependent names and decltype

Without using the decltype feature, when you pass parameters from one function to another function, you might not know the exact types of the results that are passed back. The decltype feature provides a mechanism to generalize the return types easily. The following program shows a generic function that performs the multiplication operation on some operands:
struct Math{
    template <typename T>
    static T mult(const T& arg1, const T& arg2){ 
        return arg1 * arg2; 
    } 
};
If arg1 and arg2 are not the same type, the compiler cannot deduce the return type from the arguments. You can use the decltype feature to solve this problem, as shown in the following example:
struct Foo{
    template<typename T, typename U>
    static decltype((*(T*)0)*(*(U*)0)) mult(const T& arg1, const U& arg2) 
    { 
        return arg1 * arg2; 
    } 
};

In this example, the return type of the function is the type of the multiplication result of the two template-dependent function parameters.

The typeof operator and decltype

IBM extensionThe decltype feature is similar to the existing typeof feature. One difference between these two features is that decltype only accepts an expression as its operand, while typeof can also accept a type name. Consider the following example:
__typeof__(int) var1; 	// okay
decltype(int) var2; 	// error

In this example, int is a type name, so it is invalid as the operand of decltype.

Note: __typeof__ is an alternate spelling of typeof.

Related information