模板实参
为使模板被实例化,它的每个模板形参(类型、常量或模板)都必须被一个对应的模板实参替换。实参可以被显式提供,推导,或为默认。
模板实参列表 (参考模板标识语法)中的每个实参具有以下类别之一:
- 常量模板实参
- 类型模板实参
- 模板模板实参
目录 |
[编辑] 常量模板实参
又称为非类型模板实参(见下文)。
可以用在常量模板形参上的模板实参,可以是任何明显常量求值的表达式。 |
(C++11 前) |
可以用在常量模板形参上的模板实参,可以是任何初始化器子句。如果此初始化器子句是表达式,那么它必须是明显常量求值的表达式。 |
(C++11 起) |
给定常量模板形参声明的类型 为 T
,提供给该形参的模板实参为 E。
虚设声明 T x = E; 必须满足对具有静态存储期的 constexpr 变量的定义的语义约束。 |
(C++20 起) |
如果 如果被推导的类型不是结构化类型,那么程序非良构。 对于类型中使用了占位符类型的常量模板形参包,每个模板实参的类型会独立进行推导,而且不需要互相匹配。 |
(C++17 起) |
template<auto n> struct B { /* ... */ }; B<5> b1; // OK:常量模板形参的类型是 int B<'a'> b2; // OK:常量模板形参的类型是 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; // OK
(可能推导的)(C++17 起)T
类型的常量模板形参 P 的值,按如下方式从它的模板实参 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; // 错误:模板形参对象的初始化不是常量表达式 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; // OK:数组到指针转换和 cv 限定转换 struct Y {}; template<const Y& b> struct Z {}; Y y; Z<y> z; // OK:没有转换 template<int (&pa)[5]> struct W {}; int b[5]; W<b> w; // OK:没有转换 void f(char); void f(int); template<void (*pf)(int)> struct A {}; A<&f> a; // OK:重载决议选择 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; // OK:静态成员的地址 X<&S::s> x6; // OK:静态成员的地址 template<const int& CRI> struct B {}; B<1> b2; // 错误:模板实参要求临时量 int c = 1; B<c> b1; // OK
[编辑] 类型模板实参
类型模板形参的模板实参必须是类型标识,它可以指名不完整类型:
template<typename T> class X {}; // 类模板 struct A; // 不完整类型 typedef struct {} B; // 无名类型的类型别名 int main() { X<A> x1; // OK:'A' 指名类型 X<A*> x2; // OK:'A*' 指名类型 X<B> x3; // OK:'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; // OK:匹配 eval 的部分特化 eval<B<int, float>> eB; // OK:匹配 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 的第三个(默认)形参是常量形参
在采纳 P0552R0 前,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; // OK X<B> xb; // 在 P0552R0 后 OK;之前是错误的:非严格匹配 X<C> xc; // 在 P0552R0 后 OK;之前是错误的:非严格匹配 template<template<class...> class Q> class Y { /* ... */ }; Y<A> ya; // OK Y<B> yb; // OK Y<C> yc; // OK template<auto n> class D { /* ... */ }; // 注意:C++17 template<template<int> class R> class Z { /* ... */ }; Z<D> zd; // 在 P0552R0 后 OK:模板形参比模板实参更特殊 template<int> struct SI { /* ... */ }; template<template<auto> class> void FA(); // 注意:C++17 FA<SI>(); // 错误
[编辑] 模板实参等价性
模板实参等价性用来确定两个模板标识是否相同。
如果两个值拥有相同的类型,且满足以下条件之一,那么它们模板实参等价:
- 它们拥有整数或枚举类型且它们的值相同。
- 它们拥有指针类型且它们拥有同一指针值。
- 它们拥有成员指针类型且它们指代同一类成员或都是空成员指针值。
- 它们拥有左值引用类型且它们指代同一对象或函数。
|
(C++11 起) |
|
(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. 使之明确 |