The decltype(expression) type specifier (C++11)
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.
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
- 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.
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.
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
.
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.
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).
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).
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.
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.
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
- constant qualifiers
- volatile qualifiers
- & specifiers
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;
}
Beginning of IBM Extension.
__ptr64
and __ptr128
are 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.
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
End of IBM Extension.
Template dependent names and decltype
struct Math{
template <typename T>
static T mult(const T& arg1, const T& arg2){
return arg1 * arg2;
}
};
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
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.
__typeof__
is an alternate spelling of typeof.Related information