Class template argument deduction (CTAD) (since C++17)
クラステンプレートをインスタンス化するためには、 class template のすべてのテンプレート引数が既知である必要がありますが、すべてのテンプレート引数を明示的に指定する必要はありません。以下のコンテキストでは、コンパイラが初期化子の型からテンプレート引数を推論します:
std::pair p(2, 4.5); // std::pair<int, double> p(2, 4.5); と推論される std::tuple t(4, 3, 2.5); // auto t = std::make_tuple(4, 3, 2.5); と同じ std::less l; // std::less<void> l; と同じ
- new式 :
template<class T> struct A { A(T, T); }; auto y = new A{1, 2}; // 割り当てられた型は A<int>
- 関数形式キャスト 式:
auto lck = std::lock_guard(mtx); // std::lock_guard<std::mutex> と推論される std::copy_n(vi1, 3, std::back_insert_iterator(vi2)); // std::back_insert_iterator<T> と推論される // ここで T はコンテナ vi2 の型 std::for_each(vi.begin(), vi.end(), Foo([&](int i) {...})); // Foo<T> と推論される // ここで T は一意のラムダ型
template<class T> struct X { constexpr X(T) {} }; template<X x> struct Y {}; Y<0> y; // OK, Y<X<int>(0)> |
(C++20以降) |
目次 |
クラステンプレートの推論
暗黙的に生成されるデダクションガイド
関数形式キャストまたは変数宣言において、型指定子がプライマリクラステンプレートの名前
C
のみで構成されている場合(つまり、付随するテンプレート引数リストがない場合)、推論の候補は以下のように形成されます:
-
Cが定義されている場合、名前付きプライマリテンプレートで宣言されている各コンストラクタ(またはコンストラクタテンプレート)C iに対して、架空の関数テンプレートF iが構築され、以下のすべての条件が満たされる:
-
-
F iのテンプレートパラメータは、Cのテンプレートパラメータに続き(もしC iがコンストラクタテンプレートである場合)、C iのテンプレートパラメータが追加されます (デフォルトテンプレート引数も含まれます)。
-
|
(C++20以降) |
-
-
F iの パラメータリスト はC iのパラメータリストです。 -
F iの戻り値型は、Cにクラステンプレートのテンプレートパラメータを<>で囲んで続けたものです。
-
-
Cが定義されていないか、コンストラクタを宣言していない場合、仮想的なコンストラクタC()から上記のように導出された追加の架空の関数テンプレートが追加されます。
-
いずれの場合でも、仮想的なコンストラクタ
C(C)から上記のように導出された追加の架空の関数テンプレートが追加され、これはコピー推論候補と呼ばれます。
-
各
ユーザー定義推論ガイド
G iに対して、以下の全ての条件を満たす架空の関数または関数テンプレートF iが構築されます:
-
-
F iのパラメータリストは、G iのパラメータリストである。 -
F iの戻り値型は、G iの単純なテンプレート識別子である。 -
G iがテンプレートパラメータを持つ場合(構文 (2) )、F iは関数テンプレートであり、そのテンプレートパラメータリストはG iのテンプレートパラメータリストである。それ以外の場合、F iは関数である。
-
template<class T> struct A { T t; struct { long a, b; } u; }; A a{1, 2, 3}; // aggregate deduction candidate: // template<class T> // A<T> F(T, long, long); template<class... Args> struct B : std::tuple<Args...>, Args... {}; B b{std::tuple<std::any, std::string>{}, std::any{}}; // aggregate deduction candidate: // template<class... Args> // B<Args...> F(std::tuple<Args...>, Args...); // type of b is deduced as B<std::any, std::string> |
(C++20以降) |
テンプレート引数推論
と
オーバーロード解決
が、仮想的なクラス型の架空のオブジェクトを初期化するために実行されます。この仮想クラスのコンストラクタシグネチャはガイド(戻り値の型を除く)と一致し、オーバーロード集合を形成する目的で使用されます。初期化子はクラステンプレート引数推論が実行されたコンテキストによって提供されますが、
リスト初期化
の第一段階(初期化子リストコンストラクタの考慮)は、初期化子リストが単一の式から構成され、その式の型が(CV修飾可能性のある)
U
である場合に省略されます。ここで
U
は
C
の特殊化、または
C
の特殊化から派生したクラスです。
これらの仮想的なコンストラクタは仮想クラス型の公開メンバーです。ガイドが明示的コンストラクタから形成された場合、これらは明示的となります。オーバーロード解決が失敗した場合、プログラムは不適格となります。それ以外の場合、選択された
F
テンプレート特殊化の戻り値型が、推定されるクラステンプレート特殊化となります。
template<class T> struct UniquePtr { UniquePtr(T* t); }; UniquePtr dp{new auto(2.0)}; // 宣言されているコンストラクタは1つ: // C1: UniquePtr(T*); // 暗黙的に生成される推論ガイドのセット: // F1: template<class T> // UniquePtr<T> F(T* p); // F2: template<class T> // UniquePtr<T> F(UniquePtr<T>); // コピー推論候補 // 初期化のための仮想クラス: // struct X // { // template<class T> // X(T* p); // F1から // // template<class T> // X(UniquePtr<T>); // F2から // }; // "new double(2.0)"を初期化子として // Xオブジェクトの直接初期化を行う // F1ガイドに対応するコンストラクタが選択され、T=doubleとなる // T=doubleのF1の場合、戻り値の型はUniquePtr<double> // 結果: // UniquePtr<double> dp{new auto(2.0)}
または、より複雑な例として(注記: "
S::N
" はコンパイルされません:スコープ解決修飾子は推論可能な対象ではありません):
template<class T> struct S { template<class U> struct N { N(T); N(T, U); template<class V> N(V, U); }; }; S<int>::N x{2.0, 1}; // 暗黙的に生成される推論ガイドは以下の通り(Tは既にintであることが分かっている) // F1: template<class U> // S<int>::N<U> F(int); // F2: template<class U> // S<int>::N<U> F(int, U); // F3: template<class U, class V> // S<int>::N<U> F(V, U); // F4: template<class U> // S<int>::N<U> F(S<int>::N<U>); (コピー推論候補) // 初期化子 "{2.0, 1}" を用いた直接リスト初期化に対するオーバーロード解決では // U=int, V=double のF3が選択される // 戻り値の型は S<int>::N<int> // 結果: // S<int>::N<int> x{2.0, 1};
ユーザー定義推論ガイド
ユーザー定義の導出ガイドの構文は、関数(テンプレート)宣言の構文と末尾戻り値型を持つが、関数名としてクラステンプレートの名前を使用する点が異なります:
explicit
(任意)
template-name
(
parameter-list
)
->
simple-template-id
requires-clause
(任意)
;
|
(1) | ||||||||
template <
template-parameter-list
>
requires-clause
(任意)
explicit (任意) template-name
(
parameter-list
)
->
simple-template-id
requires-clause
(任意)
;
|
(2) | ||||||||
| template-parameter-list | - | 空でないカンマ区切りの テンプレートパラメータ のリスト |
| explicit | - |
explicit
指定子
|
| template-name | - | 引数が推論されるクラステンプレートの名前 |
| parameter-list | - | (空の場合もある) パラメータリスト |
| simple-template-id | - | 単純テンプレート識別子 |
| requires-clause | - | (C++20以降) requires 節 |
|
ユーザー定義推論ガイドのパラメータはプレースホルダ型を持つことはできません: 省略関数テンプレート 構文は許可されていません。 |
(C++20以降) |
ユーザー定義のデダクションガイドは、クラステンプレートの名前を指定する必要があり、クラステンプレートと同じセマンティックスコープ(名前空間または外側のクラス)内で導入されなければならず、メンバークラステンプレートの場合、同じアクセス権を持たなければなりません。ただし、デダクションガイドはそのスコープのメンバーにはなりません。
デデュクションガイドは関数ではなく、本体を持ちません。デデュクションガイドは名前探索では見つからず、 他のデデュクションガイドとのオーバーロード解決 を除いてオーバーロード解決には参加しません。クラステンプレートの引数推論時を除きます。デデュクションガイドは同じ翻訳単位で同じクラステンプレートに対して再宣言することはできません。
// テンプレートの宣言 template<class T> struct container { container(T t) {} template<class Iter> container(Iter beg, Iter end); }; // 追加の推論ガイド template<class Iter> container(Iter b, Iter e) -> container<typename std::iterator_traits<Iter>::value_type>; // 使用例 container c(7); // OK: 暗黙的に生成されたガイドを使用してT=intと推論 std::vector<double> v = {/* ... */}; auto d = container(v.begin(), v.end()); // OK: T=doubleと推論 container e{5, 6}; // エラー: std::iterator_traits<int>::value_typeは存在しない
オーバーロード解決の目的で作成される仮想的なコンストラクタ(前述)は、暗黙的に生成される推論ガイドが明示的コンストラクタに対応する場合、またはユーザー定義の推論ガイドが explicit として宣言されている場合に、明示的コンストラクタとなります。常に通り、そのようなコンストラクタはコピー初期化の文脈では無視されます:
template<class T> struct A { explicit A(const T&, ...) noexcept; // #1 A(T&&, ...); // #2 }; int i; A a1 = {i, i}; // エラー: #2の右辺値参照から推論できず、 // #1はexplicitであり、コピー初期化では考慮されない A a2{i, i}; // OK、#1がA<int>に推論され、初期化も行う A a3{0, i}; // OK、#2がA<int>に推論され、初期化も行う A a4 = {0, i}; // OK、#2がA<int>に推論され、初期化も行う template<class T> A(const T&, const T&) -> A<T&>; // #3 template<class T> explicit A(T&&, T&&) -> A<T>; // #4 A a5 = {0, 1}; // エラー: #3がA<int&>に推論され、 // #1と#2が同じパラメータのコンストラクタになる A a6{0, 1}; // OK、#4がA<int>に推論され、#2が初期化する A a7 = {0, i}; // エラー: #3がA<int&>に推論される A a8{0, i}; // エラー: #3がA<int&>に推論される // 注記: https://github.com/cplusplus/CWG/issues/647 を参照のこと。a7とa8の例が // 誤りであると主張し、以下のように置き換えられる可能性がある //A a7 = {0, i}; // エラー: #2と#3の両方が一致し、オーバーロード解決に失敗 //A a8{i,i}; // エラー: #3がA<int&>に推論され、 // // #1と#2が同じコンストラクタを宣言する
コンストラクタまたはコンストラクタテンプレートのパラメータリストでメンバtypedefまたはエイリアステンプレートを使用しても、それ自体では、暗黙的に生成されるガイドの対応するパラメータを非推定コンテキストにすることはありません。
template<class T> struct B { template<class U> using TA = T; template<class U> B(U, TA<U>); // #1 }; // #1から生成される暗黙の推論ガイドは以下と等価 // template<class T, class U> // B(U, T) -> B<T>; // 以下ではない // template<class T, class U> // B(U, typename B<T>::template TA<U>) -> B<T>; // こちらは推論不可能となる B b{(int*)0, (char*)0}; // OK、B<char*>を推論
エイリアステンプレートの推論
関数形式キャストまたは変数宣言において、引数リストなしのエイリアステンプレート名
template<class T> class unique_ptr { /* ... */ }; template<class T> class unique_ptr<T[]> { /* ... */ }; template<class T> unique_ptr(T*) -> unique_ptr<T>; // #1 template<class T> unique_ptr(T*) -> unique_ptr<T[]>; // #2 template<class T> concept NonArray = !std::is_array_v<T>; template<NonArray A> using unique_ptr_nonarray = unique_ptr<A>; template<class A> using unique_ptr_array = unique_ptr<A[]>; // unique_ptr_nonarray に対して生成されるガイド: // #1 から (unique_ptr<T> を unique_ptr<A> から推論すると T = A): // template<class A> // requires(argument_of_unique_ptr_nonarray_is_deducible_from<unique_ptr<A>>) // auto F(A*) -> unique_ptr<A>; // #2 から (unique_ptr<T[]> を unique_ptr<A> から推論すると何も得られない): // template<class T> // requires(argument_of_unique_ptr_nonarray_is_deducible_from<unique_ptr<T[]>>) // auto F(T*) -> unique_ptr<T[]>; // ここで argument_of_unique_ptr_nonarray_is_deducible_from は以下のように定義可能: // template<class> // class AA; // template<NonArray A> // class AA<unique_ptr_nonarray<A>> {}; // template<class T> // concept argument_of_unique_ptr_nonarray_is_deducible_from = // requires { sizeof(AA<T>); }; // unique_ptr_array に対して生成されるガイド: // #1 から (unique_ptr<T> を unique_ptr<A[]> から推論すると T = A[]): // template<class A> // requires(argument_of_unique_ptr_array_is_deducible_from<unique_ptr<A[]>>) // auto F(A(*)[]) -> unique_ptr<A[]>; // #2 から (unique_ptr<T[]> を unique_ptr<A[]> から推論すると T = A): // template<class A> // requires(argument_of_unique_ptr_array_is_deducible_from<unique_ptr<A[]>>) // auto F(A*) -> unique_ptr<A[]>; // ここで argument_of_unique_ptr_array_is_deducible_from は以下のように定義可能: // template<class> // class BB; // template<class A> // class BB<unique_ptr_array<A>> {}; // template<class T> // concept argument_of_unique_ptr_array_is_deducible_from = // requires { sizeof(BB<T>); }; // 使用例: unique_ptr_nonarray p(new int); // unique_ptr<int> に推論 // #1 から生成された推論ガイドは unique_ptr<int> を返す // #2 から生成された推論ガイドは unique_ptr<int[]> を返すが、 // argument_of_unique_ptr_nonarray_is_deducible_from<unique_ptr<int[]>> が満たされないため無視される unique_ptr_array q(new int[42]); // unique_ptr<int[]> に推論 // #1 から生成された推論ガイドは失敗 (new int[42] から A(*)[] の A を推論できない) // #2 から生成された推論ガイドは unique_ptr<int[]> を返す |
(C++20 以降) |
注記
クラステンプレートの引数推論は、テンプレート引数リストが存在しない場合にのみ行われます。テンプレート引数リストが指定されている場合、推論は行われません。
std::tuple t1(1, 2, 3); // OK: 型推論 std::tuple<int, int, int> t2(1, 2, 3); // OK: 全ての引数が指定されている std::tuple<> t3(1, 2, 3); // エラー: tuple<>に一致するコンストラクタが存在しない // 型推論は実行されない std::tuple<int> t4(1, 2, 3); // エラー
|
集約型のクラステンプレート引数推論は通常、ユーザー定義の推論ガイドを必要とします: template<class A, class B> struct Agg { A a; B b; }; // implicitly-generated guides are formed from default, copy, and move constructors template<class A, class B> Agg(A a, B b) -> Agg<A, B>; // ^ This deduction guide can be implicitly generated in C++20 Agg agg{1, 2.0}; // deduced to Agg<int, double> from the user-defined guide template<class... T> array(T&&... t) -> array<std::common_type_t<T...>, sizeof...(T)>; auto a = array{1, 2, 5u}; // deduced to array<unsigned, 3> from the user-defined guide |
(C++20まで) |
ユーザー定義の推論ガイドはテンプレートである必要はありません:
template<class T> struct S { S(T); }; S(char const*) -> S<std::string>; S s{"hello"}; // S<std::string> に推論される
クラステンプレートのスコープ内では、パラメータリストなしのテンプレート名は注入されたクラス名であり、型として使用できます。この場合、クラス引数推論は行われず、テンプレートパラメータは明示的に指定する必要があります:
template<class T> struct X { X(T) {} template<class Iter> X(Iter b, Iter e) {} template<class Iter> auto foo(Iter b, Iter e) { return X(b, e); // 推論なし: Xは現在のX<T>を指す } template<class Iter> auto bar(Iter b, Iter e) { return X<typename Iter::value_type>(b, e); // 明示的に指定する必要がある } auto baz() { return ::X(0); // 注入されたクラス名ではない; X<int>と推論される } };
オーバーロード解決 において、部分順序付けは関数テンプレートがユーザー定義推論ガイドから生成されたかどうかよりも優先されます:コンストラクタから生成された関数テンプレートがユーザー定義推論ガイドから生成されたものよりも特殊化されている場合、コンストラクタから生成された方が選択されます。コピー推論候補は通常、ラッピングコンストラクタよりも特殊化されているため、この規則はコピーが一般にラッピングよりも優先されることを意味します。
template<class T> struct A { A(T, int*); // #1 A(A<T>&, int*); // #2 enum { value }; }; template<class T, int N = T::value> A(T&&, int*) -> A<T>; //#3 A a{1, 0}; // #1を使用してA<int>を推論し、#1で初期化 A b{a, 0}; // #2を使用(#3より特殊化されている)してA<int>を推論し、#2で初期化
部分順序付けを含む以前のタイブレーカーが2つの候補関数テンプレートを区別できなかった場合、以下の規則が適用されます:
- ユーザー定義の推論ガイドから生成された関数テンプレートは、コンストラクタまたはコンストラクタテンプレートから暗黙的に生成されたものよりも優先されます。
- コピー推論候補は、コンストラクタまたはコンストラクタテンプレートから暗黙的に生成された他のすべての関数テンプレートよりも優先されます。
- 非テンプレートコンストラクタから暗黙的に生成された関数テンプレートは、コンストラクタテンプレートから暗黙的に生成された関数テンプレートよりも優先されます。
template<class T> struct A { using value_type = T; A(value_type); // #1 A(const A&); // #2 A(T, T, int); // #3 template<class U> A(int, T, U); // #4 }; // #5, コピー推論候補 A(A); A x(1, 2, 3); // #3を使用、非テンプレートコンストラクタから生成 template<class T> A(T) -> A<T>; // #6, #5よりも特殊化度が低い A a(42); // #6を使用してA<int>を推論し、#1で初期化 A b = a; // #5を使用してA<int>を推論し、#2で初期化 template<class T> A(A<T>) -> A<A<T>>; // #7, #5と同じ特殊化度 A b2 = a; // #7を使用してA<A<int>>を推論し、#1で初期化
cv修飾されていないテンプレートパラメータへの右辺値参照は、 そのパラメータがクラステンプレートパラメータである場合、 転送参照 ではありません:
template<class T> struct A { template<class U> A(T&&, U&&, int*); // #1: T&& は forwarding reference ではない // U&& は forwarding reference である A(T&&, int*); // #2: T&& は forwarding reference ではない }; template<class T> A(T&&, int*) -> A<T>; // #3: T&& は forwarding reference である int i, *ip; A a{i, 0, ip}; // エラー: #1 から推論できない A a0{0, 0, ip}; // A<int> の推論に #1 を使用し、初期化に #1 を使用 A a2{i, ip}; // A<int&> の推論に #3 を使用し、初期化に #2 を使用
単一の引数から初期化する際、その型が問題のクラステンプレートの特殊化である場合、デフォルトではラッピングよりもコピー推論が優先されます:
std::tuple t1{1}; //std::tuple<int> std::tuple t2{t1}; //std::tuple<int>、std::tuple<std::tuple<int>>ではない std::vector v1{1, 2}; // std::vector<int> std::vector v2{v1}; // std::vector<int>、std::vector<std::vector<int>>ではない (P0702R1) std::vector v3{v1, v2}; // std::vector<std::vector<int>>
コピーとラッピングの特別なケースを除いて、リスト初期化における初期化子リストコンストラクタの強い優先順位は維持されます。
std::vector v1{1, 2}; // std::vector<int> std::vector v2(v1.begin(), v1.end()); // std::vector<int> std::vector v3{v1.begin(), v1.end()}; // std::vector<std::vector<int>::iterator>
クラステンプレート引数推論が導入される前、引数を明示的に指定することを避ける一般的なアプローチは、関数テンプレートを使用することでした:
std::tuple p1{1, 1.0}; //std::tuple<int, double>、推論を使用 auto p2 = std::make_tuple(1, 1.0); //std::tuple<int, double>、C++17以前
| 機能テストマクロ | 値 | 規格 | 機能 |
|---|---|---|---|
__cpp_deduction_guides
|
201703L
|
(C++17) | クラステンプレートのテンプレート引数推論 |
201907L
|
(C++20) | 集成体とエイリアスに対するCTAD |
不具合報告
以下の動作変更の欠陥報告書は、以前に公開されたC++規格に対して遡及的に適用されました。
| DR | 適用対象 | 公開時の動作 | 正しい動作 |
|---|---|---|---|
| CWG 2376 | C++17 |
変数宣言の型が引数が推論されるクラステンプレートと異なる場合でも
CTADが実行される |
この場合CTADを
実行しない |
| CWG 2628 | C++20 | 暗黙の推論ガイドが制約を伝播しなかった | 制約を伝播する |
| CWG 2697 | C++20 |
ユーザー定義推論ガイドで省略関数テンプレート構文が
許可されるか不明確だった |
禁止 |
| CWG 2707 | C++20 | 推論ガイドがトレーリング requires 節を持つことができなかった | 持つことができる |
| CWG 2714 | C++17 |
暗黙の推論ガイドがコンストラクタの
デフォルト引数を考慮しなかった |
それらを考慮する |
| CWG 2913 | C++20 |
CWG issue 2707
の解決により推論ガイドの構文が
関数宣言の構文と一貫性がなくなった |
構文を調整した |
| P0702R1 | C++17 |
初期化子リストコンストラクタがコピー推論候補を
優先し、ラッピングが発生する可能性があった |
コピー時に初期化子リストフェーズを
スキップする |