最終更新日時:
が更新

履歴 編集

ジェネリックラムダのテンプレート構文(C++20)

概要

C++14では、ラムダ式のパラメータ型にautoキーワードを使用することで、任意の型のパラメータを受け取れるようになった:

auto f = [](auto x, auto y) {};

f(1, "Hello"); // xの型はint、yの型はconst char*
f(3.14, 'A');  // xの型はdouble、yの型はchar

C++20では、任意の型のパラメータを受け取るために、関数テンプレートと同様の、テンプレートパラメータの定義ができるようになる:

auto f = []<class T>(const std::vector<T>& v) {
  if (v.empty()) {
    return T();
  }
  else {
    return v.front();
  }
};

std::vector<int> v = {1, 2, 3};
std::vector<std::string> w;

f(v); // Tの型はint
f(w); // Tの型はstd::string

仕様

  • テンプレートパラメータの定義を含むラムダ式の構文は、以下のようになる:

    [キャプチャリスト] <テンプレートパラメータリスト> (パラメータリスト) mutable 例外仕様 属性 -> 戻り値の型 { 関数の本体 }
    

  • テンプレートパラメータリストは省略可能

  • テンプレートパラメータには、ほかのテンプレート構文と同様に、typenameclassどちらのキーワードも使用できる
  • テンプレートパラメータには、テンプレートテンプレートパラメータも指定できる
  • autoキーワードとの共存ができる
  • autoキーワードを使用した場合、パラメータごとに異なるテンプレートパラメータとなるが、この構文では全てのパラメータを同じ型にすることもできる
  • 推論規則は関数テンプレートと同じ

この機能が必要になった背景・経緯

autoキーワードを使用したジェネリックラムダは、柔軟さが足りなかった。たとえばパラメータをstd::vectorコンテナにして要素の型だけ可変にしたい場合、関数テンプレートでは「template <class T> void f(std::vector<T> v)」のように書けばよかった。しかし、autoキーワードの場合にはこのような型推論のための構文が使用できず、パラメータをstd::vectorに限定することがむずかしかった。

// 型Tがstd::vectorコンテナかを判定する
template <typename T> struct is_std_vector : std::false_type { };
template <typename T> struct is_std_vector<std::vector<T>> : std::true_type { };

auto f = [](auto vector) {
  static_assert(is_std_vector<decltype(vector)>::value, "");
};

テンプレート構文では以下のようになる:

auto f = []<typename T>(std::vector<T> v) {
};

また、同じ状況において、std::vectorとして受け取ったコンテナ型の要素となる型を取り出したい場合、関数テンプレートでは型推論によって取り出されたT型を単に使用すればよかった。autoキーワードを使用する場合には、以下のようにvalue_typeを取り出す冗長な指定が必要になった:

auto f = [](auto vector) {
  using T = typename decltype(vector)::value_type;
};

ほかの状況として、2つめのパラメータが1つめのパラメータによって型を限定できる、という場合に、関数テンプレートでは以下のように記述できた:

template <class Iterator>
void advance(Iterator it, typename std::iterator_traits<Iterator>::difference_type n) {
  //…
}

autoキーワードの場合は、そういった状況ではdecltypeを使用しなければならなかった:

auto advance = [](auto it, typename std::iterator_traits<decltype(it)>::difference_type n) {
  //…
};

テンプレート構文では以下のようになる:

auto advance = []<typename Iterator>(Iterator it, typename std::iterator_traits<Iterator>::difference_type n) {
  //…
};

最後に、完全転送の問題として、autoキーワードを使用した場合、std::forward()関数に型を指定する唯一の方法はdecltypeを使用することだった:

auto f = [](auto&&... args) {
  g(std::forward<decltype(args)>(args)...);
};

これは正しく動作し、この書き方はScott Meyersのブログでも記事で紹介されているが、Meyersが記事を書かなければならなかったということが、これがユーザーにとって難しい問題であることを表していた。

テンプレート構文では以下のようになる:

auto f = []<typename... Args>(Args&&... args) {
  g(std::forward<Args>(args)...);
};

関連項目

参照