2013-08-27

decltype(auto)

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 ) )と書かなければならず、コードが重複し、冗長になり、変更時に不整合になる恐れもある。

Return type deduction for normal functions

No comments: