C++11 の型推論で変数定義を簡略化する - C++ プログラミング

PROGRAM


C++11 の型推論で変数定義を簡略化する

C++11 では、変数の型指定を簡略化するための auto キーワードと decltype キーワードが追加されました。

これらを活用することで、わかりきった変数定義をとても簡単に記述することができます。

auto

auto キーワードの使い方

auto キーワードは、代入する値や関数の戻り値から、それに適切な型をコンパイラが自動的にみつけます。

例えば STL の配列をイテレータで繰り返し処理する で紹介した std::vector<T> を繰り返し処理するコードの場合、この auto キーワードを使うことで簡単にプログラムすることができます。

// auto の一言で、コンパイラに適切な std::vector<T>::iterator 型を見つけさせることができます。

for (auto iterator = vector->begin(); iterator != vector->end(); iterator++)

{

}

この例では、std::vector<T> の begin 関数が返す型は std::vector<T>::iterator であることが決まっているので、その型が変数 iterator の型として選ばれます。

定数を代入するときにも auto 型を使用することが可能です。

// int 型が選ばれます。

auto a = 1;

 

// double 型が選ばれます。

auto b = 1.8;

 

// float 型が選ばれます。

auto c = 1.8f;

 

// const char* 型が選ばれます。

auto d = "TEXT";

このようにプログラムでは型の指定を省略できていますが、あくまでも、分り切っている型をコンパイラに選んでもらっているだけであって、PHP や JavaScript のように状況に合わせて自動変換されることはありません。

 

auto は単純に型の定義をコンパイラに任せるだけなので、適切な型への再代入は普通に行えます。

// ここで a が int 型であることが確定しています。

auto a = 1;

 

// a は int 型なので、同じ int 型の b に代入できます。

int b = a;

 

// ぜんぜん関係ない型に代入しようとするとコンパイル時にエラーになります。

CMyClass* c = a;

このように、コンパイル時に型チェックもしっかり行われるので、auto で指定した変数の型をプログラマが取り違えて使ってしまう心配はありません。

 

関数には使えない

この auto キーワードは、関数では使えません。

判り切った型を自動で見つける機能なので、引数にこれを指定されてもそれが何型か判断できません。

// 【間違い】auto は関数の引数では使えません。

void function(auto value)

{

}

また、関数の戻り値にも使えません。

// 【間違い】auto は関数の戻り値では使えません。

auto function()

{

return 1;

}

戻り値の場合は、場合によっては型を推定できそうなこともありますが、そこまでの自動検出機能は auto は備えていないようです。

もっとも、そこまで細かく解析して型を決定されてしまうと、プログラムを読むときの負担が増えてしまいそうなので、auto が関数の戻り値で使えないのはちょうどいい仕様なのかもしれないですね。

 

decltype

decltype の使い方

decltype キーワードは、別の変数や関数を引数に取って、その型を定義するために使います。

たとえば int 型の変数 a があったとき、decltype(a) とすると、それは変数 a で採用されている int 型になります。

int a;

decltype(a) b;

こうすることで、変数 a と同じ型の変数 b を定義することができます。

 

参照型を得たい場合には、次のように指定できます。

decltype((a)) c;

少し特殊な表現ですけど、このように値型の変数名を括弧で括ってあげることで、その型を格納する参照を得ることもできます。

ポインタ型の変数を "*" で値に変換したものを decltype に指定すると、その値を指せる参照型になります。

int* p;

 

decltype(*p) d;

 

このような decltype の表現が特に効果を発揮するのが、auto キーワードと組み合わせたときです。

auto start = instance.getStartPoint();

decltype(start) seek = instance.find(condition, start);

たとえばこのように使うことで、コンパイラが型を自動的に決定した変数 "start" と同じ型の変数 "seek" を定義することができます。

 

なお、decltype で得られる型が、参照でもないポインタでもない型の場合は、それをひとつの型として、それを参照にしたりポインタにしたりすることもできます。

CMyClass value;

 

// CMyClass* 型を定義しています。

decltype(value)* pointer = &value;

 

