文章目录
- 一. 为什么要有可变参数模板?
- 二. 什么是可变参数模板?
- 三. 如何展开参数包?
- 1. 递归函数方式展开参数包
- 2. 通过初始化列表展开参数包
一. 为什么要有可变参数模板?
C++98/03 中的模板为能够实现泛型编程提供了便利,但它们仍有不足之处:
有没有什么统一的办法能够让我们的类模版和函数模版中的参数不再固定呢?这样我们就可以不用根据参数个数再去依次定义多个它们对应的模板了。
C++11 的新特性可变参数模板能够让你表示任意个数、任意类型的参数的函数模板和类模板,相比 C++98/03:类模版和函数模版中只能含固定数量和固定类型的模版参数,可变参数模版无疑是一个巨大的改进。
二. 什么是可变参数模板?
下面就是一个基本可变参数的函数模板
/*
- Args是一个模板参数包
- args是一个函数形参参数包
- 声明一个参数包 Args...args,这个参数包中可以包含0到任意个模板参数
*/
template <class ...Args>
void ShowList(Args... args)
{}
上面的参数 args 前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。
我们可以实例化上面的函数模板:传入任意类型、数量的参数
#include <iostream>
using namespace std;
template <class ...Args>
void ShowList(Args... args)
{}
int main()
{
// 1、向参数包传入一个整型参数
ShowList(1);
// 2、传入一个整型和一个字符
ShowList(1, 'A');
// 3、传入一个整型1、字符'A'、字符串"sort"
ShowList(1, 'A', std::string("sort"));
return 0;
}
此外我们还能够在可变参数的函数模板中利用sizeof...(参数包名称)
计算其参数包中包含的参数个数:
编译运行:
三. 如何展开参数包?
关于参数包里的各个参数,我们是无法直接获取到的,只能通过展开参数包的方式,来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何获取到每个参数。由于语法不支持使用 args[i] 这样取下标的方式获取可变参数,所以我们需要用一些奇招来获取到参数包的值。
1. 递归函数方式展开参数包
既然不能直接从参数包中拿到参数,那我们考虑在 ShowList 函数中再引入一个 T 类型的模板参数 value,用来专门接收传入的第一个参数,这样其他 n-1 个参数就保存到了参数包中;然后用参数包继续递归调用 ShowList ,依次拿到后面的其他参数。
根据上面的想法,写出如下函数:
// 展开函数
template <class T, class ...Args>
void ShowList(T value, Args... args)
{
// 1、传入的第一个参数保存到value中
cout << value <<" ";
// 2、递归继续拿参数包中的参数
ShowList(args...);
}
int main()
{
ShowList(1, 'A', std::string("sort"));
return 0;
}
递归调用图:
现在能够拿到参数包中的所有参数了,但是参数全拿完后这个递归如何停止呢?能否通过检查参数包中数据的个数来终止递归呢?
编译后会报错。因为这是个函数模板,编译阶段,编译器只负责推演模板的类型并配合进行基本的语法检查,而橙色框框起来的 if 逻辑代码是运行时才会执行的;实际编译时编译器不会管这段代码而直接推演下一次递归的类型和进行语法检查。
既然问题在于函数模板,我们考虑再实现一个 ShowList 同名的无参函数,这样当最后参数包为空时,系统会最优先匹配到这个无参的 ShowList 函数,然后在它里面进行 return 结束递归:
#include <iostream>
using namespace std;
// 递归终止函数(无参时最优先匹配该函数)
void ShowList()
{
cout<<endl;
// 在这里结束递归逻辑
}
// 展开函数(依次获取参数包的参数)
template <class T, class ...Args>
void ShowList(T value, Args... args)
{
cout << value <<" ";
ShowList(args...);
}
int main()
{
ShowList(1);
ShowList(1, 'A');
ShowList(1, 'A', std::string("sort"));
return 0;
}
编译运行:
2. 通过初始化列表展开参数包
这种展开参数包的方式,不需要通过递归终止函数,而是通过初始化列表来初始化一个变长数组,从而将参数包展开的,PrintArg(...)
不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。这种就地展开参数包的方式实现的关键是初始化列表。
// 处理不传参的情况
void ShowList()
{}
// 处理参数包中每一个参数
template <class T>
int PrintArg(T t)
{
cout << t << " ";
return 0;
}
// 通过初始化列表获取参数
template <class ...Args>
void ShowList(Args... args)
{
int arr[] = { PrintArg(args)... };
cout << endl;
}
int main()
{
ShowList();
ShowList(1, 'A');
ShowList(1, 'A', std::string("sort"));
return 0;
}
运行结果
解释说明
扩展:通过初始化列表和逗号表达式来张开参数包
// 处理参数包中每一个参数
template <class T>
void PrintArg(T t)
{
cout << t << " ";
}
// 初始化列表 + 逗号表达式
template <class ...Args>
void ShowList(Args... args)
{
int arr[] = { (PrintArg(args), 0)... };
cout << endl;
}
解释说明