目录
- 可变参数模板
- 引入
- 基础讲解
- sizeof... 运算符
- 包展开(解包)
- 递归解包
- Lambda 捕获
- 转发参数包
- 实战
可变参数模板
引入
给你一个问题?
编写一个简单的
函数。
例如:
bool b = true;
print(12, ‘c’, 3.14, false, b, “hello”);
// output
// 12 c 3.14 false true hello
基础讲解
可变参数
:参数的数目是可变的,不固定的。即数目可以是任意有限个。
模板
:参数的类型是一种模板,是可经推导的,可以是任意存在的类型(系统类型或自定义类型)。
一个可变参数模板就是一个可以接受可变参数的模板函数或模板类。
形式如下:
template <typename... Args>
void func(Args... args) {}
template <class... Args>
class Obj {};
参数包
:可变数目的参数。
分析上面代码
template <typename... Args>
,定义了一个参数包,这包存放的可变参数的数据类型。类似于template <typename T>
,这是这里用typename...
代替了typename,用来说明定义的一个参数包(可以有多个类型数据)。Args
参数包名称,可更改,但我们常命名为 Args , 习惯所致。
void func(Args... args) {}
, 用上面定义的数据类型参数包Args,定义一个相对应的存放数据的数据包。Args... args
可以理解为对类型包 Args 依次取出数据类型,用来定义数据,最后把数据放入数据包 args 中。
注意:Args,args有的书上分别定义为模板参数包和函数参数包。 上面我说的Args是数据类型包,args是数据包,只是为了更好的理解Args和args。其实,我认为叫什么不重要,主要是你能更好的理解它。
int i = 2;
char c = 'A';
double d = 3.14;
func(i, c, d); // 包中有三个参数
func(i, c); // 包中有两个参数
func(i); // 有一个参数
func(); // 空包
编译器会为 func 分别实例化出四个不同的版本
void func(int, char, double);
void func(int, char);
void func(int);
void func();
sizeof… 运算符
到现在,我们已经学会简单的定义一个可变参数包。我们如何使用这个参数包的。
借助sizeof...
运算符,获得参数包中元素的个数。
template<typename... Args>
void putSize(Args... args)
{
cout << "sizeof...(Args) : " << sizeof...(Args) << endl; // 类型参数的数目
cout << "sizeof...(Args) : " << sizeof...(Args) << endl; // 数据参数的数目
}
更进一步,我们可以写个工具类,包装一下 sizeof…
#include <iostream>
using namespace std;
// 工具类,返回参数包中元素数目
template<class... Args>
struct Count
{
static const std::size_t value() { return sizeof...(Args); }
};
template<class... Args>
void f(Args... args)
{
cout << sizeof...(Args) << endl; // 5
cout << sizeof...(args) << endl; // 5
cout << Count<Args...>::value() << endl; // 5
cout << Count<decltype(args)...>::value() << endl; // 5
}
int main()
{
f(1, 2, 'c', 3.14, "hello");
return 0;
}
包展开(解包)
-
C++不允许对通过
[]
的方式进行解包,args[0] 是错误的。
-
C++的包展开是通过
args...
的形式,后面...
就意味着展开包。
template <typename T, typename... Args> void func(T v, Args... args)
{
func(args...); // 形如 func(arg1, arg2, arg3);
}
int i = 2; double d = 1.8; char c = 'c';
func(i, d, c); // Args 中包含三个类型数据 int、double、char // args 包含三个数据 2、1.8、'c'
上面是直接展开包,存进去什么,取出来什么。
但是如果现在有个需求,已知args 中存放的是 int i, double d, char c, 但我要求是展开后取出的数据都是数据对应的地址,即 &i, &d, &c,又该如何解包。
C++提供一种机制,可以实现。&args...
就可实现,每次取出的数据 arg 自动转换成 &arg。
验证一下。
#include <iostream>
using namespace std;
void p1(int a, int b)
{
cout << "a: " << a << " b: " << b << endl;
}
void p2(int *a, int *b) // 接受指针参数
{
cout << "a: " << *a << " b: " << *b << endl;
}
template <class... Args>
void f1(Args... args)
{
p1(args...); // ...代表解包 展开为 p1(arg1, arg2);
p2(&args...); // &代表解包模式 &args... 被解包为 &arg1, &arg2, &arg3等等
// 展开为 p2(&arg1, arg2);
}
int main()
{
f1(2, 3);
return 0;
}
/*output
a: 2 b: 3
a: 2 b: 3
*/
更进一步,我们可以这样解包。
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)
int res[size] = {1, args..., 2}; // 展开成 {1, arg1, arg2, arg3, 2}
Class c2 = Class(n, ++args...); // 调用 Class::Class(n, ++E1, ++E2, ++E3);
递归解包
利用C++的模板推导来实现。
要求:
- 需要一个递归解包函数,用来递归解包。
- 需要一个终止递归函数,用来终止递归。
void f() { cout << endl; } // 如果 args 为空包, 即包已经全部展开,此时匹配此函数,结束递归。
template<typename T, typename... Args>
void f(T v, Args... args)
{
cout << v << ' ';
f(args...);
}
按照模板的推导规则,f 函数的调用规则是这样的。
依次从 args 参数包中取出一个参数放到 v 中, 剩下的依然放在包中。
void f<int, double, char>(int v, double __args1, char __args2);
void f<double, char>(double v, char __args1);
void f<char>(char v);
void f();
Lambda 捕获
// Lambda 捕获
template<typename... Args>
void f2(Args... args)
{
auto lm = [&, args...] { return p1(args...); };
lm();
}
转发参数包
我们依然可以借助forward机制来编写函数。实现将参数原封不动的传递给其他函数。
注意:这是 std::forward 完美转发的知识。还包括左右值引用,万能引用,引用折叠等细节知识。不是我们文章的重点,我们就不展开说了。
一般右值引用的传递,我们使用std::forward<type>(value)
就行。对于参数包略有不同。借助std::forward<Args>(args)...
就可以实现参数的完美转发了。
template<typename T, typename... Args>
void f(T v, Args... args)
{
f(std::forward<type>(value)...);
}
实战
要求: 编写一个简单的 print
函数,实现接受任意有限数量的参数,并输出。
#include <iostream>
// 终止递归函数
void print(){
std::cout << std::endl;
}
// 递归解包函数
template <class T, class... Args>
void print(T&& val, Args&&... args)
{
std::cout << std::boolalpha << val << " ";
print(std::forward<Args>(args)...); // 转发参数包
}
int main()
{
bool b = true;
print(12, 'c', 3.14, false, b, "hello");
return 0;
}
/*output
12 c 3.14 false true hello
*/