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++ 关键词
形参包
模板形参包是接受零或更多模板实参(非类型、类型或模板)的模板形参。函数模板形参包是接受零或更多函数实参的函数形参。
至少有一个形参包的模板被称作变参模板。
语法
模板形参包(出现于别名模版、类模板、变量模板及函数模板形参列表中)
类型 ... Args(可选)
|
(1) | ||||||||
typename|class ... Args(可选)
|
(2) | ||||||||
template < 形参列表 > typename(C++17)|class ... Args(可选)
|
(3) | ||||||||
函数参数包(声明符的一种形式,出现于变参函数模板的函数形参列表中)
Args ... args(可选)
|
(4) | ||||||||
形参包展开(出现于变参模板体中)
模式 ...
|
(5) | ||||||||
模式
的逗号分隔列表。模式必须包含至少一个形参包。解释
变参类模板可用任意数量的模板实参实例化:
template<class ... Types> struct Tuple {}; Tuple<> t0; // Types 不包含实参 Tuple<int> t1; // Types 包含一个实参:int Tuple<int, float> t2; // Types 包含二个实参:int 与 float Tuple<0> error; // 错误:0 不是类型
变参函数模板可用任意数量的函数实参调用(模板实参通过模板实参推导推导):
template<class ... Types> void f(Types ... args); f(); // OK:args 不包含实参 f(1); // OK:args 包含一个实参:int f(2, 1.0); // OK:args 包含二个实参:int 与 double
在主类模板中,模板形参包必须是模板形参列表的最后一个形参。在函数模板中,模板参数包可以在列表中稍早出现,只要其后的所有形参均可从函数实参推导或拥有默认实参即可:
template<typename... Ts, typename U> struct Invalid; // 错误:Ts.. 不在结尾 template<typename ...Ts, typename U, typename=void> void valid(U, Ts...); // OK:能推导 U // void valid(Ts..., U); // 不能使用:Ts... 在此位置是非推导语境 valid(1.0, 1, 2, 3); // OK:推导 U 为 double,Ts 为 {int,int,int}
包展开
模式后随省略号,其中至少有一个形参包的名字至少出现一次,其被展开成零或更多个逗号分隔的模式实例,其中形参包的名字按顺序被替换成包中的各个元素。
template<class ...Us> void f(Us... pargs) {} template<class ...Ts> void g(Ts... args) { f(&args...); // “&args...” 是包展开 // “&args” 是其模式 } g(1, 0.2, "a"); // Ts... args 展开成 int E1, double E2, const char* E3 // &args... 展开成 &E1, &E2, &E3 // Us... 展开成 int* E1, double* E2, const char** E3
若两个形参包出现于同一模式中,则它们同时展开,而且它们必须有相同长度:
template<typename...> struct Tuple {}; template<typename T1, typename T2> struct Pair {}; template<class ...Args1> struct zip { template<class ...Args2> struct with { typedef Tuple<Pair<Args1, Args2>...> type; // Pair<Args1, Args2>... 是包展开 // Pair<Args1, Args2> 是模式 }; }; typedef zip<short, int>::with<unsigned short, unsigned>::type T1; // Pair<Args1, Args2>... 展开成 // Pair<short, unsigned short>, Pair<int, unsigned int> // T1 是 Tuple<Pair<short, unsigned short>, Pair<int, unsigned>> typedef zip<short>::with<unsigned short, unsigned>::type T2; // 错误:包展开中包含不同长度的形参包
若包展开内嵌于另一包展开中,则其所展开的是出现于最内层包展开的形参包,并且必须在外围(而非最内层)的包展开中必须提及另一个包:
template<class ...Args> void g(Args... args) { f(const_cast<const Args*>(&args)...); // const_cast<const Args*>(&args) 是模式,它同时展开两个包(Args 与 args) f(h(args...) + args...); // 嵌套包展开: // 内层包展开是“args...”,它首先展开 // 外层包展开是 h(E1, E2, E3) + args 它被第二次展开 // (成为 h(E1,E2,E3) + E1, h(E1,E2,E3) + E2, h(E1,E2,E3) + E3) }
展开场所
取决于发生展开的场所,其所产生的逗号分隔列表可以是不同种类的列表:函数形参列表,成员初始化器列表,属性列表,等等。以下列出了所有允许的语境。
函数实参列表
包展开可以出现在函数调用运算符的括号内,此情况下省略号左侧的最大表达式或花括号初始化器列表是被展开的模式。
f(&args...); // 展开成 f(&E1, &E2, &E3) f(n, ++args...); // 展开成 f(n, ++E1, ++E2, ++E3); f(++args..., n); // 展开成 f(++E1, ++E2, ++E3, n); f(const_cast<const Args*>(&args)...); // f(const_cast<const E1*>(&X1), const_cast<const E2*>(&X2), const_cast<const E3*>(&X3)) f(h(args...) + args...); // 展开成 // f(h(E1,E2,E3) + E1, h(E1,E2,E3) + E2, h(E1,E2,E3) + E3)
正式而言,函数调用表达式中的表达式列表被归类为初始化器列表,其模式是初始化器子句,它或是赋值表达式,或是花括号初始化器列表。
有括号初始化器
包展开可出现于直接初始化器,函数式转型及其他语境(成员初始化器,new 表达式等)的括号之内,这种情况下的规则与适用于上述函数调用表达式的规则相同:
Class c1(&args...); // 调用 Class::Class(&E1, &E2, &E3) Class c2 = Class(n, ++args...); // 调用 Class::Class(n, ++E1, ++E2, ++E3); ::new((void *)p) U(std::forward<Args>(args)...) // std::allocator::allocate
花括号包围的初始化器
在花括号初始化器列表(花括号包围的初始化器和其他花括号初始化器列表的列表,用于列表初始化和其他一些语境中)中,也可以出现包展开:
template<typename... Ts> void func(Ts... args){ const int size = sizeof...(args) + 2; int res[size] = {1,args...,2}; // 因为初始化器列表保证顺序,所以这可用于按顺序对包的每个元素调用函数: int dummy[sizeof...(Ts)] = { (std::cout << args, 0)... }; }
模板实参列表
包展开可用于模板形参列表的任何位置,前提是模板拥有与该展开相匹配的形参。
template<class A, class B, class...C> void func(A arg1, B arg2, C...arg3) { container<A,B,C...> t1; // 展开成 container<A,B,E1,E2,E3> container<C...,A,B> t2; // 展开成 container<E1,E2,E3,A,B> container<A,C...,B> t3; // 展开成 container<A,E1,E2,E3,B> }
函数形参列表
在函数形参列表中,若省略号出现于某个形参声明中(无论它是否指名函数形参包(例如在 Args ...
args中)),则该形参声明是模式:
template<typename ...Ts> void f(Ts...) {} f('a', 1); // Ts... 展开成 void f(char, int) f(0.1); // Ts... 展开成 void f(double) template<typename ...Ts, int... N> void g(Ts (&...arr)[N]) {} int n[1]; g<const char, int>("a", n); // Ts (&...arr)[N] 展开成 // const char (&)[2], int(&)[1]
注意:在模式 Ts (&...arr)[N]
中,省略号是最内层元素,而非如所有其他包展开中一样为其最后元素。
注意:Ts (&...)[N]
不被允许,因为 C++11 语法要求带括号的省略号形参拥有名字:CWG #1488。
模板形参列表
包展开可以出现于模板形参列表中:
template<typename... T> struct value_holder { template<T... Values> // 展开成非类型模板形参列表, struct apply { }; // 例如 <int, char, int(&)[5]> };
基类说明符与成员初始化器列表
包展开可以用于指定类声明中的基类列表。典型情况下,这也意味着其构造函数也需要在成员初始化器列表中使用包展开,以调用这些基类的构造函数:
template<class... Mixins> class X : public Mixins... { public: X(const Mixins&... mixins) : Mixins(mixins)... { } };
Lambda 俘获
包展开可以出现于 lambda 表达式的俘获子句中
template<class ...Args> void f(Args... args) { auto lm = [&, args...] { return g(args...); }; lm(); }
sizeof... 运算符
sizeof... 也被归类为包展开
template<class... Types> struct count { static const std::size_t value = sizeof...(Types); };
动态异常说明动态异常说明中的异常列表亦可为包展开 template<class...X> void func(int arg) throw(X...) { // ... 在不同情形下抛出不同的 X } |
(C++17 前) |
对齐说明符
包展开允许在关键词 alignas 所用的类型列表和表达式列表中使用
属性列表
包展开允许在属性列表中使用,如 [[attributes...]]。例如:void [[attributes...]] function()
折叠表达式在折叠表达式中,模式是不包含未展开形参包的整个子表达式。 using 声明在 using 声明中,省略号可以出现于声明器列表内,这对于从一个形参包进行派生时有用: template <typename... bases> struct X : bases... { using bases::g...; }; X<B, D> x; // OK:引入 B::g 与 D::g |
(C++17 起) |
注解
本节未完成 原因:关于部分特化和其他访问单独元素方式的一些话?提及递归 vs 对数 vs 短路,例如折叠表达式 |
示例
#include <iostream> void tprintf(const char* format) // 基础函数 { std::cout << format; } template<typename T, typename... Targs> void tprintf(const char* format, T value, Targs... Fargs) // 递归变参函数 { for ( ; *format != '\0'; format++ ) { if ( *format == '%' ) { std::cout << value; tprintf(format+1, Fargs...); // 递归调用 return; } std::cout << *format; } } int main() { tprintf("% world% %\n","Hello",'!',123); return 0; }
输出:
Hello world! 123
上述例子定义了类似 std::printf 的函数,并以一个值替换格式字符串中字符 % 的每次出现。
首个重载在仅传递格式字符串且无形参展开时调用。
第二个重载中分别包含针对实参头的一个模板形参和一个形参包,这允许递归调用中仅传递形参的尾部,直到它变为空。
Targs
是模板形参包而 Fargs
是函数形参包
参阅
函数模板 | |
类模板 | |
sizeof... | 查询形参包中的元素数量。 |
C 风格的变参函数 | |
预处理器宏 | 亦可为变参 |
折叠表达式 |