decltype(expression) 指定子は、C++11 に導入される型指定子です。この型指定子を使用すると、型に依存する可能性のある式の結果の型に基づいて型を取得できます。
int i;
static const decltype(i) j = 4;
この例では、decltype(i) は型名 int と等価です。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;
}
この例では、各 decltype ステートメントの後のコメントで、定義される変数の型を説明しています。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;
}
この例では、コンパイラーは、一致する func 関数を認識できないため、エラー・メッセージを出します。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;
}
この例では、オブジェクト式 g の定数修飾子は、decltype(g.x) の結果では望まれません。
同様に、ポインター式 h の volatile 修飾子は、decltype(h->x) の結果では望まれません。オブジェクト式 g およびポインター式 h は左辺値であり、オブジェクト式 func() は右辺値ですが、これらは括弧で囲まないメンバー変数の decltype の結果が参照型かどうかということには影響を与えません。decltype(expression) で宣言される expression が、括弧で囲まれた静的でない非参照クラス・メンバー変数である場合、expression の親オブジェクト式または親ポインター式の定数または volatile 型修飾子は、decltype(expression) の結果に反映されます。同様に、オブジェクト式またはポインター式の左辺値または右辺値は、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;
}
この例では、decltype((g.x)) の結果は、オブジェクト式 g の定数修飾子を継承します。
同様に、decltype((h->x)) の結果は、ポインター式 h の volatile 修飾子を継承します。
オブジェクト式 g およびポインター式 h は左辺値であるため、decltype((g.x)) および decltype((h->x)) は参照型です。オブジェクト式 func() は右辺値であるため、decltype((func().x)) は非参照型です。組み込み演算子 .* または ->* を decltype(expression) 内で使用する場合、expression の親オブジェクト式または親ポインター式の定数または volatile 型修飾子は、expression が括弧で囲まれているか、または括弧で囲まない構造体メンバー変数であるかに関係なく、decltype(expression) の結果を導きます。同様に、オブジェクト式またはポインター式の左辺値または右辺値は、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;
}
int i = 5;
static const decltype(i++) j = 4; // i is still 5
変数 i は、decltype コンテキストの外側では 1 増加しません。template <int N>
struct Foo{
static const int n=N;
};
int i;
decltype(Foo<101>::n,i) var = i; // int&
この例では、var が変数 i の型によってのみ決定される場合でも、Foo テンプレートのインスタンス生成が行われます。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;
}
struct Math{
template <typename T>
static T mult(const T& arg1, const T& arg2){
return arg1 * arg2;
}
};
arg1 および arg2 が同じ型でない場合、コンパイラーは引数から戻りの型を推定できません。この問題を解決するために、以下の例に示すように、decltype 機能を使用できます。struct Foo{
template<typename T, typename U>
static decltype((*(T*)0)*(*(U*)0)) mult(const T& arg1, const U& arg2)
{
return arg1 * arg2;
}
};
この例では、関数の戻りの型は、テンプレートに依存する 2 つの関数仮パラメーターの乗算結果の型です。
decltype 機能は、既存の typeof 機能に類似しています。
これらの 2 つの機能間の 1 つの違いは、decltype はオペランドとして式のみを受け入れるのに対して、typeof は型名も受け入れることができる点です。次の例を検討してみます。__typeof__(int) var1; // okay
decltype(int) var2; // error