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

注: IBM は、C++11 (承認前の呼称は C++0x) の、選定された機能をサポートしています。IBM は、この標準の機能の開発および実装を継続します。この言語レベルの実装は、IBM による標準の解釈に基づいています。 新しい C++11 標準ライブラリーのサポートを含め、すべての 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) を使用して型を取得する場合、以下の規則が適用されます。
  1. expression が、括弧で囲まない ID 式 またはクラス・メンバーである場合、decltype(expression) は、expression によって命名されるエンティティーの型です。そのようなエンティティーが存在しないか、または expression が多重定義関数のセットを命名する場合、プログラムは不適格になります。
  2. 上記以外の場合に、expression が xvalue である場合、decltype(expression) は、T&& です。ここで、T は expression の型です。
  3. 上記以外の場合に、expression が左辺値である場合、decltype(expression) は、T& です。ここで、T は expression の型です。
  4. 上記以外の場合、decltype(expression) は 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;
}
この例では、各 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 標準ではサポートされませんが、このコンパイラー・リリースではインプリメントされています。

テンプレート従属名および 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 の代替スペルです。