可变参数模板是C++11引入的特性,它能够创建可变数量参数的模板函数和模板类。这一特性让我们可以编写出更加灵活和通用的代码。
可能光这样子说,没有感觉,那就举一个简单的例子吧,假设现在有一个需求:
要编写一个函数,它可以接收任意数量的参数,并且参数类型只需要是cout能够显示的即可。
从需求来看,传进函数的参数数量不固定,并且类型的顺序也不固定,我们不可能定义很多个打印函数来做到这个需求,因为组合的情况太多太多了。
这种情况下,用可变参数模板就可以解决这个问题。在后面的部分,我将用上面这个例子贯穿全文来说明可变参数模板,旨在说明可变参数模板这个技术。
一. 模板参数包和函数参数包
C++11提供了一个用省略号(...)表示的元运算符,用这个运算符来表示模板参数包和函数参数包的标识符,模板参数包基本上是一个类型列表,函数参数包基本上是一个值列表,语法如下:
模板参数包Args与任意数量(包括零)的类型匹配,函数参数包args所包含的值列表与模板参数包所含的类型列表(无论是类型还是数量)匹配。
上面那个打印函数参数的那个例子,我们可以声明为如下这样:
template<typename... Args>
void show_list(Args... args) {
......
}
如果现在我们需要这样调用它:
show_list('I', "love", 2024);
- 模板参数包Args包含的类型就是:char、const char*、int。
- 函数参数包args对应包含的值就是:'I'、 "love"、2024。
二.展开参数包
那模板参数包和函数参数包的内容该如何访问呢?索引功能在这里无法使用,即不能使用类似于Args[1]来访问包中的第二个参数类型;
1. 递归方式访问
函数参数包的话,可以将省略号放在其右边(即:args...),将参数包展开。采用递归的方式访问:对参数列表里的第一项进行处理,再将余下的内容传递给递归调用,直到列表为空。
还是关于打印参数这个例子,看一下如何展开这个函数参数包并打印。和常规递归一样,确保递归终止很重要, 最后写出来的代码如下:
// 递归终止条件
void show_list() {};
template<typename T, typename... Args>
void show_list(T value, Args... args) {
// 打印第一个参数,然后递归打印剩余参数
cout << value << " ";
show_list(args...);
};
void test01() {
show_list('I', "love", 2024);
return;
};
在上面的示例中,show_list
函数使用了可变参数模板。当调用show_list
函数时,它会递归地打印每个参数,直到没有剩余参数为止。 这里有一个技巧就是函数模板的头改为了:
template<typename T, typename... Args>
void show_list(T value, Args... args) {}
show_list()的第一个实参决定了T和value的值,而其他实参决定了Args和args的值。这让函数能够对value进行处理;然后递归调用show_list(),并以args...的方式将其他实参传递给它。每一次递归调用处理一个值,并且传递缩短了的列表,直到列表为空为止:
- 当调用了show_list('I', "love", 2024),第一个实参会使得T为char,value为‘I’;另外两个类型(string和int)将放入Args包中,对应的另外两个实参将放入args包中。
- 接下来show_list(args...): 考虑到args...的展开作用,这一步与如下代码等价:show_list("love", 2024)。至此参数列表减少了一项
- 再按上述规则依次类推,直到参数列表为空为止。
最后函数调用过程就如下面这张图所示
2. 折叠表达式展开
折叠表达式展开的方式是到了C++ 17才提供的,C++ 11是不支援的。折叠表达式示例如下:
template<typename... Args>
void show_list(const Args&... args) {
(cout << ... << args)<<endl;
};
但是打印时各个参数都粘在了一起,不好看。可以利用逗号表达式控制一下格式打印,如下所示:
template<typename... Args>
void show_list(const Args&... args) {
(cout << ... << (cout<<args," ")) << endl;
};
3. 其他方式
还有其他的展开参数包的方式,不过这里就不多做介绍了,可以参考其他的博客: