可变参数模板
在C++11之前,不论是类模板或是函数模板都只允许接收固定数目的模板参数。C++11新增加了可变参数模板,其中允许使用任意个数、任意类型的模板参数,无需在模板定义时将参数固定。可变数目的参数称为参数包,在模板中通过省略号(“…”)表示。参数包可分为两种:模板参数包,表示零个或多个模板参数,函数参数包,表示零个或多个函数参数。
接下来给出一个可变参数函数模板,学习其定义方式,代码如下所示:
template <class T, class... Args> //Args是模板参数包,表示零个或多个模板参数
void foo(const T& t, const Args&... args) //args是函数参数包,表示零个或多个函数参数
{
函数体
}
上述代码中,定义可变参数函数模板foo(),该函数拥有一个T类型引用参数,以及一个名args的模板参数包,包中可以包含零个或多个类型参数。
与普通的函数模板一样,编译器根据函数实例,推断出模板参数类型,对于可变参数模板函数还需要推断出可变参数个数。根据上述foo()函数模板,若有如下函数调用形式,编译器根据实际参数类型及数量可以推断出foo()函数的不同版本,代码如下所示:
int n = 0;
double d = 3.14;
string s = “hello”;
foo(n, s, 10, d); //参数包中有三个参数
foo(s, 10); //参数包中有一个参数
foo(n); //参数包中无参数
根据上述代码,编译器为foo()函数实例化出三个版本,内容如下所示:
void foo(const int &, const string &, const int &, const double &);
void foo(const string &, const int &);
void foo(const int &);
根据调用形式中第一个参数推断出模板中T的类型,剩余参数提供可变参数内容。
可变参数模板还可以应用在模板类中,若有如下声明:
template<class… Values>
class Tuple;
模板类Tuple的对象能接收多个不同类型参数作为它的模板形参,若有如下代码:
class Tuple<int,std::vector<int>,std::map<std::string,std::vector<int>>> obj;
上述Tuple类的参数包中包含int、vector、map三种类型参数。
在传统的C语言中,printf ()函数是常用的可变参函数,能够对不同类型的任意多个数据,按格式进行输出,C++中可以通过可变参数模板实现printf()的功能。变长参数模板中无法像在类或函数中那样使用参数包,在这里以递归的方法取出可用参数,下面是利用C++11中可变参数模板实现的printf()函数,代码如下所示:
//定义只有一个字符指针参数的函数, 其调用形式如printf(“hello”)
void printf(const char *s)
{
while (*s)
{
//若出现形如printf(“%d,hello!”)的调用形式,抛出异常
//因为此时printf()函数实现的是将参数字符串原样输出,不会处理格式控制符
if (*s == '%' && *(++s) != '%')
throw std::runtime_error("invalid format string: missing arguments");
std::cout << *s++; //输出字符内容
}
}
//定义普通的printf()函数模板,用于输出多种不同类型数据
template<typename T, typename... Args>
void printf(const char* s, T value, Args... args)
{
while (*s)
{
if (*s == '%' && *(++s) != '%')
{
std::cout << value;
//递归调用printf(),每次从参数包中取出一个参数
printf(*s ? ++s : s, args...);
return;
}
std::cout << *s++;
}
throw std::logic_error("extra arguments provided to printf");
}
printf()函数会不断地递归调用自身:函数参数包 args... 在调用时,会被模板类匹配分离为T value和 Args... args直到 args... 变为空参数,则会与简单的 printf(const char *s) 匹配,退出递归。