C++ 参考手册
- C++11
- C++14
- C++17
- C++20
- C++ 编译器支持情况表
- 独立与宿主实现
- C++ 语言
- 变量模板(C++14 起)
- 整数字面量
- 聚合初始化
- 比较运算符
- 默认比较(C++20 起)
- 转义序列
- for 循环
- while 循环
- 用户定义转换
- SFINAE
- 主函数
- ASCII 码表
- 标识符
- 类型
- 内存模型
- 对象
- 基本概念
- 表达式
- 声明
- 初始化
- 函数
- 语句
- 类
- 运算符重载
- 模板
- 异常
- 事务性内存
- 占位符类型说明符 (C++11 起)
- decltype 说明符
- 函数声明
- final 说明符 (C++11 起)
- override 说明符(C++11 起)
- 引用声明
- 移动构造函数
- 移动赋值运算符
- 枚举声明
- constexpr 说明符(C++11 起)
- 列表初始化 (C++11 起)
- 构造函数与成员初始化器列表
- using 声明
- nullptr,指针字面量
- 基础类型
- 类型别名,别名模版 (C++11 起)
- 形参包
- 联合体声明
- 字符串字面量
- 用户定义字面量 (C++11 起)
- 属性说明符序列(C++11 起)
- Lambda 表达式 (C++11 起)
- noexcept 说明符 (C++11 起)
- noexcept 运算符 (C++11 起)
- alignof 运算符(C++11 起)
- alignas 说明符 (C++11 起)
- 存储类说明符
- 基于范围的 for 循环 (C++11 起)
- static_assert 声明
- 隐式转换
- 代用运算符表示
- 自增/自减运算符
- 折叠表达式(C++17 起)
- 类模板实参推导(C++17 起)
- 模板形参与模板实参
- if 语句
- inline 说明符
- 结构化绑定声明 (C++17 起)
- switch 语句
- 字符字面量
- 命名空间
- 求值顺序
- 复制消除
- consteval 说明符 (C++20 起)
- constinit 说明符 (C++20 起)
- 协程 (C++20)
- 模块 (C++20 起)
- 约束与概念 (C++20 起)
- new 表达式
- do-while 循环
- continue 语句
- break 语句
- goto 语句
- return 语句
- 动态异常说明
- throw 表达式
- try 块
- 命名空间别名
- 类声明
- cv(const 与 volatile)类型限定符
- 默认初始化
- 值初始化(C++03 起)
- 零初始化
- 复制初始化
- 直接初始化
- 常量初始化
- 引用初始化
- 值类别
- C++ 运算符优先级
- 布尔字面量
- 浮点字面量
- typedef 说明符
- 显式类型转换
- static_cast 转换
- dynamic_cast 转换
- const_cast 转换
- reinterpret_cast 转换
- delete 表达式
- 构造函数与成员初始化器列表
- this 指针
- 访问说明符
- 友元声明
- virtual 函数说明符
- explicit 说明符
- 静态成员
- 默认构造函数
- 复制构造函数
- 复制赋值运算符
- 析构函数
- 类模板
- 函数模板
- 显式(全)模板特化
- 汇编声明
- C++ 的历史
- 作用域
- 生存期
- 定义与单一定义规则(ODR)
- 名字查找
- 有限定的名字查找
- 无限定的名字查找
- 如同规则
- 未定义行为
- 翻译阶段
- 常量表达式
- 赋值运算符
- 算术运算符
- 逻辑运算符
- 成员访问运算符
- 其他运算符
- sizeof 运算符
- typeid 运算符
- 指针声明
- 数组声明
- 语言链接
- 详述类型说明符
- 默认实参
- 变长实参
- 实参依赖查找
- 重载决议
- 重载函数的地址
- 注入类名
- 非静态数据成员
- 非静态成员函数
- 嵌套类
- 派生类
- 空基类优化
- 抽象类
- 位域
- 转换构造函数
- 成员模板
- 模板实参推导
- 部分模板特化
- sizeof... 运算符
- 待决名
- 函数 try 块
- 扩充命名空间 std
- 字母缩写
- RAII
- 三/五/零之法则
- PImpl
- 零开销原则
- 类型
- 隐式转换
- 注释
- C++ 关键词
- 预处理器
- C++ 标准库头文件
- 具名要求
- 功能特性测试 (C++20)
- 工具库
- 类型支持(基本类型、RTTI、类型特性)
- 概念库 (C++20)
- 错误处理
- 动态内存管理
- 日期和时间工具
- 字符串库
- 容器库
- 迭代器库
- 范围库 (C++20)
- 算法库
- 数值库
- 输入/输出库
- 文件系统库
- 本地化库
- 正则表达式库
- 原子操作库
- 线程支持库
- 实验性 C++ 特性
- 有用的资源
- 索引
- std 符号索引
- 协程支持 (C++20)
- C++ 关键词
模板形参与模板实参
每个模板都是由一或多个模板形参所参数化的,它们在模板声明语法中的 形参列表 中指定:
template < 形参列表 > 声明
|
(1) | ||||||||
形参列表 中的每个形参可以是:
- 非类型模板形参;
- 类型模板形参;
- 模板模板形参。
非类型模板形参
类型 名字(可选) | (1) | ||||||||
类型 名字(可选) = default
|
(2) | ||||||||
类型 ... 名字(可选)
|
(3) | (C++11 起) | |||||||
占位符 名字 | (4) | (C++17 起) | |||||||
auto
的任何类型(例如单纯的 auto、auto ** 或 auto &),被推导类类型的占位符 (C++20 起),或者 decltype(auto)。非类型模板形参必须拥有结构化类型,它是下列类型之一(可选地有 cv 限定,忽略限定符):
(C++11 起) |
|
(C++20 起) |
数组与函数类型可以写在模板声明中,但它们被自动替换为适合的对象指针和函数指针。
当在类模板体内使用非类型模板形参的名字时,它是不可修改的纯右值,除非其类型是左值引用类型,或其类型是类类型 (C++20 起)。
形式为 class Foo 的模板形参不是类型为 Foo
的非类型模板形参,虽然 class Foo 还能是详述类型说明符且 class Foo x; 声明 x
为 Foo
类型的对象。
若非类型模板形参的类型包含占位符类型 template<auto n> struct B { /* ... */ }; B<5> b1; // OK:非类型模板形参类型为 int B<'a'> b2; // OK:非类型模板形参类型为 char B<2.5> b3; // 错误:非类型模板形参类型不能是 double 对于类型中使用了占位符类型的非类型模板形参包,每个模板实参的类型是独立地进行推导的,而且不需要互相匹配: template<auto...> struct C {}; C<'C', 0, 2L, nullptr> x; // OK |
(C++17 起) |
指名类类型 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<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 < 形参列表 > typename(C++17)|class 名字(可选)
|
(1) | ||||||||
template < 形参列表 > typename(C++17)|class 名字(可选) = default
|
(2) | ||||||||
template < 形参列表 > typename(C++17)|class ... 名字(可选)
|
(3) | (C++11 起) | |||||||
不同于类型模板形参声明,模板模板形参只能用关键词 |
(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; };
模板实参
为使模板被实例化,其每个模板形参(类型、非类型或模板)必须由一个对应的模板实参所替换。对于类模板,实参或被显式提供,或从初始化器推导, (C++17 起)或为默认。对于函数模板,实参或被显式提供,或从语境推导,或为默认。
若实参同时可被解释成类型标识和表达式,则它始终被解释成类型标识,即使其对应的是非类型模板形参也是如此:
template<class T> void f(); // #1 template<int I> void f(); // #2 void g() { f<int()>(); // "int()" 既是类型又是表达式, // 调用 #1 因为它被解释成类型 }
模板非类型实参
实例化拥有非类型模板形参的模板时,应用下列限制:
特别是,这意味着字符串字面量、数组元素的地址和非静态成员的地址,不能被用作模板实参,来实例化其对应非类型模板形参是对象指针的模板形参的模板。 |
(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) 仅有的例外是,引用或指针类型的非类型模板形参,以及类类型的非类型模板形参及其子对象之中的引用或指针类型的非静态数据成员 (C++20 起),不能指代下列对象/是下列对象的地址 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; // 错误:数组元素的地址 X<&s.m> x4; // 错误:非静态成员的地址 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 |
(C++17 起) |
模板类型实参
类型模板形参的模板实参必须是类型标识,它可以指名不完整类型:
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<class 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
,A
的每个模板形参必须与 P
的对应模板形参严格匹配 (C++17 前)P
必须至少与 A
一样特殊 (C++17 起)。若 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 的第三(默认)形参是非类型形参
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; // C++17 在 CWG 150 后 OK // 更早时为错误:非严格匹配 X<C> xc; // C++17 在 CWG 150 后 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; // OK template <int> struct SI { /* ... */ }; template <template <auto> class> void FA(); // 注意:C++17 FA<SI>(); // 错误
正式来说,给定以下对两个函数模板的重写,根据函数模板的部分排序规则,如果对应于模板模板形参
若重写生成了非法类型,则 |
(C++17 起) |
默认模板实参
默认模板实参在形参列表中于 = 号之后指定。可以为任何种类的模板形参(类型、非类型或模板)指定默认实参,但不能对形参包指定。
若为主类模板、主变量模板 (C++14 起)或别名模版的模板形参指定默认实参,则其每个后继模板形参都必须有默认实参,但最后一个可以是模板形参包。在函数模板中,对跟在默认实参之后的形参没有限制,而仅当类型形参具有默认实参,或可从函数实参推导时,才可跟在形参包之后。
以下情况不允许默认形参
- 在类模板的成员的类外定义中(必须在类体内的声明中提供它们)。注意非模板类的成员模板可以在其类外定义中使用默认形参(见 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<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 起) |
示例
#include <iostream> // 简单的非类型模板形参 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] > s2; // 错误:数组元素无连接 S2<okay> s2; // 能用 int a[5]; int f(int n) { return n; } 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
本节未完成 原因:更多示例 |