模板形参和模板实参
内容 |
[编辑] 模板形参
每个 模板 都由一个或多个模板形参参数化,这些形参在模板声明语法的 形参列表 中指示
template < 形参列表 > 声明 |
(1) | ||||||||
template < 形参列表 > requires 约束 声明 |
(2) | (自 C++20 起) | |||||||
形参列表 中的每个形参可以是
- 非类型模板形参;
- 类型模板形参;
- 模板模板形参。
![]() |
P2841(概念和变量模板模板形参)将“非类型模板形参/实参”重命名为“常量模板形参/实参”,但其含义不变。由于“非类型模板形参”及其缩写“NTTP”的广泛使用,当前的术语被保留。 |
[编辑] 非类型模板形参
类型 名称 (可选) | (1) | ||||||||
类型 名称 (可选) = 默认值 |
(2) | ||||||||
类型 ... 名称 (可选) |
(3) | (自 C++11 起) | |||||||
类型 | - | 以下类型之一
| ||||
名称 | - | 非类型模板形参的名称 | ||||
默认值 | - | 默认模板实参 |
结构类型是以下类型之一(可选地带有 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 起) |
[编辑] 类型模板形参
类型形参键 名称 (可选) | (1) | ||||||||
类型形参键 名称 (可选) = 默认值 |
(2) | ||||||||
类型形参键 ... 名称 (可选) |
(3) | (自 C++11 起) | |||||||
类型约束 名称 (可选) | (4) | (自 C++20 起) | |||||||
类型约束 名称 (可选) = 默认值 |
(5) | (自 C++20 起) | |||||||
类型约束 ... 名称 (可选) |
(6) | (自 C++20 起) | |||||||
类型形参键 | - | typename 或 class 之一。在类型模板形参声明中,这些关键字之间没有区别 |
类型约束 | - | 概念的名称,或概念的名称后跟模板实参列表(在尖括号中)。无论哪种方式,概念名称都可以选择性地进行限定 |
名称 | - | 类型模板形参的名称 |
默认值 | - | 默认模板实参 |
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 名称,它别名化在模板实例化时提供的类型。
每个受约束形参
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 < 形参列表 > 类型形参键 名称 (可选) |
(1) | ||||||||
template < 形参列表 > 类型形参键 名称 (可选) = 默认值 |
(2) | ||||||||
template < 形参列表 > 类型形参键 ... 名称 (可选) |
(3) | (自 C++11 起) | |||||||
类型形参键 | - | 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 起),要么是默认的。对于函数模板,实参要么显式提供,要么从语境推导,要么是默认的。
如果实参可以解释为类型标识和表达式,则它始终被解释为类型标识,即使相应的模板形参是非类型也是如此
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
[编辑] 类型模板实参
类型模板形参的模板实参必须是类型标识,它可以命名不完整类型
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 }
[编辑] 模板模板实参
模板模板形参的模板实参必须是标识表达式,它命名一个类模板或模板别名。
当实参是类模板时,在匹配形参时仅考虑主模板。偏特化(如果有)仅在基于此模板模板形参的特化恰好被实例化时才被考虑。
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 表达式PP
。
如果重写产生无效类型,则 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 起) |
特性测试宏 | 值 | Std | 特性 |
---|---|---|---|
__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++ 标准。
DR | 应用于 | 已发布行为 | 正确行为 |
---|---|---|---|
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. 已明确 |