参考:(90条消息) C++11可变模版参数的妙用 typename... 三点解析_阿六_Dexter的博客-CSDN博客_c++11 typename
1、概述
C++11的新特性--可变参数模板(variadic templates),是C++11新增的最强大的特性之一,它对参数进行了高度泛化,它能表示0到任意个参数、任意类型的参数。相比于C++98/03的类模版和函数模版中只能含固定数量的模版参数,那么,可变参数模板(参数可变)无疑是一个巨大的改进。然而由于可变参数模板比较抽象,使用起来需要一定的技巧,所以它也是C++11中最难理解和掌握的特性之一。虽然掌握可变参数模板有一定难度,但是它却是C++11中最有意思的一个特性。
2、可变模版参数的展开
可变参数模板和普通模板的语义是一样的,只是写法上稍有区别,声明可变参数模板时需要在typename或class后面带上省略号“...”。比如我们常常这样声明一个可变模版参数:template<typename...>或者template<class...>,一个典型的可变参数模版的定义是这样的:
template <class... T>
void f(T... args);
上面的参数args前面有省略号,所以它就是一个可变 模版参数,我们把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。我们无法直接获取参数包args中的每个参数,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。
可变模版参数和普通的模版参数语义是一致的,所以可以应用于函数和类,即可变模版参数函数和可变模版参数类。
2.1可变模版参数函数
一个简单的可变模版参数函数:
template <class... T>
void f(T... args)
{
cout << sizeof...(args) << endl; //打印变参的个数
}
f(); //0
f(1, 2); //2
f(1, 2.5, ""); //3
上面的例子中,f()没有传入参数,所以参数包为空,输出的size为0,后面两次调用分别传入两个和三个参数,故输出的size分别为2和3(计算变参个数的格式: sizeof...(args))。由于可变模版参数的类型和个数是不固定的,所以我们可以传任意类型和个数的参数给函数f。这个例子只是简单的将可变模版参数的个数打印出来,如果我们需要将参数包中的每个参数打印出来的话就需要通过一些方法了。展开可变模版参数函数的方法一般有两种:一种是通过递归函数来展开参数包,另外一种是通过逗号表达式来展开参数包。下面来看看如何用这两种方法来展开参数包。
2.1.1递归函数方式展开参数包
通过递归函数展开参数包,需要提供一个参数包展开的函数 和 一个递归终止函数,递归终止函数正是用来终止递归的,来看看下面的例子。
#include <iostream>
using namespace std;
//递归终止函数
void print()
{
cout << "empty" << endl;
}
//展开函数
template <class T, class ...Args>
void print(T head, Args... rest)
{
cout << "parameter " << head << endl;
print(rest...);
}
int main(void)
{
print(1,2,3,4);
return 0;
}
上例,会输出每一个参数,直到为空时输出empty。展开参数包的函数有两个,一个是递归函数,另外一个是递归终止函数,参数包Args...在展开的过程中递归调用自己,每调用一次参数包中的参数就会少一个,直到所有的参数都展开为止,当没有参数时,则调用非模板函数print终止递归过程。
注:我理解这里的递归,实际上就是不断实例化模板对象的过程。
第一次调用print(1,2,3,4);则实例化之后为print(1, Args... rest),即head=1,rest=2,3,4.
第二次则实例化 print(2, Args... rest),即head=2,rest=3,4.
如此类推。
递归调用的过程是这样的:
print(1,2,3,4);
print(2,3,4);
print(3,4);
print(4);
print();
程序执行结果如下
parameter 1
parameter 2
parameter 3
parameter 4
empty
上面的递归终止函数还可以写成这样:
template <class T>
void print(T t)
{
cout << t << endl;
}
修改递归终止函数后,上例中的调用过程是这样的:
print(1,2,3,4);
print(2,3,4);
print(3,4);
print(4);
当参数包展开到最后一个参数时递归为止。
再看一个通过可变模版参数求和的例子:
#include <iostream>
using namespace std;
template<typename T>
T sum(T t)
{
printf("t=%d\n",t);
return t;
}
template<typename T, typename ... Types>
T sum (T first, Types ... rest)
{
printf("first=%d\n",first);
return first + sum<T>(rest...);
}
int main(void)
{
printf("sum=%d\n",sum(1,2,3,4));
return 0;
}
sum在展开参数包的过程中将各个参数相加求和,参数的展开方式和前面的打印参数包的方式是一样的。
调用过程如下:
这里注意,只要第一次调用时参数T确定了是什么类型,后面递归时也都确定了是什么类型,因为有显示指定第一个参数类型,如下
如果将第一个参数类型定义为浮点数,后面的参数即使按整型赋值,也会被按浮点数处理。
首先将上面的代码修改一下,把参数的类型打印出来,如下
#include <iostream>
#include <cxxabi.h>
#include <typeinfo>
#include <string>
#include <cstring>
char * getClearName(const char* name)
{
int status = -1;
char* clear_name = abi::__cxa_demangle(name, NULL, NULL, &status);
const char* demangle_name = (status==0) ? clear_name : name;
char* pc=new char[100];
strcpy(pc,demangle_name);
return pc;
}
using namespace std;
template<typename T>
T sum(T t)
{
printf("t type is :%s\n",getClearName(typeid(t).name()));
printf("t=%d\n\n",t);
return t;
}
template<typename T, typename ... Types>
T sum (T first, Types ... rest)
{
printf("first type is :%s\n",getClearName(typeid(first).name()));
printf("first=%d\n\n",first);
return first + sum<T>(rest...);
}
int main(void)
{
printf("sum=%d\n",sum(1 ,2 ,3 ,4));
return 0;
}
执行后打印如下:
first type is :int
first=1first type is :int
first=2first type is :int
first=3t type is :int
t=4sum=10
然后,只将sum的第一个参数修改为 浮点数,比如第一个参数填写 1.2,则默认按double 类型处理。如下
#include <iostream>
#include <cxxabi.h>
#include <typeinfo>
#include <string>
#include <cstring>
char * getClearName(const char* name)
{
int status = -1;
char* clear_name = abi::__cxa_demangle(name, NULL, NULL, &status);
const char* demangle_name = (status==0) ? clear_name : name;
char* pc=new char[100];
strcpy(pc,demangle_name);
return pc;
}
using namespace std;
template<typename T>
T sum(T t)
{
printf("t type is :%s\n",getClearName(typeid(t).name()));
printf("t=%f\n\n",t);
return t;
}
template<typename T, typename ... Types>
T sum (T first, Types ... rest)
{
printf("first type is :%s\n",getClearName(typeid(first).name()));
printf("first=%f\n\n",first);
return first + sum<T>(rest...);
}
int main(void)
{
printf("sum=%f\n",sum(1.2 ,2 ,3 ,4));
return 0;
}
执行后打印结果如下:
first type is :double
first=1.200000first type is :double
first=2.000000first type is :double
first=3.000000t type is :double
t=4.000000sum=10.200000
可见,所有参数均按double类型处理了。