C++14の新機能: 関数の戻り値の型推定
C++14では、通常の関数に、戻り値の型推定という機能が加わった。
C++14の戻り値の型推定を解説する前に、まずC++11の話を使用。
戻り値の型推定は、C++11のlambda式に備わっていた。ただし、C++11では、lambda式が戻り値の型推定を行うためには、本体はreturn文ひとつだけでなければならないという制約があった。return文ひとつだけでない場合、戻り値の型はvoid型となる。
// C++11のlambda式
// 戻り値の型はint
[](){ return 0 ; } ;
// 戻り値の型はvoid
[]() { } ;
// ill-formed.
// 戻り値の型はvoidなのにint型を返している。
[]()
{
int x = 0 ;
return x ;
} ;
C++14では、通常の関数の戻り値の型推定機能の導入と共に、そのような制限は撤廃された。
// C++14のlambda式
// well-formed
[]()
{
int x = 0 ;
return x ;
} ;
また、C++11では、placeholder typeという機能が導入された。
// C++11のplaceholder type
auto x = 0 ; // int
C++14では、placeholder typeに、decltype(auto)が追加された。
// C++14のplaceholer type
decltype(auto) x = 0 ; // int
decltype(auto)とautoの違いについては、前回の解説を参照。
本の虫: C++14の新機能: decltype(auto)
C++14に追加された通常の関数の戻り値の型推定は、このplaceholder typeを関数の戻り値の型として指定できる。その場合、型はreturn文のオペランドの式から推定される。
// C++14の戻り値の型推定
// void
auto f() { }
// int
auto g() { return 0 ; }
// int
decltype(auto) h() { return 0 ; }
戻り値の型としてのplaceholder typeは、後置することもできる。
auto f() -> auto
{
return 0 ;
}
この文法が認められている理由は、主に、lambda式にplaceholder typeを記述するためだ。
// int &
[]() -> auto &
{
static int x ;
return x ;
} ;
decltype(auto)は、主に戻り値の型推定を行わせるために追加された。
int & f() ;
// int
auto g() { return f() ; }
// int &
decltype(auto) h() { return f() ; }
C++14の戻り値の型推定には、C++11のlambda式の戻り値の型推定のような、とても使いづらい制限はない。普通に書けば普通に動く。ただし、細かい点で注意すべきところはある。
すべてのreturn文のオペランドの式の型は、一致していなければならない。
auto f()
{
return 0 ;
return 0.0 ; // ill-formed. 型の不一致
}
再帰はできる。もちろん、return文のオペランドの式の型は、すべて一致していなければならない。
auto ackermann( int m, int n )
{
if ( m == 0 )
return n + 1 ;
if ( n == 0 )
return ackermann( m - 1, 1 ) ;
else
return ackermann( m - 1, ackermann( m, n-1 ) ) ;
}
placeholder typeの型推定にあたって、まだ型推定されていないplaceholder typeが現れる場合は、エラーとなる。これは、変数の場合と同等だ。
auto x = x ; // エラー
auto f() ;
// エラー
auto g() { return f() ; }
placeholder typeを使った関数を再宣言する場合は、同じplaceholder typeを使わなければならない。推定される具体的な型を使うことはできない。
auto f() ; // OK、宣言
auto f() { return 0 ; } // OK、定義
auto f() ; // OK、再宣言
// ill-formed
// 同じplaceholer typeを使わなければならない
decltype(auto) f() ;
int f() ;
関数テンプレートの明示的実体化や明示的特殊化でも、同じplaceholder typeを使わなければならない。
// #1
template < typename T > auto f( T t ) { return t ; }
// 明示的実体化
extern template auto f( int ) ; // OK
extern template char f( char ) ; // エラー
// 明示的特殊化
template < > auto f( short ) ; // OK
template < > long f( long ) ; // エラー
// #2
// #2は#1とは異なるテンプレートであることに注意
template < typename T > T f( T t ) { return t ; }
extern template char f( char ) ; // OK, #2の明示的実体化
template < > long f( long ) ; // OK、#2の明示的特殊化
もちろん、上記のテンプレートは、実際に実体化して使う際に曖昧になるが、それは使う場合の話であって、テンプレートの話ではない。
virtual関数で戻り値の型推定機能を使うことはできない。提案論文のN3638によれば、技術上可能ではあるが、オーバーライドのチェックとvtableレイアウトが複雑化するため、現時点では禁止しておくとのことだ。
戻り値の型推定で、std::initializer_listを推定することはできない。
placeholder typeで変数を宣言する場合は、std::initalizer_listを推定することができるが、std::initializer_listは実装の都合上、自動ストレージ上に構築して参照渡しをするため、関数の戻り値として返すのは不適切であるという理由に寄る、
// OK
// std::initializer_list<int>
auto x = { 1, 2, 3 } ;
// エラー
// std::initializer_listは推定できない
auto f()
{
return { 1, 2, 3 } ;
}
以上、細かい点はあるが、通常の利用では、それほど気にする必要はない。普通に書けば、自然に動くように設計されているはずだ。
See Also:
本の虫: C++14の新機能: decltype(auto)
ドワンゴ広告
ドワンゴは本物のC++プログラマーを募集しています。
CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0