C++ 可变参数模板

可变参数模板

  • 可变参数模板
  • 参数包
  • 示例1
  • 示例2
  • 包扩展(展开)
  • 示例3
  • 参考资料

可变参数模板

一个可变参数模板(variadic template)就是一个接受可变数目参数的函数模板或类模板。

参数包

可变数目的参数被称为参数包(parameter packet)。

存在两种参数包:模板参数包(template parameter packet),表示0个或多个模板参数;函数参数包(function parameter packet),表示0个或多个函数参数。

采用省略号(…)的形式来指出这是一个参数包。

在模板参数列表中,class…或typename…指出接下来的参数表示0个或多个类型的列表。一个类型名后面跟省略号(T…)表示0个或多个给定类型T的非类型参数的列表。在函数参数列表中,如果一个参数的类型是一个模板参数包,则此参数也是一个函数参数包。

eg:

//Args 是一个模板参数包;rest是一个函数参数包  
//Args 表示0个或多个模板参数类型  
//rest 表示0个或多个函数参数  
template<typename T,typename... Args>  
void foo(const T &t, const Args& ... rest);  

声明了foo函数是一个可变参数函数模板,它有一个名为T的类型参数和一个名为Args的模板参数包。

示例1

template<typename T,typename... Args>
void foo(const T &t, const Args& ... rest)
{
	cout << sizeof...(Args) << ends;
	cout << sizeof...(rest) << ends;
}

void test01()
{
	int i = 10;
	double d = 3.14;
	string s = "hello";
	foo(i, s, 42, d);//包中有3个参数
	foo("hi");//包中有0个参数
}

结果:
C++ 可变参数模板_第1张图片
编译器为foo实例化了两个不同的版本:

void foo(const int&,const string&,const int&,const double&);
void foo(const char[3]&);

每个实例的T类型都是第一个实参的类型,剩下的是参数包。
sizeof...运算符可以用来查看包中元素个数。

示例2

通过递归调用实现可变参数函数print.

template<typename T>
ostream& print(ostream &os,const T &t)
{
	return os << t;
}

template<typename T, typename... Args>
ostream& print(ostream &os, const T &t, const Args&... rest)
{
	os << t << " ";
	return print(os, rest...);
}

void test02()
{
	int i = 0;
	double d = 3.14;
	string s = "hello";
	print(cout,i,s,42,d,'\n');	
}

结果:
在这里插入图片描述
第一个版本的print是一个普通的函数模板,它接受一个输出流和一个T类型,其的功能是输出T,并返回输出流的引用。其负责终止递归并打印初始调用中的最后一个实参。

第二个版本是一个可变参数函数模板,它打印绑定到t的实参,并调用自身打印参数包中剩下的值。
在每一次调用

return print(os, rest...);

时,rest中的第一个包中的实参被绑定到t,剩下的形成下一个print调用的参数包。
那么,在调用

print(cout,i,s,42,d,'\n');

时,会发生如下递归调用:

调用 t rest…
print(cout,i,s,42,d,’\n’) i s,42,d,’\n’
print(cout,s,42,d,’\n’) s 42,d,’\n’
print(cout,42,d,’\n’) 42 d,’\n’
print(cout,d,’\n’) d ‘\n’
print(cout,’\n’) 调用普通版本

对于最后一次调用,编译器会选择更加特化的普通版本。

注意:普通版本和可变参数版本要在同一作用域,否则会无限递归。

包扩展(展开)

对于一个参数包,除了获取其大小以外,唯一能对其进行的操作就是展开(expand)它。当展开一个包时,我们要提供用于每个扩展元素的模式(pattern)。展开一个包就是将它分解成构成的元素,对每个元素应用模式,获得展开后的列表。通过在模式右边放一个省略号(…)来触发展开操作。
eg:

template<typename T, typename... Args> //展开Args
ostream& print(ostream &os, const T &t, const Args&... rest) //展开rest

第一个展开操作展开模板参数包,为print生成函数参数列表。第二个扩展操作出现在对print的调用中。此模式为print调用生成实参列表。

对Args的展开中,编译器将模式const Args& 应用到模板参数包Args中的每个元素。因此,此模式的扩展结果是一个逗号分隔的0个或多个类型的列表,每个类型形如const type& 。
例如:

print(cout,i,s,42,d,'\n');	//参数包大小为4

最后4个实参的类型和模式一起确定了未知参数的类型。此调用被实例化为:

ostream& print(ostream &, const int&, const string&, const int&, const double&, const char&);

第二个扩展发生在对print的递归调用中。在此情况下,模式是函数参数包的名字(即rest)。此模式扩展出一个由包中元素组成的、逗号分隔的列表。因此,这个调用等价于

print(os,s,42,d,'\n');

示例3

使用较为复杂的扩展模式。

//... print 的两个版本

template<typename T>
string ossPrint(const T &t)
{
	ostringstream oss;
	oss << t;
	return oss.str();
};

template<typename... Args>
ostream &msg(ostream &os, const Args&... rest)
{
	return print(os, ossPrint(rest)...);
}

void test03()
{
	int i = 0;
	double d = 3.14;
	string s = "hello";
	msg(cout, i, s, 42, d, '\n');
}

C++ 可变参数模板_第2张图片
此处使用的模式是ossPrint(rest),这个模式表示对包中的每一个元素调用ossPrint。

注意省略号的位置。是在ossPrint(rest)…而不是ossPrint(rest…),其原因是ossPrint并不接受一个可变参数包,它只会接受一个普通类型参数。

参考资料

《C++ Primer 第5版》

你可能感兴趣的:(C++,c++)