このページはC++20に採用された言語機能の変更を解説しています。
のちのC++規格でさらに変更される場合があるため関連項目を参照してください。
概要
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 例外仕様 属性 -> 戻り値の型 { 関数の本体 }
-
テンプレートパラメータリストは省略可能
- テンプレートパラメータには、ほかのテンプレート構文と同様に、
typename
、class
どちらのキーワードも使用できる - テンプレートパラメータには、テンプレートテンプレートパラメータも指定できる
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)...);
};