このページはC++14に採用された言語機能の変更を解説しています。
のちのC++規格でさらに変更される場合があるため関連項目を参照してください。
概要
関数宣言の構文において、先頭の戻り値型をauto
もしくはdecltype(auto)
とすることで、戻り値の型が関数のreturn
文から推論される。
// 関数f()の戻り値型はint
auto f()
{
return 42;
}
decltype(auto)
は、戻り値として変数への参照を返したい場合に使用する。
// autoの場合はintが戻り値型となるが、
// decltype(auto)とすることでint&が戻り値型となる。
decltype(auto) f(int& r)
{
return r;
}
この関数宣言構文はメンバ関数に対しても使用できる。
先行宣言をする場合、その関数を使用するコードから、関数の定義が見える必要がある。
仕様
先行宣言と再宣言
戻り値の型を推論する関数宣言構文は、先行宣言と再宣言を許可する。
auto f(); // 先行宣言
auto f() // 定義
{
return 42;
}
auto f(); // 再宣言
これは、関数テンプレートの場合も同様である。
ただし、戻り値の型を推論する関数宣言構文で先行宣言した場合に、通常の戻り値型を指定する関数宣言構文では再宣言できない。
auto f(); // 先行宣言
int f(); // コンパイルエラー : 再宣言に別な関数宣言構文を使用できない
関数テンプレートの明示的な特殊化とインスタンス化の場合も、同じ関数宣言構文を使用する必要がある。
この構文は、型変換演算子にも適用される:
struct X {
operator auto() const {
return 1;
}
};
int x = X{};
複数個のreturn文
複数のreturn
文がある場合にはそれらの式に共通する型が戻り値の型となる。
複数のreturn
文があり、それらに共通する型がない場合、プログラムは不適格となる。
// 戻り値の型は、intとconst int&の共通の型であるint
auto f()
{
int x = 3;
const int& cx = x;
if (true)
return x;
else
return cx;
}
再帰
戻り値の型を推論する関数宣言構文は、再帰を許可する:
auto sum(int i) {
if (i == 1)
return i; // 戻り値の型がintに推論される
else
return sum(i-1) + i; // OK : まだ戻り値の型が確定はしていない
}
ただし、戻り値の型が確定しない再帰は許可しない:
auto h() { return h(); } // コンパイルエラー
相互再帰は許可しない:
// パラメータnが偶数か奇数かを判定する関数
// お互いの関数が、お互いの実装を使用する
auto is_even(int n);
auto is_odd(int n);
auto is_even(int n)
{
return n == 0 ? true : is_odd(n - 1); // コンパイルエラー : 戻り値の型を推論できない
}
auto is_odd(int n)
{
return n == 0 ? false : is_even(n - 1); // コンパイルエラー : 戻り値の型を推論できない
}
テンプレートによる戻り値型の置き換え失敗は、SFINAEではなくコンパイルエラー
戻り値の型を推論する関数宣言構文において、戻り値の型がテンプレートのインスタンス化で推論される場合、そのインスタンス化の失敗は、テンプレートの置き換え失敗ではなくエラーとする。decltype
にはラムダ式を渡せないために、decltype
によって戻り値型を推論する関数宣言構文ではラムダ式を返せないように思えるが、この仕様によって、戻り値の型を推論する関数宣言構文で、ラムダ式をreturn
文で返すことを許可する。
// ラムダ式によって定義される、一意な名前の関数オブジェクト型を返す
auto f()
{
return []{};
}
戻り値型推論の補助
戻り値の型を推論する関数宣言構文において、プレースホルダー型であるauto
に対して、CV修飾、参照修飾、ポインタ修飾などを付加して戻り値型の推論を補助できる。
たとえば、以下のように静的変数を返した場合、関数f()
の戻り値型はint
であると推論される:
static int static_value = 3;
auto f()
{
return static_value;
}
この関数が必ずint&
を返したい、という場合には、戻り値型のプレースホルダーをauto&
のように参照修飾することで、戻り値を参照として返せる:
static int static_value = 3;
auto& f()
{
return static_value;
}
この機能は、戻り値の型を後置する関数宣言構文とラムダ式の、後置戻り値型(trailing return type)に対しても適用できる。以下のページを参照:
初期化子リストの推論
戻り値の型を推論する関数宣言構文において、波カッコで囲まれた初期化子リストは、return
文で返せない。
これは、配列をreturn
文で返せない仕様によるものである。
仮想関数への適用は許可しない
戻り値の型を推論する関数宣言構文は、仮想関数には使用できない。
これは、オーバーライドのチェックと仮想関数テーブルのレイアウトが複雑になるためである。
例
#include <iostream>
// 関数オブジェクトfを呼び出した結果の型を、関数g()の戻り値型とする
template <class F>
auto g(F f)
{
return f();
}
int main()
{
// 変数resultの型はint
auto result = g([]{ return 3; });
std::cout << result << std::endl;
}
出力
3
この機能が必要になった背景・経緯
戻り値の型を推論する関数宣言構文は、C++11で導入されたauto
キーワードによる変数の型推論、ラムダ式での戻り値型推論に合わせて導入された。
C++11で、戻り値の型を後置する関数宣言構文が導入され、それによってdecltype
を使用してreturn
文に指定した式の結果となる型を容易に返せるようになった:
// 関数オブジェクトfを呼び出した結果の型を、関数g()の戻り値型とする
template <class F>
auto g(F f) -> decltype(f()) { return f(); }
しかしこの表記は、同じ式を2回書くことになるため冗長だった。戻り値の型を推論できるようになったことで、これがより短く簡潔に書けるようになった:
template <class F>
auto g(F f) { return f(); }
検討されたほかの選択肢
戻り値の型を後置する関数宣言構文のページを参照。