要理解可变参数模板的实现,需要先理解几个概念。
- 参数包(包括模板参数包和函数参数包)
- 展开参数包
- 递归
template<typename T>
void print(T value)
{}
在上面的代码中,有两个参数列表。模板参数列表只包含T,函数参数列表只包含value,这里要说明的是模板参数列表是类型列表,函数参数列表是值列表。C++11提供了一个用省略号表示的元运算符,来声明参数包的标识符,如下。
template<typename... Args>
void print(Args... args)
{}
其中,Args是一个模板参数包,args是一个函数参数包,举个例子。
print(1, 2, 'a', "hello", 3.4);
当执行上面的调用时,模板参数包Args包含类型int、int、char、const char*、double,函数参数包args包含值1、2、‘a’、“hello”、3.4。
在函数中如何访问这些包中的内容呢?这些包并不是数组,所以不能像数组那样用下标访问,而是以展开包的方式访问(将省略号放在函数包的右边)。
template<typename... Args>
void print(Args... args)
{
print(args...);
}
到这里,可变参数模板的实现只差一步了。可以发现上面的代码在被调用时进入了无限递归。解决办法是,将函数参数包展开,对列表中的第一项进行处理,再将余下的内容传递给递归调用,直至列表为空,新的定义如下。
template<typename T, typename... Args>
void print(T value, Args... args)
{
cout << value << " ";
print(args...);
}
上面的内容基本已经实现了可变参数模板。往往为了提高程序效率,不以值方式接收参数而是以引用方式接收参数。另外,对于多个参数而言,往往会对最后一个参数作特别的处理,同时,要考虑到没有参数传入的情况,最终的版本如下。
void print()
{}
template<typename T>
void print(const T& value)
{
cout << value << endl;
}
template<typename T, typename... Args>
void print(T value, const Args&... args)
{
cout << value << " ";
print(args...);
}
当然,如果涉及到右值引用的问题,需要用到std::forward来保持右值引用类型的不变性。