クラステンプレート引数推論 (CTAD) (C++17以降)
クラステンプレートをインスタンス化するには、すべてのテンプレート引数が既知である必要がありますが、すべてのテンプレート引数を指定する必要はありません。以下のコンテキストでは、コンパイラは初期化子の型からテンプレート引数を推論します。
std::pair p(2, 4.5); // deduces to std::pair<int, double> p(2, 4.5); std::tuple t(4, 3, 2.5); // same as auto t = std::make_tuple(4, 3, 2.5); std::less l; // same as std::less<void> l;
- new式:
template<class T> struct A { A(T, T); }; auto y = new A{1, 2}; // allocated type is A<int>
auto lck = std::lock_guard(mtx); // deduces to std::lock_guard<std::mutex> std::copy_n(vi1, 3, std::back_insert_iterator(vi2)); // deduces to std::back_insert_iterator<T>, // where T is the type of the container vi2 std::for_each(vi.begin(), vi.end(), Foo([&](int i) {...})); // deduces to Foo<T>, // where T is the unique lambda type
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が定義されている場合、指定されたプライマリテンプレートで宣言されている各コンストラクタ(またはコンストラクタテンプレート)Ciについて、以下のすべての条件を満たす仮想関数テンプレートFiが構築されます。
Fiのテンプレートパラメータは、Cのテンプレートパラメータに続いて(Ciがコンストラクタテンプレートの場合)Ciのテンプレートパラメータです(デフォルトテンプレート引数も含まれます)。
|
(C++20以降) |
Fiのパラメータリストは、Ciのパラメータリストです。Fiの戻り値の型は、Cに続いてクラステンプレートのテンプレートパラメータが<>で囲まれたものです。
Cが定義されていないか、コンストラクタを宣言していない場合、仮想コンストラクタC()から上記のように派生した追加の仮想関数テンプレートが追加されます。
- いずれの場合も、仮想コンストラクタ
C(C)から上記のように派生した追加の仮想関数テンプレートが追加され、これはコピー推論候補と呼ばれます。
- 各ユーザー定義推論ガイド
Giについて、以下のすべての条件を満たす仮想関数または関数テンプレートFiが構築されます。
Fiのパラメータリストは、Giのパラメータリストです。Fiの戻り値の型は、Giの単純なテンプレート識別子です。Giがテンプレートパラメータを持つ場合(構文(2))、Fiは関数テンプレートであり、そのテンプレートパラメータリストはGiのテンプレートパラメータリストです。それ以外の場合、Fiは関数です。
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以降) |
テンプレート引数推論とオーバーロード解決は、仮想クラス型の仮想オブジェクトの初期化のために実行されます。そのコンストラクタシグネチャは、オーバーロードセットを形成する目的でガイド(戻り値の型を除く)と一致し、初期化子はクラステンプレート引数推論が実行されたコンテキストによって提供されます。ただし、初期化子リストが型U(おそらくcv修飾された)の単一の式で構成されている場合、リスト初期化の最初のフェーズ(初期化子リストコンストラクタを考慮する)は省略されます。ここでUはCの特殊化、またはCの特殊化から派生したクラスです。
これらの仮想コンストラクタは、仮想クラス型のパブリックメンバです。ガイドが明示的なコンストラクタから形成された場合、それらは明示的です。オーバーロード解決が失敗した場合、プログラムは不正です。それ以外の場合、選択されたFテンプレート特殊化の戻り値の型が、推論されたクラステンプレート特殊化になります。
template<class T> struct UniquePtr { UniquePtr(T* t); }; UniquePtr dp{new auto(2.0)}; // One declared constructor: // C1: UniquePtr(T*); // Set of implicitly-generated deduction guides: // F1: template<class T> // UniquePtr<T> F(T* p); // F2: template<class T> // UniquePtr<T> F(UniquePtr<T>); // copy deduction candidate // imaginary class to initialize: // struct X // { // template<class T> // X(T* p); // from F1 // // template<class T> // X(UniquePtr<T>); // from F2 // }; // direct-initialization of an X object // with "new double(2.0)" as the initializer // selects the constructor that corresponds to the guide F1 with T = double // For F1 with T=double, the return type is UniquePtr<double> // result: // 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}; // the implicitly-generated deduction guides are (note that T is already known to be 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>); (copy deduction candidate) // Overload resolution for direct-list-init with "{2.0, 1}" as the initializer // chooses F3 with U=int and V=double. // The return type is S<int>::N<int> // result: // S<int>::N<int> x{2.0, 1};
[編集] ユーザー定義推論ガイド
ユーザー定義推論ガイドの構文は、関数(テンプレート)宣言の構文に後置戻り値の型が付いたものですが、関数名としてクラステンプレートの名前を使用します。
explicit (optional) template-name ( parameter-list ) -> simple-template-id requires-clause (optional) ; |
(1) | ||||||||
template <template-parameter-list > requires-clause (optional)explicit (optional) template-name ( parameter-list ) -> simple-template-id requires-clause (optional) ; |
(2) | ||||||||
| template-parameter-list | - | テンプレートパラメータの空でないコンマ区切りリスト |
| explicit | - | explicit指定子 |
| template-name | - | 引数が推論されるクラステンプレートの名前 |
| parameter-list | - | (空である可能性のある)パラメータリスト |
| simple-template-id | - | 単純テンプレート識別子 |
| requires-clause | - | (C++20以降) requires句 |
|
ユーザー定義推論ガイドのパラメータはプレースホルダ型を持つことはできません。省略された関数テンプレート構文は許可されません。 |
(C++20以降) |
ユーザー定義推論ガイドはクラステンプレート名を指定しなければならず、クラステンプレートと同じ意味スコープ(名前空間または囲むクラス)内で導入されなければなりません。メンバクラステンプレートの場合、同じアクセス権を持たなければなりませんが、推論ガイドはそのスコープのメンバにはなりません。
推論ガイドは関数ではなく、本体を持ちません。推論ガイドは名前探索によって見つけられず、クラステンプレート引数を推論する際の他の推論ガイドに対するオーバーロード解決を除いて、オーバーロード解決には参加しません。推論ガイドは、同じクラステンプレートに対して同じ翻訳単位内で再宣言することはできません。
// declaration of the template template<class T> struct container { container(T t) {} template<class Iter> container(Iter beg, Iter end); }; // additional deduction guide template<class Iter> container(Iter b, Iter e) -> container<typename std::iterator_traits<Iter>::value_type>; // uses container c(7); // OK: deduces T=int using an implicitly-generated guide std::vector<double> v = {/* ... */}; auto d = container(v.begin(), v.end()); // OK: deduces T=double container e{5, 6}; // Error: there is no std::iterator_traits<int>::value_type
オーバーロード解決の目的のための仮想コンストラクタ(上記で説明)は、明示的なコンストラクタから形成された暗黙的に生成される推論ガイド、またはexplicitと宣言されたユーザー定義推論ガイドに対応する場合に、explicitになります。常にそうですが、そのようなコンストラクタはコピー初期化コンテキストでは無視されます。
template<class T> struct A { explicit A(const T&, ...) noexcept; // #1 A(T&&, ...); // #2 }; int i; A a1 = {i, i}; // error: cannot deduce from rvalue reference in #2, // and #1 is explicit, and not considered in copy-initialization. A a2{i, i}; // OK, #1 deduces to A<int> and also initializes A a3{0, i}; // OK, #2 deduces to A<int> and also initializes A a4 = {0, i}; // OK, #2 deduces to A<int> and also initializes 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}; // error: #3 deduces to A<int&> // and #1 & #2 result in same parameter constructors. A a6{0, 1}; // OK, #4 deduces to A<int> and #2 initializes A a7 = {0, i}; // error: #3 deduces to A<int&> A a8{0, i}; // error: #3 deduces to A<int&> // Note: check https://github.com/cplusplus/CWG/issues/647, claiming that // examples a7 and a8 are incorrect, to be possibly replaced as //A a7 = {0, i}; // error: #2 and #3 both match, overload resolution fails //A a8{i,i}; // error: #3 deduces to A<int&>, // // #1 and #2 declare same constructor
コンストラクタまたはコンストラクタテンプレートのパラメータリストでメンバtypedefまたはエイリアステンプレートを使用しても、それ自体で、暗黙的に生成されたガイドの対応するパラメータが非推論コンテキストになることはありません。
template<class T> struct B { template<class U> using TA = T; template<class U> B(U, TA<U>); // #1 }; // Implicit deduction guide generated from #1 is the equivalent of // template<class T, class U> // B(U, T) -> B<T>; // rather than // template<class T, class U> // B(U, typename B<T>::template TA<U>) -> B<T>; // which would not have been deducible B b{(int*)0, (char*)0}; // OK, deduces 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[]>; // generated guide for unique_ptr_nonarray: // from #1 (deduction of unique_ptr<T> from unique_ptr<A> yields T = A): // template<class A> // requires(argument_of_unique_ptr_nonarray_is_deducible_from<unique_ptr<A>>) // auto F(A*) -> unique_ptr<A>; // from #2 (deduction of unique_ptr<T[]> from unique_ptr<A> yields nothing): // template<class T> // requires(argument_of_unique_ptr_nonarray_is_deducible_from<unique_ptr<T[]>>) // auto F(T*) -> unique_ptr<T[]>; // where argument_of_unique_ptr_nonarray_is_deducible_from can be defined as // 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>); }; // generated guide for unique_ptr_array: // from #1 (deduction of unique_ptr<T> from unique_ptr<A[]> yields T = A[]): // template<class A> // requires(argument_of_unique_ptr_array_is_deducible_from<unique_ptr<A[]>>) // auto F(A(*)[]) -> unique_ptr<A[]>; // from #2 (deduction of unique_ptr<T[]> from unique_ptr<A[]> yields T = A): // template<class A> // requires(argument_of_unique_ptr_array_is_deducible_from<unique_ptr<A[]>>) // auto F(A*) -> unique_ptr<A[]>; // where argument_of_unique_ptr_array_is_deducible_from can be defined as // 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>); }; // Use: unique_ptr_nonarray p(new int); // deduced to unique_ptr<int> // deduction guide generated from #1 returns unique_ptr<int> // deduction guide generated from #2 returns unique_ptr<int[]>, which is ignored because // argument_of_unique_ptr_nonarray_is_deducible_from<unique_ptr<int[]>> is unsatisfied unique_ptr_array q(new int[42]); // deduced to unique_ptr<int[]> // deduction guide generated from #1 fails (cannot deduce A in A(*)[] from new int[42]) // deduction guide generated from #2 returns unique_ptr<int[]> |
(C++20以降) |
[編集] 注釈
クラステンプレート引数推論は、テンプレート引数リストが存在しない場合にのみ実行されます。テンプレート引数リストが指定されている場合、推論は行われません。
std::tuple t1(1, 2, 3); // OK: deduction std::tuple<int, int, int> t2(1, 2, 3); // OK: all arguments are provided std::tuple<> t3(1, 2, 3); // Error: no matching constructor in tuple<>. // No deduction performed. std::tuple<int> t4(1, 2, 3); // Error
|
集約のクラステンプレート引数推論には、通常、ユーザー定義推論ガイドが必要です。 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"}; // deduced to 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); // no deduction: X is the current X<T> } template<class Iter> auto bar(Iter b, Iter e) { return X<typename Iter::value_type>(b, e); // must specify what we want } auto baz() { return ::X(0); // not the injected-class-name; deduced to be 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}; // uses #1 to deduce A<int> and initializes with #1 A b{a, 0}; // uses #2 (more specialized than #3) to deduce A<int> and initializes with #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, the copy deduction candidate A(A); A x(1, 2, 3); // uses #3, generated from a non-template constructor template<class T> A(T) -> A<T>; // #6, less specialized than #5 A a(42); // uses #6 to deduce A<int> and #1 to initialize A b = a; // uses #5 to deduce A<int> and #2 to initialize template<class T> A(A<T>) -> A<A<T>>; // #7, as specialized as #5 A b2 = a; // uses #7 to deduce A<A<int>> and #1 to initialize
cv修飾されていないテンプレートパラメータへの右辺値参照は、そのパラメータがクラステンプレートパラメータである場合、転送参照ではありません。
template<class T> struct A { template<class U> A(T&&, U&&, int*); // #1: T&& is not a forwarding reference // U&& is a forwarding reference A(T&&, int*); // #2: T&& is not a forwarding reference }; template<class T> A(T&&, int*) -> A<T>; // #3: T&& is a forwarding reference int i, *ip; A a{i, 0, ip}; // error, cannot deduce from #1 A a0{0, 0, ip}; // uses #1 to deduce A<int> and #1 to initialize A a2{i, ip}; // uses #3 to deduce A<int&> and #2 to initialize
問題のクラステンプレートの特殊化である型の単一の引数から初期化する場合、デフォルトでは、通常、ラッピングよりもコピー推論が優先されます。
std::tuple t1{1}; //std::tuple<int> std::tuple t2{t1}; //std::tuple<int>, not std::tuple<std::tuple<int>> std::vector v1{1, 2}; // std::vector<int> std::vector v2{v1}; // std::vector<int>, not 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>, using deduction auto p2 = std::make_tuple(1, 1.0); //std::tuple<int, double>, pre-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 | 初期化子リストコンストラクタが コピー推論候補を先取りし、ラッピングを引き起こす可能性がある |
初期化子リストフェーズは コピー時にスキップされる |