decltype(expression) 型指定子 (C++11)

注: IBM® は、C++11 (承認前の呼称は C++0x) の、選定された機能をサポートしています。IBM は、この標準の機能の開発および実装を継続します。この言語レベルの実装は、IBM による標準の解釈に基づいています。新しい C++ 標準ライブラリーのサポートを含め、すべての C++11 機能を IBM が実装し終えるまで、リリースごとに実装が変更される可能性があります。IBM は、IBM による C++11 の新機能の実装に関し、ソース、バイナリー、またはリスト、および他のコンパイラー・インターフェースにおいて、以前のリリースとの互換性を維持するための試みは行いません。したがって、これらの新機能は安定的なプログラミング・インターフェースとしては利用しないでください。

decltype(expression) 指定子は、C++11 に導入される型指定子です。この型指定子を使用すると、型に依存する可能性のある式の結果の型に基づいて型を取得できます。

decltype(expression) は、オペランドとして expression を取ります。decltype(expression) を使用して変数を定義する場合、これはコンパイラーによって expression の型または派生型に置換されると見なすことができます。次の例を検討してみます。
int i;
static const decltype(i) j = 4;

この例では、decltype(i) は型名 int と等価です。

decltype の一般的な使用規則

decltype(expression) を使用して型を取得する場合、以下の規則が適用されます。
  • expression が、括弧で囲まない ID 式またはクラス・メンバーである場合、decltype(expression) は、expression によって命名されるエンティティーの型です。そのようなエンティティーが存在しないか、または expression が多重定義関数のセットを命名する場合、プログラムは不適格になります。
  • 上記以外の場合に、expression が関数呼び出しまたは多重定義演算子 (expression の前後の括弧は無視されます) の呼び出しである場合、decltype(expression) は静的に選択される関数の戻りの型です。
  • 上記以外の場合に、expression が左辺値である場合、decltype(expression) は、T& です。ここで、T は expression の型です。
  • 上記以外の場合、decltype(expression) は 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; 
}

この例では、各 decltype ステートメントの後のコメントで、定義される変数の型を説明しています。

以下の例は、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;
}

この例では、コンパイラーは、一致する func 関数を認識できないため、エラー・メッセージを出します。

decltype を構造体メンバー変数と共に使用する場合の規則

decltype(expression) を使用して型を取得するときに、expressionオブジェクト式 (. 演算子を使用) またはポインター式 (-> 演算子を使用) の括弧で囲まないメンバー変数である場合、以下の規則が適用されます。
  • オブジェクト式またはポインター式が、定数または volatile 修飾子を使用して指定される場合、型修飾子は decltype(expression) の結果を導きません。
  • オブジェクト式またはポインター式の左辺値または右辺値は、decltype(expression) が参照型かどうかということに影響を与えません。
例:
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; 
}

副次作用および decltype

decltype(expression) を使用して型を取得する場合、decltype の括弧付きのコンテキストで追加命令を実行できますが、decltype コンテキストの外側には副次作用はありません。

次の例を検討してみます。
int i = 5;
static const decltype(i++) j = 4; // i is still 5

変数 i は、decltype コンテキストの外側では 1 増加しません。

この規則には例外があります。以下の例では、decltype に有効な式を指定する必要があるため、コンパイラーはテンプレートのインスタンス化を実行する必要があります。
template <int N>
struct Foo{ 
static const int n=N; 
}; 
int i; 
decltype(Foo<101>::n,i) var = i; // int&

この例では、var が変数 i の型によってのみ決定される場合でも、Foo テンプレートのインスタンス生成が行われます。

decltype での冗長修飾子および指定子

decltype(expression) は構文上は型指定子と見なされるため、以下の冗長修飾子または指定子は無視されます。
  • 定数修飾子
  • volatile 修飾子
  • & 指定子
以下に、これらの使用例を示します。
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;
}
注: decltype(expression) の & 冗長指定子を無視する機能は、現行 C++11 標準ではサポートされませんが、このコンパイラー・リリースではインプリメントされています。

IBM 拡張 IBM 拡張の始まり。

__ptr64 および __ptr128 は、ポインター型のサイズを指定するために使用されるポインター属性指定子です。decltype(expression) で宣言された expression がポインター型である場合、ポインター属性指定子は decltype(expression) の結果では無視されます。何らかのポインター属性が decltype(expression) に指定されている場合、同じ宣言内でポインター属性が重複することになり、エラーと診断されます。

例:
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 拡張 IBM 拡張の終わり。

テンプレート従属名および decltype

decltype 機能を使用せずに、関数同士でパラメーターを受け渡す場合、返される結果の正確な型を確認できない場合があります。decltype 機能は、戻りの型を簡単に一般化するための機構を提供します。以下のプログラムは、複数のオペランドで乗算演算を実行する汎用関数を示します。
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 つの関数仮パラメーターの乗算結果の型です。

typeof 演算子および decltype

IBM 拡張 decltype 機能は、既存の typeof 機能に類似しています。 これらの 2 つの機能間の 1 つの違いは、decltype はオペランドとして式を受け入れるのに対して、typeof は型名も受け入れることができる点です。次の例を検討してみます。
__typeof__(int) var1; 	// okay
decltype(int) var2; 	// error

この例では、int は型名であるため、decltype のオペランドとしては無効です。

注: __typeof__ は typeof の代替スペルです。

関連情報