型推論

auto (C++11)

C++ は C 言語 の 機能 を 引き継い だ ため、 auto という 時代遅れ の 役に立た ない キーワード が そのまま 存在 し て い まし た。 C++11 に なっ て、 auto を 新しい 意味、 つまり 型 推論 の ため に 使う こと となり まし た。 auto は 幾つ かの 場面 で 使用 でき ます。 まず、auto は 初期化 を 伴う 変数 宣言 時 に、 型名 として 使用 でき ます。 こう する と、 初期化 の 式 の 内容 から 型 が 推論 さ れ ます。

char f1() {
  return 'A'; 
} 
int& f2() { 
  static int n = 0;
  return n;
}
int main() {
  auto n1 = 10; //int
  const auto n2 = 10; //const int
  auto n3 = n1 * 0. 1; //double 
  auto n4 = f1(); // char 
  auto n5 = f2(); // int 
  auto& n6 = f2(); // int& 
}

n5 と n6 の 違い に 注意 が 必要 です。 auto によって 推論 さ れる 型 は、 参照 を 表す &(および C++11 で 追加 さ れ た 右辺 値 参照 を 表す &&)が 取り除か れ た もの になり ます。 参照 に し たけれ ば auto& の よう に、 参照 で ある こと を 明示 し なけれ ば なり ません。

decltype (C++11)

C ++ 11 で 追加 さ れ た decltype は、 コンパイル 時 に、 式 の 型 を 取得 する 機能 です。式 の 部分 の 型 を 推論 し て、「 decltype(式)」 全体 が、 型名 に 置き換わり ます。 例えば、 次 の よう に 使え ます。

decltype( 10.5) n1; 
decltype( 10.5 * 2) n2;

この場合、 n1、n2 はいずれも double 型ということになります。

int& f 2() { 
 static int x = 0; 
 return x; 
} 
int main() {
 decltype( f 2()) n 2 = f 2(); // n 2 は int& 
 auto n 3 = f 2(); //n 3 は int 
}

decltype を 使っ た n2 は 参照型 になり ます が、 auto を 使っ た n3 は 非参照 型 になります。n2は参照型なので、初期値が必要です。

戻り値型の指定

decltype を うまく 利用 できる 場面 は、 関数 の 戻り 値 型 を 指定 する とき です。 例えば、 add 関数 テンプレート の 戻り 値 型 を 考え て み ましょ う。 C++03 までは、 次 の よう に 書く しか あり ませ ん。

template < typename Result, typename T1, typename T2 > 
Result add( T1 a, T2 b) {
  return a + b; 
} 
double result = add < double >( 10, 3. 5);

実際 に add 関数 テンプレート を 使う とき に なる まで、 T1 型 と T2 型 が 具体的 に 何 型 で ある か 分から ない ため、 + 演算子 を 適用 し た 結果 得 られる 型 も、 何 型 か 分かり ませ ん。 その ため、 戻り 値 型 について も、 呼び出し 側 で 指定 する 必要 が あり まし た。しかし、 型 推論 の 機能 の 存在 を 考える と、 return に 与える 式 を 使っ て、 戻り 値 型 を 推論 する こと が でき ても 良 さ そう です。 つまり、 decltype( a + b) とすれば いい のでは ない かと 思え ます。次 の よう に 書ける こと を 期待 し ます が、 残念 ながら これ は コンパイル エラー になり ます。

template < typename T1, typename T2 > 
decltype( a + b) add( T1 a, T2 b) // コンパイル エラー 
{ 
  return a + b; 
}

原因 は、 decltype( a + b) の「 a」 と「 b」 が、 仮 引数 の a、 b よりも 手前 側 に 現れる ため、 a と b が 仮 引数 の 名前 で ある こと を コンパイラ が 理解 でき ない こと に あり ます。この 問題 を 解決 する ため に、 C++11 で、 戻り 値 型 を 関数 宣言 の 末尾 に 記述 する 構文 が 追加 さ れ まし た。

#include < iostream > 
template < typename T1, typename T2 > 
auto add( T1 a, T2 b) -> decltype( a + b) {
  return a + b; 
}
int main() {
  std:: cout << add( 10, 3.5) << std:: endl;
}

関数 宣言 の 末尾 に「->」 を 置き、 その 後ろ に 戻り 値 型 を 記述 し ます。 従来、 戻り 値 型 を 記述 し て い た 位置 には 変わり に「 auto」 を 置き ます。これで、戻り 値 型 を 指定 する 仕事 を、 呼び出し 側 に 押し付け なく て 済む よう になり ました。

