C++11の新機能として簡単に使用できるのが型推論です。
従来のC++では変数等を作成する場合、型(char、int、double、...etc)を明示的に指定する必要があります。
しかし、自分が作成したものでないライブラリ等を利用する際、関数の戻り値が複雑な場合などに受け取り側の型を指定するのが難しい、可読性が下がるなどの問題があります。
そこでC++11では型推論という機能があります。
型推論を使用することによって受け取る値に応じてコンパイル時に型を決定してくれます。
1: auto a = 1; //aはint型 2: auto b = 1 + 1.0; //bはdouble型 3: decltype(a) c; //cはint型 4: decltype(a + b) d; //dはdouble型
autoは変数が受け取る値に応じて型を決定します。なので、宣言した時に必ず初期化しなければいけません。
decltypeは()内の値に応じて方を決定します。こちらは初期化する必要はありません。また、4行目を見れば分かるように式を入れることも可能です。
これだけだと、型に対して柔軟に対応出来るようになっただけの用に思えるかもしれませんので、実際に使用する場面について説明します。
STLのvectorの配列の中身をiteratorを使用して見る際、従来のやり方の場合
1: #include <iostream> 2: #include <vector> 3: 4: using namespace std; 5: 6: int main() 7: { 8: vector<int> vec; 9: 10: for (int i = 0; i < 10; i++) 11: { 12: vec.push_back(i); 13: } 14: 15: vector<int>::iterator it = vec.begin(); 16: 17: while (it != vec.end()) 18: { 19: cout << *it << endl; 20: 21: it++; 22: } 23: 24: return 0; 25: }
のようになると思います。この時、15行目の
vector<int>::iterator it = vec.begin();
がイテレータにvecの先頭アドレスを代入しているのですが、
vector<int>::iterator
このイテレータは、int型を格納するvectorにしか使用できません。また、今回の場合int型なので短いですが、型の種類によっては長くなり一行で書いた際に可読性が下がる問題があります。
これを先程のautoを使用すると
auto it = vec.begin();
になり、非常にスマートになるのが分かります。また、同じようにイテレータを使うもの(list、map、...etc)に関しては全てこの書き方で済みます。
書き換えたソースコード全体は
1: #include <iostream> 2: #include <vector> 3: 4: using namespace std; 5: 6: int main() 7: { 8: vector<int> vec; 9: 10: for (int i = 0; i < 10; i++) 11: { 12: vec.push_back(i); 13: } 14: 15: auto it = vec.begin();//イテレータを書くのが楽に! 16: 17: while (it != vec.end()) 18: { 19: cout << *it << endl; 20: 21: it++; 22: } 23: 24: return 0; 25: }
になります。
次に、decltypeを使用する例を説明します。プログラムによっては
1: #include <vector> 2: 3: using namespace std; 4: 5: int main() 6: { 7: auto a = func();//funcで複雑な型の何かが返される 8: 9: vector</*変数aの型を格納したい!*/> vec; 10: 11: return 0; 12: }
のように、vector等で関数から受け取った値の型を指定したい場合があります。しかし、autoはあくまでも受け取った際の型を推論してくれるだけなので、9行目を
vector<auto> vec;
や
vector<a> vec;
というように書くことは出来ません。そこでdecltypeを使用することによって実現することが出来ます。
vector<decltype(a)> vec;
decltypeはこのようにテンプレートを使用したものに対して型を指定する時に使用することができます。
つまり、先ほどのソースコードを修正すると
1: #include <vector> 2: 3: using namespace std; 4: 5: int main() 6: { 7: auto a = func();//funcで複雑な型の何かが返される 8: 9: vector<decltype(a)> vec; 10: 11: return 0; 12: }
のようになります。
最後にautoとdecltypeを複合して、戻り値の型が変化する関数の書き方を説明します。
テンプレートを2つ使用した関数で戻り値をもテンプレートの場合
1: #include <iostream> 2: 3: using namespace std; 4: 5: template<typename T1, typename T2> 6: T1 func(T1 a, T2 b) 7: { 8: return a + b; 9: } 10 11: int main() 12: { 13: cout << func(1, 1.5) << endl;//2が出力される 14: 15: cout << func(1.5, 1) << endl;//2.5が出力される 16: 17: return 0; 18: }
どちらか一方の型を返すため、使用する側が注意する必要があります。(1.0にすればよくね?っていうツッコミした奴、屋上)
これをC++11では
1: #include <iostream> 2: 3: using namespace std; 4: 5: template<typename T1, typename T2> 6: auto func(T1 a, T2 b) -> decltype(a + b) 7: { 8: return a + b; 9: } 10 11: int main() 12: { 13: cout << func(1, 1.5) << endl;//2.5が出力される 14: 15: cout << func(1.5, 1) << endl;//2.5が出力される 16: 17: return 0; 18: }
と書く(6行目に注目)ことによって、どちらがint型であっても、戻り値をdoubleにすることが出来ます。
※ちなみにC++14では記述が簡単になります。
//C++14の場合 template<typename T1, typename T2> auto func(T1 a, T2 b) { return a + b; }
今回説明したauto、decltypeは複雑な機能ではないのですぐに使用できるようになると思います。(とりあえずイテレータにautoはオススメ)
ただし何でもかんでもautoにしてしまうと意味が分からなくなるので、「型が複雑で特定しづらいor特定出来るが記述が長い」時以外は従来通りに型を指定して書くようにしたほうがいいです。
以上で型推論の説明を終わります。