C++14には、decltype(auto)が追加された。これは、autoと似ているが、微妙に違う。decltype(auto)は、初期化子の式をdecltype()の中に書いたのと同じ挙動をする。
つまり、以下のようになる。
int i = 0 ; int && f() ; auto a1 = i ; // int decltype(auto) a2 = i ; // int auto b1 = (i) ; // int decltype(auto) b2 = (i) ; // int & auto c1 = f() ; // int decltype(auto) c2 = f() ; // int &&
単に、初期化子の式がiの場合は、autoもdecltype(auto)も変わりはない。
初期化子の式が(i)の場合は、型が違ってくる。なぜならば、括弧で囲った式は、decltype指定子の中に入れると、lvalueリファレンスになるからだ。「decltype(auto)は、初期化子の式をdecltype()の中に書いたのと同じ挙動をする」と先に書いた。すなわち、この場合のdecltype(auto)は、decltype((i))と書いた場合と同じ挙動をする。すなわち、型はint &となる。
関数呼び出し式もこの影響で、型が違ってくる。
autoとdecltype(auto)には、まだ違いがある。decltypeは式から型を推定するので、初期化子に式ではないものが使われている場合、エラーとなる。リスト初期化だ。
auto d1 = { 1, 2 } ; // std::initilizer_list<int> decltype(auto) d2 = { 1, 2 } ; // エラー、{ 1, 2 }は式ではない
また、decltype(auto)は、単独で使われなければ、エラーとなる。
auto * e1 = &i ; // int * decltype(auto) * e2 = &i ; // エラー、decltype(auto)は単独で使われなければならない
なぜdecltype(auto)が追加されたのかというと、関数の戻り値の型をreturn文から推定する機能を、lambda式以外の普通の関数にも広げようとした結果、decltype(auto)のようなヒントがほしい場合があったからだ。
int & f() ; // 戻り値の型はint auto g() { return f() ; } // 戻り値の型はint & auto h() -> decltype(auto) { return f() ; }
decltype(auto)がなければ、ここでint &を推定させたい場合は、decltype(f())と書かなければならない。
これはとても簡単で、テンプレートが絡まない例なので、いまいち恩恵が分かりにくいかもしれない。戻り値の型は推定させたい場合というのは、関数テンプレートで、インスタンス化するまで戻り値の型がわからない場合だ。例えば以下のような。
// 関数テンプレートfはメタプログラミングやオーバーロードにより、戻り値の型が型に依存して変わる。 template < typename T, typename U > auto g( T const & t, U const & u ) -> decltype( auto ) { return f( t, u ) ; }
decltype(auto)がないと、decltype( f( t, u ) )と書かなければならず、コードが重複し、冗長になり、変更時に不整合になる恐れもある。
No comments:
Post a Comment