// CMyClass& 型を定義しています。

decltype(value)& reference = value;

 

// CMyClass&& 型を定義しています。

decltype(value)&& rreference = std::move(value);

この方法は、decltype で参照やポインタで得られる場合には使えないところに注意します。

 

クラス型やクラス型のポインタを扱う場合

decltype では、クラス型やそれを指すポインタの型を定義することもできます。

CMyClass value1;

CMyClass* pointer1 = &value1;

 

// value1 と同じ型のクラス変数が出来上がります。初期値を与えない場合、ディフォルトコンストラクタで初期化されます。

decltype(value1) value2;

 

// value1 を括弧で括って指定すると、そのクラス変数の型を指す参照が出来上がります。

decltype((value1)) reference1 = value1;

 

// pointer1 と同じ型の変数が定義されます。

decltype(pointer1) pointer2;

 

// pointer1 から値を取り出して指定した場合は、元のポインタと同じクラスを指す参照が定義されます。

decltype(*pointer1) reference2;

考え方は通常の値やポインタと同じですが、クラスの場合は基底クラスと派生クラスが混在したり、new 演算子を使ってインスタンスを作成したりと、その場合の考え方に癖があって解りにくいところがあります。

その辺りについては 派生クラスを扱うときの留意事項と decltype で得られる型 で詳しく整理しておきます。

 

関数の戻り値の型を得ることも可能

この decltype キーワードは、引数に関数を取ることもできます。

ただ、関数名と引数の型が判っているなら、それを細かく記載するより戻り値を直接書いてしまった方が楽で判りやすい気がするので、これを使用する場面はそうそうない気がします。

 

たとえば、次の関数が定義されているとします。

char* function1();

char* funciton2(int a);

 

int function3(int value);

double function3(double value);

このとき、関数 function 1 の戻り値の型と同じ型の変数 a1 を decltype を使って定義したい場合は、次のようにします。

decltype(function1()) a1;

このように decltype キーワードの引数として、目的の関数を呼び出すときと同じ書式で指定します。

ここでもし "function()" を間違えて "function" と書いてしまうと、期待通りの型にならないので注意が必要です。

 

引数を取る関数の場合は、何でもいいのでその引数に適した型の値を添えて指定します。

decltype(function2(1)) a2;

このようにしても、指定した関数がここで実行されることはなく、あくまでも定義する型を得るための参考として使われます。

 

特にこれが意味をなすのが、オーバーロードされている関数の型を取得するときです。

decltype(function3(1.5)) a3;

オーバーロードされた関数は同じ名前で異なる引数を取る関数なので、このように引数を指定することで、どの関数の戻り値の型を取得しようとしているかが判ります。

 

クラス型を引数に取る関数の場合

リテラル定数を使って関数の引数を特定できる場合はこれまでの方法でいいのですが、クラス型を引数に取る関数の場合には、その型と同じ変数を定義して、decltype のところでそれを指定する必要があります。

たとえば、次の関数が定義されていたとします。

float function4(CMyClass* value);

この関数が返す型と同じ変数を定義したいときには、あらかじめ引数に取る型と同じ変数を定義した上で、それを decltype のところで指定します。

CMyClass* dummy;

decltype(function4(dummy)) a4;

これで、関数の戻り値と同じ型の変数を定義することができました。

 

関数ポインタ型も定義できる、そして便利で判りやすい

この decltype に関数ポインタを指定すれば、その型の変数を定義することもできます。

decltype(&function4) f4;

関数のポインタを使用する で書いた、関数を変数に代入するときと同じように、関数のポインタを "&" で取って指定することによって、その関数を格納できる変数を用意することができます。

 

この decltype が使えなかった頃は、関数ポインタの型は次のように定義する必要がありました。

float (*f4)(CMyClass*);

この書き方だと慣れるまで読みづらいですし、戻り値や引数の型を全て書かないといけなかったので、関数ポインタについては従来の記載よりも decltype を使った方が、ずいぶん楽に、そして読みやすいコードが書けた気がします。


[ もどる ]