可变参数模板

可变参数模板

一个可变参数模板就是一个接收可变数目参数的模板函数或模板类。可变数目的参数被称为参数包。存在两种参数包:模板参数包,表示零个或多个模板参数;函数参数包,表示零个或多个函数参数。

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

例如

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

声明了foo是一个可变参数函数模板,他有一个名为T的类型参数,和一个名为Args的模板参数包。这个包表示零个或多个额外的类型参数。foo的函数参数列表包含一个const&类型的参数,指向T的类型,还包含一个名为rest的函数参数包,此包表示零个或多个函数参数。

与往常一样,编译器从函数的实参推断模板参数类型。对于一个可变参数模板,编译器还会推断包中参数的数目。例如,给定下面的调用

int i = 0;double d = 3.14;string s = "how now brown cow";
foo(i, s, 42, d);			//包中有三个参数
foo(s, 42,"hi");			//包中有两个参数
foo(d, s);					//包中有一个参数
foo("hi");					//空包

编译器会为foo实例化出四个不同的版本

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

sizeof…运算符

当我们需要知道包中有多少个元素时,可以使用sizeof...运算符。sizeof...返回一个常量表达式,而且不会对其实参求值。

template<typename... Args>
void g(Args... args)
{
    cout << sizeof...(Args) << endl;	//类型参数的数目
    cout << sizeof...(args) << endl;	//函数参数的数目
}

编写可变参数函数模板

可以使用一个initializer_list定义一个可接受可变数目实参的函数。但是,所有实参必须具有相同的类型(或它们的类型可以转换为同一个公共类型)。当我们既不知道想要处理的实参的数目也不知道它们的类型时,可变参数函数是很有用的。

可变参数函数通常是递归的。第一步调用处理包中的第一个实参,然后用剩余实参调用自身。print函数是这样的模式,每次递归调用将第二个实参打印到第一个实参表示的流中。为了终止递归,还需要定义一个非可变参数的print函数,它接收一个流和一个对象:

//用来终止递归并打印最后一个元素的函数
//此函数必须在可变参数版本的print定义之前声明
template<typename T>
ostream &print(ostream& os, const T& t)
{
    return os << t;	
}
//包中除了最后一个元素之外的其他元素都会调用这个版本的print
template<typename T,typename... Args>
ostream& print(ostream& os, const T& t, const Args&... rest)
{
    os << t << ", ";				//打印第一个实参
    return print(os, rest...);		//递归调用,打印其他实参
}

第一个版本的print负责终止递归并打印初始调用中的最后一个实参。第二个版本的print是可变参数版本,它打印绑定到t的实参,并调用自身来打印函数参数包中的剩余值。

这段程序的关键部分是可变参数函数中对print的调用:

return print(os, rest...);		//递归调用,打印其他实参

我们的可变参数版本的print函数接受三个参数:一个是ostream&,一个const T&和一个参数包。而此调用值传递了两个实参。其结果是rest中的第一个实参被绑定到t,剩余实参形成下一个print调用的参数包。因此,在每个调用,包中的第一个实参被移除,称为绑定到t的实参。即,给定

print(count, i, s, 42);		//包中有两个参数

递归会执行如下:

调用trest…
print(count,i,s,42)is,42
print(count,s,42)s42
print(count,42)调用非可变参数版本的print

前两个调用只能与可变参数版本的print匹配,非可变参数版本不可行,因为这两个调用分别传递四个和三个实参,而非可变参数print只接受两个实参。

对于最后一次递归调用print(cout, 42),两个print版本都是可行的。但是,非可变参数模板比可变参数模板更特例化,因此编译器选择非可变参数版本。

包扩展

扩展一个包就是将它分解为构成的元素,对每个元素应用模式,获得扩展后的列表。我们通过在模式右边放一个省略号(...)来触发扩展操作。

例如,print函数包含两个扩展:

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

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

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

print(count,i,s,42);	//包中有两个参数

最后两个实参的类型和模式一起确定了尾置参数的类型。此调用被实例化为

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

第二个扩展发生在对print的递归调用。在此情况下,模式是函数参数包的名字()

理解包扩展

可以编写第二个可变参数函数,对其每个实参调用debug_rep,然后调用print打印结果string

//在print调用中对每个实参调用debug_rep
template<typename... Args>
ostream& errorMsg(ostream& os, const Args&... rest)
{
    //print(os, debug_rep(a1), debug_rep(a2), ..., debug_rep(an))
    return print(os, debug_rep(rest)...);
}

这个print调用使用了模式debug_rep(rest)。此模式表示我们希望对函数参数包rest中的每个元素调用debug_rep。扩展结果将是一个逗号分割的debug_rep调用列表。即,下面调用:

errorMsg(cerr, fcnName, code.num(), otherData, "other", item);

print(cerr, debug_rep(fcnName), debug_rep(code.num())
     		debug_rep(otherData), debug_rep("otherData").
     		debug_rep(item));

与之相对,下面的模式会编译失败

//将包传递给debug_rep;print(os, debug_rep(a1,a2,...,an))
print(os,debug_rep(rest...))//错误,此调用无匹配函数

这段代码的问题是我们在debug_rep调用中扩展了rest。它等价于

print(cerr, debug_rep(fcnName, code.num(), otherData, "other", item));

扩展中的模式会独立地应用于包中的每个元素

转发参数包

可以组合使用可变参数模板与forward机制来编写函数,实现将实参不变地传递给其他函数。

保持类型信息是一个两阶段的过程。首先,为了保持实参中的类型信息,必须将emplace_back的函数参数定义为模板类型参数的右值引用。

class StrVec{
public:
	template<class... Args>
    void emplace_back(Args&&...);
}

模板参数包扩展中的模式是&&,意味着每个函数参数将是一个指向其对应实参的右值引用。

其次,当emplace_back将这些实参传递给construct时,必须使用forward来保持实参的原始类型。

template<class... Args>
inline void StrVec::emplace_back(Args&&... args)
{
    chk_n_alloc();
    alloc.construct(first_free++, std::forward<Args>(args)...);
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值