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 an xvalue, decltype(expression) is T&&, where T is the type of expression.
- 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* g(){
return new int[0];
}
int&& fun(){
int&& var = 1;
return 1;
}
struct A{
double x;
};
template <class T> T tf(const T& t){
return t;
}
bool f(){
return false;
}
struct str1{
template <typename T, typename U>
static decltype((*(T*)0) * (*(U*)0)) mult(const U& arg1, const T& arg2){
return arg1 * arg2;
}
};
template <typename T, typename U> struct str2{
typedef decltype((*(T*)0) + (*(U*)0)) btype;
static btype g(T t, U u);
};
int main(){
int i = 4;
const int j = 6;
const int& k = i;
int&& m = 1;
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") var8 = "decltype"; // const char(&)[9]
decltype(a) var9; // int[5]
decltype(a[3]) var10 = i; // int&([] returns an lvalue)
decltype(*p) var11 = i; // int&(*operator returns an lvalue)
decltype(fun()) var12 = 1; // int&&
decltype(tf(A())) var13; // A
decltype(f()) var14; // bool
decltype((f())) var15; // bool, parentheses around f() are ignored
decltype(f) var16; // bool()
decltype(&f) var17; // bool(*)()
decltype(&A::x) var18; // double A::*
decltype(str1::mult(3.0, 4u)) var19; // double
decltype(str2<float, short>::g(1,3)) var20; // float
decltype(m) var21 = 1; // int&&
decltype((m)) var22 = m; // int&
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
- 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 an 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 nonstatic non-reference class 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 an 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
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;
}
&
specifiers in
decltype(expression) is not supported in the current C++11 standard, but it is
implemented in this compiler release.Template dependent names and decltype
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 extension](../shared_source/graphics/ngibm_begin.gif)
__typeof__(int) var1; // okay
decltype(int) var2; // error
int
is a type name, so it is invalid as the operand of decltype.
__typeof__
is an alternative spelling of
typeof.![IBM extension](../shared_source/graphics/ngibm_end.gif)