しかし、 この 方法 では まだ、 型 推論 の 使い方 として は 中途半端 だ と 言え ます。 なぜなら、 return に 与える 式 を、 decltype にも 記述 し なけれ ば なら ず、 同じ こと を 2 回 書か ね ば なら ない から です。

decltype(auto) (C++14)

C++14 には decltype( auto) という、 decltype と auto が 合体 し た 名前 の 機能 が あり ます。 decltype( auto) は基本的には decltype と 同じく、型を取得するものです。

int& f(){
  static int x = 0;
  return x; 
}
int main() {
  auto n1 = f(); // int 
  decltype( f()) n2 = f(); // int& 
  decltype( auto) n3 = f(); // int& 
}

decltype( auto) の「 auto」 は、 decltype( 式) の「 式」 にあたる 部分 を、 auto が 型 を 決める 方法 と 同じ 方法 で 決定 する こと を 表し て い ます。

関数の戻り値型の推論 (C++14)

ここ で 取り上げる 戻り 値 型 の 推論 は、 型 推論 に すべて を 任せる こと が でき ます。 その 方法 には、 auto を 使う もの と、 decltype( auto) を 使う もの とが あり ます。いずれ の 方法 でも、 式 を 持っ た return 文 が 1つ だけ なら ば、 その 式 の 型 に 推論 さ れ ます。 return 文 が 複数 登場 する 場合、 その すべて に 共通 する 型 が あれ ば、 その 型 に 推論 さ れ ます。 共通 する 型 が 推論 でき ない 場合 は、 コンパイル エラー となり ます。 なお、 return 文 が 無い 場合 や、 戻り 値 の 指定 が 無い return 文 の 場合 は void 型に 推論 さ れ ます。

autoによる戻り値型の推論 (C++14)

C++14 からは、 関数 の 戻り 値 型 を「 auto」 と する こと で、 戻り 値 型 の 推論 が 可能 です。 auto は C++11 で 加わっ た 機能 です が、 関数 の 戻り 値 型 として 使える よう に なっ た のは C++14 から です。 C++11 で 戻り 値 型 に auto を 使う 場合 は、 戻り 値 型 の 後置 構文 と セット で なけれ ば なり ませ ん。

#include < iostream > 
template < typename T1, typename T2 > 
auto add( T1 a, T2 b) {
  return a + b; 
}
int main() {
  std:: cout << add( 10, 3. 5) << std:: endl;
}

auto は 参照 の & を 取り除い て しまう ので、 これ を 避ける 必要 が ある 場合 は 注意 が 必要 です。 参照 で 返し たけれ ば、 すぐ 後 で 取り上げる「 decltype( auto)」 による 方法 を 使い ます。

decltype(auto) による戻り値型の推論 (C++14)

もう 1つ の 方法 として、 戻り 値 型 を「 decltype( auto)」 と する こと が でき ます。 どの よう に 型 推論 さ れる かが 少し 異なり ます が、 目的 は 同じ です。

#include < iostream > 
template < typename T1, typename T2 > 
decltype(auto) add( T1 a, T2 b) { 
  return (a + b); // VisualStudio 2015/ 2017 では ( ) で囲まないと警告される 
} 
int main() {
  std:: cout << add( 10, 3.5) <<std:: endl; 
}

また、 return 文 で 単独 の 変数 を 指定 する とき に、 うっかり ( ) で 囲む と、 想定外 の 型 に 推論 さ れる かも 知れ ない こと に 注意 し て 下さい。 単なるa は int 型 です が、( a) の よう に ( ) で 囲む と、 式 に なる ため int& 型 になり ます。 これ は decltype( auto) の ルール では なく、 C ++ 全般 の ルール です。

関数宣言だけでは戻り値型が推論できない

auto を 使う に せよ、 decltype(auto) を 使う に せよ、 戻り 値 型 は、 return 文 の 内容 によって 推論 さ れる 訳 です から、 関数 定義 が 無い と 推論 し よう が あり ませ ん。 その ため、 戻り 値 型 を 推論 さ せ て いる 関数 を 呼び出す とき には、 呼び出し 位置 から 関数 定義 が 見え て い ない と いけ ませ ん。

#include < iostream > 
auto f(); // 宣言 だけ 
int main() { 
  std:: cout << f() << std:: endl; // コンパイルエラー。f()の戻り値型が不明
} 
auto f() // 定義 { 
  return "xyz"; // これがあって初めて、戻り値型が推論できる 
}

コメント

タイトルとURLをコピーしました