1.基本含义
可变参模板的英文名叫 V a r i a d i c T e m p l a t e Variadic \ Template Variadic Template,是 C + + 11 C++11 C++11引入的标准。可变参模板允许参数的数量不固定,可以有任意多个。
先看一个可变参函数模板,如下:
//基本含义
//可变参函数模板
template<typename ...T>
void myvtfunct(T...args) {
std::cout << "*****begin*****\n";
std::cout << "参数个数为:" << sizeof...(args) << "\n";
std::cout << "类型个数为:" << sizeof...(T) << "\n";
std::cout << "*****endl*****\n";
}
运行下面的代码:
void Test1() {
myvtfunct();
myvtfunct(10, 20);
myvtfunct(10, 12.5, 'a', 3LL);
//指定前几个参数
myvtfunct<float, float>(1.0, 2.0, 3, 4, "abc");
}
得到下面结果:
我们了解一些概念:
( 1 ) (1) (1)在 t e m p l a t e < t y p e n a m e . . . T > template<typename... T> template<typename...T>中我们需要在 t y p e n a m e typename typename后面加入 . . . ... ...,表示当前的参数是参数包(可变参)
( 2 ) (2) (2) 在函数模板的形参中,我们传入的是 T . . . a r g s T...args T...args类型,其中 T . . . T... T...表示可变参类型(一包类型),而这时 a r g s args args称为一包参数(一堆参数)
( 3 ) (3) (3) s i z e o f . . . sizeof... sizeof...是 C + + 11 C++11 C++11标准引入,通常 s i z e o f . . . ( a r g s ) sizeof...(args) sizeof...(args)是计算可变参模板的参数包的大小,而 s i z e o f . . . ( T ) sizeof...(T) sizeof...(T)则是可变参模板的类型包的大小(个数)。
2.可变参函数模板的展开
通常,我们使用到可变参模板的时候需要对其参数包进行展开,而展开的方式比较固定,一般都是通过递归的方式来展开。
2.1使用同名递归函数来终止递归
其中一种展开方式是对参数包进行逐步展开,最后使用一个同名普通函数来结束递归。
如下,注意普通函数的前向声明:
void myfunc1(void); //前向声明
//可变参的展开
template<typename T, typename ...U>
void myfunc1(T firstarg, U... otherargs) {
std::cout << "收到的参数值为:" << firstarg << "\n";
myfunc1(otherargs...);
}
// 终止递归的函数
void myfunc1() {
std::cout << "参数包展开时执行了终止函数!\n";
}
void Test2() {
myfunc1(1, 2.5, "test");
}
调用结果为:
可以发现,对于参数包的展开,是每次减少一个参数,而最后空参会调用普通函数来结束递归。
2.2使用 i f c o n s t e x p r if \ constexpr if constexpr来终止递归
C + + 17 C++17 C++17中引入了一个编译期间 i f if if的语句,可以在编译期间就确定是否会调用,具体而言, i f c o n s t e x p r if \ constexpr if constexpr语句内的参数必须是常量或是编译期间就能确定的值。
根据这个特性,我们在可变参模板中使用它,然后在编译期间就能判断模板参数是否为
0
0
0来结束递归。
具体如下:
//编译期间if语句(constexpr if)
template<typename T,typename... U>
void myfunc2(T firstarg,U...otherargs) {
//编译失败
//size_t size = sizeof...(otherargs);
//if constexpr (size > 0) {
if constexpr(sizeof...(otherargs)> 0) { //编译时确定
std::cout << "收到的参数值为:" << firstarg << "\n";
myfunc2(otherargs...);
}
else {
std::cout << "递归终止!\n";
}
}
当然,我们不能使用普通的
i
f
if
if,因为
i
f
if
if在编译期间时无法确定的。
另外,对于编译期间
i
f
if
if和条件编译还是有区别的:
( 1 ) (1) (1)条件变量如果条件为假,则不会编译
( 2 ) (2) (2)编译期间 i f if if即使条件为假,也会编译
如下,我们没有定义 _ M Y D E F \_MYDEF _MYDEF,自然条件编译就不成立,因此不会编译,而 i f c o n s t e x p r if \ constexpr if constexpr即使为假,仍然会编译:
void Test4() {
#ifdef _MYDEF
func();
#endif
//以下代码编译失败
const bool flag = false; //必须是常量
if constexpr (flag) {
func(); //即使为假也会编译
}
}
因此,由于我们没有定义函数 f u n c func func,所以 i f c o n s t e x p r if \ constexpr if constexpr括号内的内容将编译失败。
3.可变参函数模板的重载
和普通函数模板一样,可变参的函数模板也能重载,而调用先后顺序和函数模板一样,先是普通函数,然后才是函数模板:
//重载
template<typename ...T>
void myfunc(T...arg) {
std::cout << " myfunc(T...arg)执行了!\n";
}
//指针版本
template<typename ...T>
void myfunc(T*...arg) {
std::cout << " myfunc(T*...arg)执行了!\n";
}
//普通函数
void myfunc(int arg) {
std::cout << " myfunc(int arg)执行了!\n";
}
void Test5() {
myfunc(1); //优先执行普通函数
int* t = new int(1);
myfunc(t);//调用重载的指针版本
myfunc(1LL); //最后才是函数模板
}
调用结果如下:
先是调用普通函数,然后再调用了有修饰符的可变参函数模板,最后才是一般的可变参函数模板。