模板形參和模板實參
目錄 |
[編輯] 模板形參
每個模板都由一個或多個模板形參引數化,這些形參在模板宣告語法的形參列表中指出
template < parameter-list > declaration |
(1) | ||||||||
template < parameter-list > requires constraint declaration |
(2) | (C++20 起) | |||||||
形參列表中的每個形參可以是
- 非型別模板形參;
- 型別模板形參;
- 模板模板形參。
![]() |
P2841 (Concept and variable-template template-parameters) 將“非型別模板形參/實參”重新命名為“常量模板形參/實參”,但未改變其含義。由於“非型別模板形參”及其縮寫“NTTP”的廣泛使用,當前術語得以保留。 |
[編輯] 非型別模板形參
type name (可選) | (1) | ||||||||
type name (可選) = default |
(2) | ||||||||
type ... name (可選) |
(3) | (C++11 起) | |||||||
型別 | - | 以下型別之一
| ||||
name | - | 非型別模板形參的名稱 | ||||
default | - | 預設模板實參 |
“結構化型別”是以下型別之一(可帶有 cv 限定符,限定符會被忽略)
(C++11 起) |
|
(C++20 起) |
陣列和函式型別可以在模板宣告中書寫,但它們會自動被替換為指向物件和指向函式的指標。
當非型別模板形參的名稱在類模板主體內的表示式中使用時,它是一個不可修改的純右值,除非其型別是左值引用型別,或者除非其型別是類型別(C++20 起)。
形式為 class Foo 的模板形參不是型別為 Foo
的無名非型別模板形參,即使 class Foo 否則是詳盡型別說明符,並且 class Foo x; 宣告 x 的型別為 Foo
。
命名類型別 struct A { friend bool operator==(const A&, const A&) = default; }; template<A a> void f() { &a; // OK const A& ra = a, &rb = a; // Both bound to the same template parameter object assert(&ra == &rb); // passes } |
(C++20 起) |
[編輯] 型別模板形參
type-parameter-key name (可選) | (1) | ||||||||
type-parameter-key name (可選) = default |
(2) | ||||||||
type-parameter-key ... name (可選) |
(3) | (C++11 起) | |||||||
type-constraint name (可選) | (4) | (C++20 起) | |||||||
type-constraint name (可選) = default |
(5) | (C++20 起) | |||||||
type-constraint ... name (可選) |
(6) | (C++20 起) | |||||||
type-parameter-key | - | typename 或 class 。在型別模板形參宣告中,這兩個關鍵詞沒有區別 |
type-constraint | - | 概念的名稱,或概念名稱後跟模板實參列表(在尖括號中)。無論哪種情況,概念名稱都可以是可選限定的 |
name | - | 型別模板形參的名稱 |
default | - | 預設模板實參 |
template<class T> class My_vector { /* ... */ };
template<class T = void> struct My_op_functor { /* ... */ };
template<My_concept T> class My_constrained_vector { /* ... */ };
template<My_concept T = void> class My_constrained_op_functor { /* ... */ };
形參的名稱是可選的
// Declarations of the templates shown above: template<class> class My_vector; template<class = void> struct My_op_functor; template<typename...> class My_tuple;
在模板宣告的主體中,型別形參的名稱是一個 typedef-name,它別名為模板例項化時提供的型別。
每個受約束形參
template<typename T> concept C1 = true; template<typename... Ts> // variadic concept concept C2 = true; template<typename T, typename U> concept C3 = true; template<C1 T> struct s1; // constraint-expression is C1<T> template<C1... T> struct s2; // constraint-expression is (C1<T> && ...) template<C2... T> struct s3; // constraint-expression is (C2<T> && ...) template<C3<int> T> struct s4; // constraint-expression is C3<T, int> template<C3<int>... T> struct s5; // constraint-expression is (C3<T, int> && ...) |
(C++20 起) |
[編輯] 模板模板形參
template < parameter-list > type-parameter-key name (可選) |
(1) | ||||||||
template < parameter-list > type-parameter-key name (可選) = default |
(2) | ||||||||
template < parameter-list > type-parameter-key ... name (可選) |
(3) | (C++11 起) | |||||||
type-parameter-key | - | class 或 typename (C++17 起) |
在模板宣告的主體中,此形參的名稱是一個模板名(需要實參才能例項化)。
template<typename T> class my_array {}; // two type template parameters and one template template parameter: template<typename K, typename V, template<typename> typename C = my_array> class Map { C<K> key; C<V> value; };
[編輯] 模板形參的名字解析
模板形參的名稱不允許在其作用域(包括巢狀作用域)內重宣告。模板形參不允許與模板名稱同名。
template<class T, int N> class Y { int T; // error: template parameter redeclared void f() { char T; // error: template parameter redeclared } }; template<class X> class X; // error: template parameter redeclared
在類模板的成員定義(出現在類模板定義之外)中,類模板成員的名稱隱藏任何包圍類模板的模板形參的名稱,但如果成員是類模板或函式模板,則不隱藏其模板形參的名稱。
template<class T> struct A { struct B {}; typedef void C; void f(); template<class U> void g(U); }; template<class B> void A<B>::f() { B b; // A's B, not the template parameter } template<class B> template<class C> void A<B>::g(C) { B b; // A's B, not the template parameter C c; // the template parameter C, not A's C }
在類模板的成員定義(出現在包含類模板定義的名稱空間之外)中,模板形參的名稱隱藏此名稱空間的成員名稱。
namespace N { class C {}; template<class T> class B { void f(T); }; } template<class C> void N::B<C>::f(C) { C b; // C is the template parameter, not N::C }
在類模板的定義中,或在此類模板成員的定義中(出現在模板定義之外),對於每個非依賴基類,如果基類的名稱或基類成員的名稱與模板形參的名稱相同,則基類名稱或成員名稱隱藏模板形參名稱。
struct A { struct B {}; int C; int Y; }; template<class B, class C> struct X : A { B b; // A's B C b; // error: A's C isn't a type name };
[編輯] 模板實參
為了例項化模板,每個模板形參(型別、非型別或模板)都必須被相應的模板實參替換。對於類模板,實參可以是顯式提供的、從初始化器推導的(C++17 起),或預設的。對於函式模板,實參可以是顯式提供的、從上下文推導的,或預設的。
如果一個實參可以同時被解釋為type-id和表示式,它總是被解釋為 type-id,即使相應的模板形參是非型別的
template<class T> void f(); // #1 template<int I> void f(); // #2 void g() { f<int()>(); // "int()" is both a type and an expression, // calls #1 because it is interpreted as a type }
[編輯] 非型別模板實參
可與非型別模板形參一起使用的模板實參可以是任何顯然常量求值表示式。 |
(C++11 前) |
(C++11 起) |
給定非型別模板形參宣告的型別為 T
,為該形參提供的模板實參為 E。
虛構宣告 T x = E; 必須滿足constexpr 變數定義的語義約束,其儲存期為靜態。 |
(C++26 起) |
如果 如果推匯出的形參型別不是結構化型別,則程式是病態的。 對於型別使用佔位符型別的非型別模板形參包,型別是為每個模板實參獨立推導的,並且不必匹配。 |
(C++17 起) |
template<auto n> struct B { /* ... */ }; B<5> b1; // OK: non-type template parameter type is int B<'a'> b2; // OK: non-type template parameter type is char B<2.5> b3; // error (until C++20): non-type template parameter type cannot be double // C++20 deduced class type placeholder, class template arguments are deduced at the // call site template<std::array arr> void f(); f<std::array<double, 8>{}>(); template<auto...> struct C {}; C<'C', 0, 2L, nullptr> x; // OK
非型別模板形參 P 的值,其(可能推匯出的)(C++17 起)型別為 T
,從其模板實參 A 按如下方式確定
|
(C++11 前) |
|
(C++11 起) (C++20 前) |
|
(C++20 起) |
template<int i> struct C { /* ... */ }; C<{42}> c1; // OK template<auto n> struct B { /* ... */ }; struct J1 { J1* self = this; }; B<J1{}> j1; // error: initialization of the template parameter object // is not a constant expression struct J2 { J2 *self = this; constexpr J2() {} constexpr J2(const J2&) {} }; B<J2{}> j2; // error: the template parameter object is not // template-argument-equivalent to introduced temporary
例項化具有非型別模板形參的模板時適用以下限制
特別是,這意味著字串字面量、陣列元素的地址和非靜態成員的地址不能用作模板實參來例項化相應的非型別模板形參為指向物件的指標的模板。 |
(C++17 前) |
引用或指標型別的非型別模板形參,以及類型別非型別模板形參及其子物件中引用或指標型別的非靜態資料成員(C++20 起)不能引用/是以下物件的地址: |
(C++17 起) |
template<const int* pci> struct X {}; int ai[10]; X<ai> xi; // OK: array to pointer conversion and cv-qualification conversion struct Y {}; template<const Y& b> struct Z {}; Y y; Z<y> z; // OK: no conversion template<int (&pa)[5]> struct W {}; int b[5]; W<b> w; // OK: no conversion void f(char); void f(int); template<void (*pf)(int)> struct A {}; A<&f> a; // OK: overload resolution selects f(int)
template<class T, const char* p> class X {}; X<int, "Studebaker"> x1; // error: string literal as template-argument template<int* p> class X {}; int a[10]; struct S { int m; static int s; } s; X<&a[2]> x3; // error (until C++20): address of array element X<&s.m> x4; // error (until C++20): address of non-static member X<&s.s> x5; // OK: address of static member X<&S::s> x6; // OK: address of static member template<const int& CRI> struct B {}; B<1> b2; // error: temporary would be required for template argument int c = 1; B<c> b1; // OK
[編輯] 型別模板實參
型別模板形參的模板實參必須是型別 ID,它可以命名不完整型別
template<typename T> class X {}; // class template struct A; // incomplete type typedef struct {} B; // type alias to an unnamed type int main() { X<A> x1; // OK: 'A' names a type X<A*> x2; // OK: 'A*' names a type X<B> x3; // OK: 'B' names a type }
[編輯] 模板模板實參
模板模板形參的模板實參必須是命名類模板或模板別名的id-expression。
當實參是類模板時,只有主模板在匹配形參時被考慮。如果有部分特化,則只有在基於此模板模板形參的特化被例項化時才被考慮。
template<typename T> // primary template class A { int x; }; template<typename T> // partial specialization class A<T*> { long x; }; // class template with a template template parameter V template<template<typename> class V> class C { V<int> y; // uses the primary template V<int*> z; // uses the partial specialization }; C<A> c; // c.y.x has type int, c.z.x has type long
為了將模板模板實參 A
匹配到模板模板形參 P
,P
必須至少與 A
一樣特化(見下文)。如果 P
的形參列表包含形參包,則 A
的模板形參列表中的零個或多個模板形參(或形參包)將由它匹配。(C++11 起)
形式上,如果給定以下對兩個函式模板的重寫,則模板模板形參 P
至少與模板模板實參 A
一樣特化,即根據函式模板的部分排序規則,對應於 P
的函式模板至少與對應於 A
的函式模板一樣特化。給定一個虛構的類模板 X
,其模板形參列表與 A
相同(包括預設實參)
- 兩個函式模板分別具有與
P
或A
相同的模板形參。 - 每個函式模板都有一個單一的函式形參,其型別是
X
的特化,模板實參對應於相應函式模板的模板形參,其中,對於函式模板的模板形參列表中的每個模板形參PP
,形成相應的模板實參AA
。如果PP
宣告一個形參包,則AA
是包展開PP...
;否則,(C++11 起)AA
是 id-expressionPP
。
如果重寫產生無效型別,則 P
不至少與 A
一樣特化。
template<typename T> struct eval; // primary template template<template<typename, typename...> class TT, typename T1, typename... Rest> struct eval<TT<T1, Rest...>> {}; // partial specialization of eval template<typename T1> struct A; template<typename T1, typename T2> struct B; template<int N> struct C; template<typename T1, int N> struct D; template<typename T1, typename T2, int N = 17> struct E; eval<A<int>> eA; // OK: matches partial specialization of eval eval<B<int, float>> eB; // OK: matches partial specialization of eval eval<C<17>> eC; // error: C does not match TT in partial specialization // because TT's first parameter is a // type template parameter, while 17 does not name a type eval<D<int, 17>> eD; // error: D does not match TT in partial specialization // because TT's second parameter is a // type parameter pack, while 17 does not name a type eval<E<int, float>> eE; // error: E does not match TT in partial specialization // because E's third (default) parameter is a non-type
在採用P0522R0之前,A
的每個模板形參都必須與 P
的相應模板形參完全匹配。這阻礙了許多合理的模板實參被接受。
儘管它很早就被指出(CWG#150),但在它被解決時,這些更改已應用於 C++17 工作草案,並且該解決方案成為事實上的 C++17 功能。許多編譯器預設停用它
- GCC 預設在 C++17 之前的所有語言模式中停用它,只能透過在這些模式中設定編譯器標誌來啟用它。
- Clang 預設在所有語言模式中停用它,只能透過設定編譯器標誌來啟用它。
- Microsoft Visual Studio 將其視為正常的 C++17 功能,並且僅在 C++17 及更高版本的語言模式中啟用它(即在 C++14 語言模式中不支援,C++14 是預設模式)。
template<class T> class A { /* ... */ }; template<class T, class U = T> class B { /* ... */ }; template<class... Types> class C { /* ... */ }; template<template<class> class P> class X { /* ... */ }; X<A> xa; // OK X<B> xb; // OK after P0522R0 // Error earlier: not an exact match X<C> xc; // OK after P0522R0 // Error earlier: not an exact match template<template<class...> class Q> class Y { /* ... */ }; Y<A> ya; // OK Y<B> yb; // OK Y<C> yc; // OK template<auto n> class D { /* ... */ }; // note: C++17 template<template<int> class R> class Z { /* ... */ }; Z<D> zd; // OK after P0522R0: the template parameter // is more specialized than the template argument template<int> struct SI { /* ... */ }; template<template<auto> class> void FA(); // note: C++17 FA<SI>(); // Error
[編輯] 預設模板實參
預設模板實參在形參列表中用 = 符號指定。預設值可以為任何型別的模板形參(型別、非型別或模板)指定,但不能為形參包指定(C++11 起)。
如果為主類模板、主變數模板(C++14 起)或別名模板的模板形參指定了預設值,則每個後續模板形參都必須具有預設實參,除了最後一個可以是模板形參包(C++11 起)。在函式模板中,預設值之後的形參沒有限制,並且形參包之後可以有更多型別形參,僅當它們具有預設值或可以從函式實參推導時(C++11 起)。
不允許使用預設形參
- 在類模板成員的類外定義中(它們必須在類體內的宣告中提供)。請注意,非模板類的成員模板可以在其類外定義中使用預設形參(參見GCC bug 53856)
- 在友元類模板宣告中
|
(C++11 前) |
在友元函式模板宣告上,預設模板實參僅當宣告是定義並且此函式在當前翻譯單元中沒有其他宣告時才允許。 |
(C++11 起) |
在宣告中出現的預設模板引數的合併方式與預設函式引數類似。
template<typename T1, typename T2 = int> class A; template<typename T1 = int, typename T2> class A; // the above is the same as the following: template<typename T1 = int, typename T2 = int> class A;
但在同一作用域中,同一引數不能被賦予兩次預設引數。
template<typename T = int> class X; template<typename T = int> class X {}; // error
當解析非型別模板引數的預設模板引數時,第一個非巢狀的 > 被視為模板引數列表的結束,而不是大於運算子。
template<int i = 3 > 4> // syntax error class X { /* ... */ }; template<int i = (3 > 4)> // OK class Y { /* ... */ };
模板模板引數的模板引數列表可以有自己的預設引數,這些預設引數僅在模板模板引數本身的作用域內有效。
// class template, with a type template parameter with a default template<typename T = float> struct B {}; // template template parameter T has a parameter list, which // consists of one type template parameter with a default template<template<typename = float> typename T> struct A { void f(); void g(); }; // out-of-body member function template definitions template<template<typename TT> class T> void A<T>::f() { T<> t; // error: TT has no default in scope } template<template<typename TT = char> class T> void A<T>::g() { T<> t; // OK: t is T<char> }
預設模板引數中使用的名稱的成員訪問是在宣告處檢查的,而不是在使用點檢查的。
class B {}; template<typename T> class C { protected: typedef T TT; }; template<typename U, typename V = typename U::TT> class D: public U {}; D<C<B>>* d; // error: C::TT is protected
當需要預設引數的值時,預設模板引數會隱式例項化,除非該模板用於命名函式。 template<typename T, typename U = int> struct S {}; S<bool>* p; // The default argument for U is instantiated at this point // the type of p is S<bool, int>* |
(C++14 起) |
[編輯] 模板實參等價性
模板實參等價性用於確定兩個模板識別符號是否相同。
如果兩個值型別相同且滿足以下任一條件,則它們是模板實參等價的:
- 它們是整型或列舉型別,且它們的值相同。
- 它們是指標型別,且它們具有相同的指標值。
- 它們是指向成員的指標型別,且它們引用相同的類成員,或都是空成員指標值。
- 它們是左值引用型別,且它們引用相同的物件或函式。
|
(C++11 起) |
|
(C++20 起) |
[編輯] 注意
在模板引數中,型別約束可以用於型別和非型別引數,具體取決於是否存在 auto。 template<typename> concept C = true; template<C, // type parameter C auto // non-type parameter > struct S{}; S<int, 0> s;
|
(C++20 起) |
功能測試宏 | 值 | 標準 | 特性 |
---|---|---|---|
__cpp_nontype_template_parameter_auto |
201606L |
(C++17) | 使用 auto 宣告非型別模板引數 |
__cpp_template_template_args |
201611L |
(c++17) (DR) |
模板模板實參的匹配 |
__cpp_nontype_template_args |
201411L |
(C++17) | 允許對所有非型別模板實參進行常量求值 |
201911L |
(C++20) | 非型別模板引數中的類型別和浮點型別 |
[編輯] 示例
#include <array> #include <iostream> #include <numeric> // simple non-type template parameter template<int N> struct S { int a[N]; }; template<const char*> struct S2 {}; // complicated non-type example template < char c, // integral type int (&ra)[5], // lvalue reference to object (of array type) int (*pf)(int), // pointer to function int (S<10>::*a)[10] // pointer to member object (of type int[10]) > struct Complicated { // calls the function selected at compile time // and stores the result in the array selected at compile time void foo(char base) { ra[4] = pf(c - base); } }; // S2<"fail"> s2; // error: string literal cannot be used char okay[] = "okay"; // static object with linkage // S2<&okay[0]> s3; // error: array element has no linkage S2<okay> s4; // works int a[5]; int f(int n) { return n; } // C++20: NTTP can be a literal class type template<std::array arr> constexpr auto sum() { return std::accumulate(arr.cbegin(), arr.cend(), 0); } // C++20: class template arguments are deduced at the call site static_assert(sum<std::array<double, 8>{3, 1, 4, 1, 5, 9, 2, 6}>() == 31.0); // C++20: NTTP argument deduction and CTAD static_assert(sum<std::array{2, 7, 1, 8, 2, 8}>() == 28); int main() { S<10> s; // s.a is an array of 10 int s.a[9] = 4; Complicated<'2', a, f, &S<10>::a> c; c.foo('0'); std::cout << s.a[9] << a[4] << '\n'; }
輸出
42
本節不完整 原因:更多示例 |
[編輯] 缺陷報告
下列更改行為的缺陷報告追溯地應用於以前出版的 C++ 標準。
缺陷報告 | 應用於 | 釋出時的行為 | 正確的行為 |
---|---|---|---|
CWG 150 (P0522R0) |
C++98 | 模板模板實參必須與模板模板引數列表完全匹配 列表完全匹配 |
更專業 也允許 |
CWG 184 | C++98 | 模板模板引數的模板引數是否允許擁有預設引數是未指定的 引數是否允許擁有預設引數是未指定的 |
添加了規範 |
CWG 354 | C++98 | 空指標值不能作為非型別模板實參 | 允許 |
CWG 1398 | C++11 | 非型別模板實參不能擁有型別 std::nullptr_t |
允許 |
CWG 1570 | C++98 | 非型別模板實參可以指定子物件的地址 | 不允許 |
CWG 1922 | C++98 | 不清楚一個類模板,其名稱是 注入類名,是否可以使用先前宣告中的預設引數 |
允許 |
CWG 2032 | C++14 | 對於變數模板,在帶有預設引數的模板引數之後沒有對模板引數的限制 引數之後沒有對模板的限制 |
應用相同的限制 如同類模板 和別名模板 |
CWG 2542 | C++20 | 不清楚閉包型別是否是結構化的 | 它不是結構化的 |
CWG 2845 | C++20 | 閉包型別不是結構化的 | 它是結構化的 如果無捕獲 |
P2308R1 | C++11 C++20 |
1. 列表初始化不被允許用於 非型別模板實參 (C++11) 2. 不清楚類型別的非型別模板 引數如何初始化 (C++20) |
1. 允許 2. 已明確 |