本の虫

著者:江添亮
ブログ: http://cpplover.blogspot.jp/
メール: boostcpp@gmail.com
Twitter: https://twitter.com/EzoeRyou
GitHub: https://github.com/EzoeRyou

アマゾンの江添のほしい物リストを著者に送るとブログ記事のネタになる

筆者にブログのネタになる品物を直接送りたい場合、住所をメールで質問してください。

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の新機能: 2進数リテラル

本の虫: C++14の新機能: decltype(auto)

ドワンゴ広告

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0