類模板引數推導 (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;
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 起) |
然後執行模板引數推導和過載決議,以初始化一個假想類型別的虛構物件,其建構函式簽名與指引匹配(除了返回型別),以形成過載集,並且初始化器由執行類模板引數推導的語境提供,但如果初始化器列表包含單個型別(可能cv限定)U
的表示式,其中U
是C
的特化或從C
的特化派生的類,則省略列表初始化的第一階段(考慮初始化器列表建構函式)。
這些虛構的建構函式是假想類型別的公有成員。如果指引是由顯式建構函式形成,或指引被宣告為explicit,則它們是顯式的。如果過載決議失敗,程式將格式錯誤。否則,所選的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(可選) 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 起) |
使用者定義的推導指引必須命名一個類模板,並且必須在與類模板相同的語義作用域(可以是名稱空間或包含類)內引入,並且對於成員類模板,必須具有相同的訪問許可權,但推導指引不會成為該作用域的成員。
推導指引不是函式,也沒有函式體。推導指引不透過名稱查詢找到,除了在推導類模板引數時與其他推導指引進行過載決議外,不參與過載決議。推導指引不能在同一個翻譯單元中為同一個類模板重新宣告。
// 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的使用者定義的推導指引。與往常一樣,在複製初始化語境中會忽略這些建構函式。
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
當較早的決勝規則(包括偏序)未能區分兩個候選函式模板時,適用以下規則:
- 由使用者定義的推導指引生成的函式模板優先於由建構函式或建構函式模板隱式生成的函式模板。
- 複製推導候選優先於所有其他由建構函式或建構函式模板隱式生成的函式模板。
- 由非模板建構函式隱式生成的函式模板優先於由建構函式模板隱式生成的函式模板。
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++ 標準。
缺陷報告 | 應用於 | 釋出時的行為 | 正確的行為 |
---|---|---|---|
CWG 2376 | C++17 | 即使宣告的變數型別不同於其引數將被推導的類模板,也會執行CTAD 當宣告的變數型別與要推導引數的類模板不同時 |
不執行 CTAD |
CWG 2628 | C++20 | 隱式推導指引未傳播約束 | 傳播約束 |
CWG 2697 | C++20 | 不清楚是否允許在使用者定義的推導指引中使用縮寫函式模板語法 使用者定義的推導指引不允許使用縮寫函式模板語法 |
已禁止 |
CWG 2707 | C++20 | 推導指引不能有尾隨的requires子句 | 推導指引可以有尾隨的requires子句 |
CWG 2714 | C++17 | 隱式推導指引未考慮建構函式的預設引數 考慮建構函式的預設引數 |
考慮它們 |
CWG 2913 | C++20 | CWG 問題 2707 的解決方案使得推導指引語法與函式宣告語法不一致 調整了語法 |
調整了語法 |
P0702R1 | C++17 | 初始化器列表建構函式可以搶佔複製推導候選,導致包裝 初始化器列表階段 |
複製時跳過 複製時跳過 |