Template arguments
为了使模板能够实例化,每个 模板参数 都必须被对应的模板实参所替代。这些实参可以显式提供、通过推导获得或使用默认值。
模板参数列表(参见 模板标识符语法 )中的每个参数属于以下类别之一:
- 常量模板实参
- 类型模板实参
- 模板模板实参
目录 |
常量模板参数
也称为 非类型模板参数 (参见 下文 )。
|
可用于常量模板参数的模板实参可以是任何 显式常量求值表达式 。 |
(C++11 前) |
| (C++11 起) |
给定常量模板参数声明的
类型
为
T
,且为该参数提供的模板实参为
E
。
|
虚构声明 T x = E ; 必须满足具有 静态存储期 的 constexpr 变量 定义的语义约束。 |
(C++20 起) |
|
若
若推导出的参数类型不是 结构类型 ,则程序非良构。 对于类型使用占位类型的常量模板参数包,每个模板参数的类型均独立推导且无需匹配。 |
(C++17 起) |
template<auto n> struct B { /* ... */ }; B<5> b1; // 正确:常量模板参数类型为 int B<'a'> b2; // 正确:常量模板参数类型为 char B<2.5> b3; // 错误(C++20 前):常量模板参数类型不能为 double // C++20 推导的类类型占位符,类模板参数在调用点推导 template<std::array arr> void f(); f<std::array<double, 8>{}>(); template<auto...> struct C {}; C<'C', 0, 2L, nullptr> x; // 正确
常量模板参数
P
的值(其类型为
T
(可能被推导)
(C++17 起)
)通过其模板实参
A
按以下方式确定:
|
(C++11 前) |
|
(C++11 起)
(C++20 前) |
|
(C++20 起) |
template<int i> struct C { /* ... */ }; C<{42}> c1; // 正确 template<auto n> struct B { /* ... */ }; struct J1 { J1* self = this; }; B<J1{}> j1; // 错误:模板参数对象的初始化 // 不是常量表达式 struct J2 { J2 *self = this; constexpr J2() {} constexpr J2(const J2&) {} }; B<J2{}> j2; // 错误:模板参数对象与引入的临时对象 // 不是模板参数等价的
|
实例化具有常量模板参数的模板时需遵循以下限制:
特别地,这意味着字符串字面量、数组元素地址和非静态成员地址不能用作模板实参来实例化对应常量模板参数为对象指针的模板。 |
(C++17 前) |
|
引用或指针类型的常量模板参数 ,以及类类型常量模板参数及其子对象中引用或指针类型的非静态数据成员 (C++20 起) 不得引用/为以下对象的地址: |
(C++17 起) |
template<const int* pci> struct X {}; int ai[10]; X<ai> xi; // 正确:数组到指针转换和cv限定符转换 struct Y {}; template<const Y& b> struct Z {}; Y y; Z<y> z; // 正确:无需转换 template<int (&pa)[5]> struct W {}; int b[5]; W<b> w; // 正确:无需转换 void f(char); void f(int); template<void (* pf)(int)> struct A {}; A<&f> a; // 正确:重载决议选择f(int)
template<class T, const char* p> class X {}; X<int, "Studebaker"> x1; // 错误:字符串字面量不能作为模板参数 template<int* p> class X {}; int a[10]; struct S { int m; static int s; } s; X<&a[2]> x3; // 错误(C++20前):数组元素的地址 X<&s.m> x4; // 错误(C++20前):非静态成员的地址 X<&s.s> x5; // 正确:静态成员的地址 X<&S::s> x6; // 正确:静态成员的地址 template<const int& CRI> struct B {}; B<1> b2; // 错误:模板参数需要临时变量 int c = 1; B<c> b1; // 正确
类型模板实参
类型模板参数的模板实参必须是 类型标识 ,该标识可以命名不完整类型:
template<typename T> class X {}; // 类模板 struct A; // 不完整类型 typedef struct {} B; // 对无名类型的类型别名 int main() { X<A> x1; // 正确:'A' 表示一个类型 X<A*> x2; // 正确:'A*' 表示一个类型 X<B> x3; // 正确:'B' 表示一个类型 }
模板模板参数
模板模板参数的模板实参必须是命名类模板或模板别名的 标识表达式 。
当参数是类模板时,匹配参数时仅考虑主模板。部分特化(如果存在)仅当基于此模板模板参数的特化被实例化时才会被考虑。
template<typename T> // 主模板 class A { int x; }; template<typename T> // 部分特化 class A<T*> { long x; }; // 带有模板模板参数 V 的类模板 template<template<typename> class V> class C { V<int> y; // 使用主模板 V<int*> z; // 使用部分特化 }; C<A> c; // c.y.x 的类型为 int,c.z.x 的类型为 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为标识表达式PP。
如果重写产生无效类型,则
P
不至少与
A
同样特化。
template<typename T> struct eval; // 主模板 template<template<typename, typename...> class TT, typename T1, typename... Rest> struct eval<TT<T1, Rest...>> {}; // 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; // 正确:匹配 eval 的部分特化 eval<B<int, float>> eB; // 正确:匹配 eval 的部分特化 eval<C<17>> eC; // 错误:C 不匹配部分特化中的 TT // 因为 TT 的第一个参数是类型模板参数,而 17 不是类型名称 eval<D<int, 17>> eD; // 错误:D 不匹配部分特化中的 TT // 因为 TT 的第二个参数是类型参数包,而 17 不是类型名称 eval<E<int, float>> eE; // 错误:E 不匹配部分特化中的 TT // 因为 E 的第三个(默认)参数是常量
在采纳
P0522R0
之前,
A
的每个模板参数都必须与
P
的对应模板参数完全匹配。这导致许多合理的模板参数无法被接受。
尽管这个问题很早就被指出( CWG#150 ),但当它得到解决时,相关修改已被应用到 C++17 工作草案中,该解决方案实际上已成为 C++17 的特性。许多编译器默认禁用它:
- GCC 在 C++17 之前的所有语言模式中默认禁用此功能,只能通过设置编译器标志启用。
- Clang 在所有语言模式中默认禁用此功能,只能通过设置编译器标志启用。
- Microsoft Visual Studio 将其视为常规 C++17 功能,仅在 C++17 及更高语言模式中启用(即在默认的 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; // 正确 X<B> xb; // P0522R0 后正确 // 早期错误:不完全匹配 X<C> xc; // P0522R0 后正确 // 早期错误:不完全匹配 template<template<class...> class Q> class Y { /* ... */ }; Y<A> ya; // 正确 Y<B> yb; // 正确 Y<C> yc; // 正确 template<auto n> class D { /* ... */ }; // 注意:C++17 template<template<int> class R> class Z { /* ... */ }; Z<D> zd; // P0522R0 后正确:模板参数 // 比模板实参更特化 template<int> struct SI { /* ... */ }; template<template<auto> class> void FA(); // 注意:C++17 FA<SI>(); // 错误
模板实参等价性
模板实参等价性用于判断两个 模板标识符 是否相同。
两个值在满足以下任一条件时是 模板实参等价 的:
- 它们是整数或枚举类型,且它们的值相同。
- 它们是指针类型,且具有相同的指针值。
- 它们是指向成员的指针类型,且指向同一个类成员或都是空成员指针值。
- 它们是左值引用类型,且引用同一个对象或函数。
|
(since C++11) |
|
(since C++20) |
歧义解析
如果模板实参既可以解释为 类型标识 也可以解释为表达式,它总是被解释为类型标识,即使对应的模板参数是常量:
template<class T> void f(); // #1 template<int I> void f(); // #2 void g() { f<int()>(); // “int()” 既是类型也是表达式, // 调用 #1 因为它被解析为类型 }
注释
在C++26之前,标准措辞中将常量模板实参称为非类型模板实参。该术语已通过 P2841R6 / PR #7587 进行了更改。
| 功能测试宏 | 值 | 标准 | 功能特性 |
|---|---|---|---|
__cpp_template_template_args
|
201611L
|
(C++17)
(DR) |
模板模板参数 的匹配 |
__cpp_nontype_template_args
|
201411L
|
(C++17) | 允许对所有 常量模板参数 进行常量求值 |
201911L
|
(C++20) | 常量模板参数 中的类类型和浮点类型 |
示例
|
本节内容不完整
原因:缺少示例 |
缺陷报告
以下行为变更缺陷报告被追溯应用于先前发布的C++标准。
| 缺陷报告 | 应用于 | 发布时的行为 | 正确行为 |
|---|---|---|---|
|
CWG 150
( P0522R0 ) |
C++98 |
模板模板实参必须与模板模板形参的
参数列表完全匹配 |
也允许更特化的
匹配方式 |
| CWG 354 | C++98 | 空指针值不能作为常量模板实参 | 允许 |
| CWG 1398 | C++11 |
常量模板实参不能具有
std::nullptr_t
类型
|
允许 |
| CWG 1570 | C++98 | 常量模板实参可以指向子对象的地址 | 不允许 |
| P2308R1 |
C++11
C++20 |
1. 不允许对常量模板实参使用
列表初始化(C++11) 2. 未明确说明类类型的常量模板 形参如何初始化(C++20) |
1. 允许
2. 已明确说明 |