模板形参与模板实参
每个模板都会由一个或多个模板形参参数化。
模板形参列表 (参考模板声明语法)中的每个形参具有以下类别之一:
- 常量模板形参
- 类型模板形参
- 模板模板形参
目录 |
[编辑] 常量模板形参
又称为非类型模板形参(见下文)。
类型 名字 (可选) | (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; // 都绑定到同一个模板形参对象 assert(&ra == &rb); // 通过 } |
(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 { /* ... */ };
形参的名字是可选的:
// 对上面所示模板的声明: 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> // 变参概念 concept C2 = true; template<typename T, typename U> concept C3 = true; template<C1 T> struct s1; // 约束表达式是 C1<T> template<C1... T> struct s2; // 约束表达式是 (C1<T> && ...) template<C2... T> struct s3; // 约束表达式是 (C2<T> && ...) template<C3<int> T> struct s4; // 约束表达式是 C3<T, int> template<C3<int>... T> struct s5; // 约束表达式是 (C3<T, int> && ...) |
(C++20 起) |
[编辑] 模板模板形参
template < 形参列表 > 类型形参关键词 名字 (可选)
|
(1) | ||||||||
template < 形参列表 > 类型形参关键词 名字 (可选) = 默认值
|
(2) | ||||||||
template < 形参列表 > 类型形参关键词 ... 名字 (可选)
|
(3) | (C++11 起) | |||||||
类型形参关键词 | - | class 或 typename 之一(C++17 起)
|
在模板声明体内,此形参的名字是一个模板名(且需要实参以实例化)。
template<typename T> class my_array {}; // 两个类型模板形参和一个模板模板形参: 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; // 错误:重声明模板形参 void f() { char T; // 错误:重声明模板形参 } }; template<class X> class X; // 错误:重声明模板形参
在某个类模板定义外的出现的类模板成员定义中,类模板成员名会隐藏任何外围类模板的模板形参名,但如果该成员是类或函数模板就不会隐藏该成员的模板形参。
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 的 B,不是模板形参 } template<class B> template<class C> void A<B>::g(C) { B b; // A 的 B,不是模板形参 C c; // 模板形参 C,不是 A 的 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 是模板形参,不是 N::C }
在类模板定义中,或类模板某个成员的位于模板定义外的定义中,对于每个非待决基类,如果基类名或基类成员名与模板形参名相同,那么该基类名或成员名隐藏模板形参名。
struct A { struct B {}; int C; int Y; }; template<class B, class C> struct X : A { B b; // A 的 B C b; // 错误:A 的 C 不是类型名 };
[编辑] 默认模板实参
默认模板实参在形参列表中在 = 号之后指定。可以为任何种类的模板形参(类型、常量或模板)指定默认实参,但不能对形参包指定(C++11 起)。
如果为主类模板、主变量模板(C++14 起)或别名模版的模板形参指定默认实参,那么它后面的所有模板形参都必须有默认实参,但最后一个可以是模板形参包(C++11 起)。在函数模板中,对跟在默认实参之后的形参没有限制,而只有在类型形参具有默认实参,或可以从函数实参推导时,才能跟在形参包之后(C++11 起)。
以下情况不允许默认形参:
- 在类模板的成员的类外定义中(必须在类体内的声明中提供它们)。注意非模板类的成员模板可以在它的类外定义中使用默认形参(见 GCC 漏洞 53856)
- 在友元类模板声明中
|
(C++11 前) |
在友元函数模板的声明上,仅当声明是定义,且此翻译单元不出现此函数的其他声明时,才允许默认模板实参。 |
(C++11 起) |
各个声明中所出现的默认模板实参,以类似默认函数实参的方式合并:
template<typename T1, typename T2 = int> class A; template<typename T1 = int, typename T2> class A; // 如上与如下相同: template<typename T1 = int, typename T2 = int> class A;
但在同一作用域中不能两次为同一形参指定默认实参:
template<typename T = int> class X; template<typename T = int> class X {}; // 错误
当解析常量模板形参的默认模板实参时,第一个非嵌套的 > 被当做模板形参列表的结尾而非大于运算符:
template<int i = 3 > 4> // 语法错误 class X { /* ... */ }; template<int i = (3 > 4)> // OK class Y { /* ... */ };
模板模板形参的模板形参列表可拥有它自己的默认实参,它只会在模板模板实参自身处于作用域中时有效:
// 类模板,带有默认实参的类型模板形参 template<typename T = float> struct B {}; // 模板模板形参 T 有形参列表, // 它由一个带默认实参的类型模板形参组成 template<template<typename = float> typename T> struct A { void f(); void g(); }; // 类体外的成员函数模板定义 template<template<typename TT> class T> void A<T>::f() { T<> t; // 错误:TT 在作用域中没有默认实参 } template<template<typename TT = char> class T> void A<T>::g() { T<> t; // OK:t 是 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; // 错误:C::TT 是受保护的
默认模板实参在需要该默认实参的值时被隐式实例化,除非模板用于指名函数: template<typename T, typename U = int> struct S {}; S<bool>* p; // 默认模板实参 U 在此点实例化 // p 的类型是 S<bool, int>* |
(C++14 起) |
[编辑] 注解
C++26 前,常量模板形参在标准用词中被称为非类型模板形参。用语是由 P2841R6 / PR#7587 更改的。
模板形参中,对类型和常量形参都可以使用类型约束,这取决于是否有 auto。 template<typename> concept C = true; template<C, // 类型形参 C auto // 常量形参 > struct S{}; S<int, 0> s;
|
(C++20 起) |
功能特性测试宏 | 值 | 标准 | 功能特性 |
---|---|---|---|
__cpp_nontype_template_parameter_auto |
201606L |
(C++17) | 声明带有 auto 的常量模板形参 |
__cpp_nontype_template_args |
201411L |
(C++17) | 允许所有常量模板实参的常量求值 |
201911L |
(C++20) | 常量模板形参中的类类型和浮点数类型 |
[编辑] 示例
#include <array> #include <iostream> #include <numeric> // 简单的常量模板形参 template<int N> struct S { int a[N]; }; template<const char*> struct S2 {}; // 复杂的常量形参的例子 template < char c, // 整型类型 int (&ra)[5], // 到(数组类型)对象的左值引用 int (*pf)(int), // 函数指针 int (S<10>::*a)[10] // 指向(int[10] 类型的)成员对象的指针 > struct Complicated { // 调用编译时所选择的函数 // 并在编译时将它的结果存储在数组中 void foo(char base) { ra[4] = pf(c - base); } }; // S2<"fail"> s2; // 错误:不能用字符串字面量 char okay[] = "okay"; // 有连接的静态对象 // S2<&okay[0] > s3; // 错误:数组元素无连接 S2<okay> s4; // 能用 int a[5]; int f(int n) { return n; } // C++20:常量模板形参可以具有字面量类类型 template<std::array arr> constexpr auto sum() { return std::accumulate(arr.cbegin(), arr.cend(), 0); } // C++20:可以在调用处推导出类模板实参 static_assert(sum<std::array<double, 8>{3, 1, 4, 1, 5, 9, 2, 6}>() == 31.0); // C++20:常量模板形参的实参推导和类模板实参推导 static_assert(sum<std::array{2, 7, 1, 8, 2, 8}>() == 28); int main() { S<10> s; // s.a 是含有 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 184 | C++98 | 未指明模板模板形参中的模板形参是否可以有默认实参 | 添加相应说明 |
CWG 1922 | C++98 | 不明确名字是注入类名的类模板是否可以使用之前的声明中的默认模板实参 | 可以使用 |
CWG 2032 | C++14 | 对于变量模板,有默认实参的模板形参后的模板形参没有任何限制 | 应用与类模板和别名模板相同的限制 |
CWG 2542 | C++20 | 不明确闭包类型是不是结构化类型 | 不是结构化类型 |
CWG 2845 | C++20 | 闭包类型不是结构化类型 | 无捕获时是结构化类型 |