Template parameters
每个 模板 都由一个或多个模板参数进行参数化。
每个在 模板参数列表 中的参数(参见 模板声明语法 )都属于以下类别之一:
- 常量模板参数
- 类型模板参数
- 模板模板参数
目录 |
常量模板参数
亦称为 非类型模板参数 (参见 下文 )。
| 类型 名称 (可选) | (1) | ||||||||
类型
名称
(可选)
=
默认值
|
(2) | ||||||||
类型
...
名称
(可选)
|
(3) | (自 C++11 起) | |||||||
| type | - |
以下类型之一:
|
||||
| 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; // 均绑定到同一模板参数对象 assert(&ra == &rb); // 通过 } |
(since C++20) |
类型模板参数
| 类型参数关键字 名称 (可选) | (1) | ||||||||
类型参数关键字
名称
(可选)
=
默认值
|
(2) | ||||||||
类型参数关键字
...
名称
(可选)
|
(3) | (C++11 起) | |||||||
| 类型约束 名称 (可选) | (4) | (C++20 起) | |||||||
类型约束
名称
(可选)
=
默认值
|
(5) | (C++20 起) | |||||||
类型约束
...
名称
(可选)
|
(6) | (C++20 起) | |||||||
| 类型参数关键字 | - |
可以是
typename
或
class
。在类型模板参数声明中这两个关键字没有区别
|
| 类型约束 | - | 可以是 concept 的名称,也可以是后跟模板参数列表(置于尖括号中)的concept名称。无论哪种情况,concept名称都可以选择性地进行限定 |
| 名称 | - | 类型模板参数的名称 |
| 默认值 | - | 默认模板参数 |
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名称,它别名化模板实例化时提供的类型。
|
每个具有类型约束
type-constraint
为Q(指定概念
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> && ...) |
(since 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 起) | |||||||
| 类型形参关键字 | - |
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不是类型名称 };
默认模板参数
默认模板实参在参数列表中的 = 符号后指定。可以为任何类型的模板参数(类型、常量或模板)指定默认值 ,但不能为参数包指定 (since C++11) 。
若为主类模板 、主变量模板 (C++14 起) 或别名模板的模板参数指定了默认值,则每个后续模板参数都必须具有默认实参 ,但最后一个参数可以是模板参数包除外 (C++11 起) 。在函数模板中,跟在默认参数后的参数不受限制 ,且参数包后仅可跟随具有默认值或可从函数实参推导的类型参数 (C++11 起) 。
不允许使用默认参数
- 在 类模板 成员的类外定义中(必须在类体内的声明中提供)。注意非模板类的 成员模板 可以在其类外定义中使用默认参数(参见 GCC bug 53856 )
- 在 友元类模板 声明中
|
(C++11 前) |
|
在友元函数模板声明中,仅当该声明为定义且此翻译单元中未出现此函数的其他声明时,才允许使用默认模板参数。 |
(since 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)> // 正确 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; // 正确: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>* |
(since C++14) |
注释
在C++26之前,标准术语中将常量模板参数称为非类型模板参数。该术语通过 P2841R6 / PR#7587 进行了更改。
|
在模板参数中,类型约束可用于类型参数和常量参数,具体取决于是否出现 auto 。 template<typename> concept C = true; template<C, // type parameter C auto // constant parameter > struct S{}; S<int, 0> s;
|
(since 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:NTTP 可以是字面量类类型 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:NTTP 参数推导与 CTAD static_assert(sum<std::array{2, 7, 1, 8, 2, 8}>() == 28); int main() { S<10> s; // s.a 是包含 10 个整数的数组 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 | 闭包类型不是结构类型 | 若无捕获则为结构类型 |