自C++11以来,模板就可以像普通的函数那样使用可变参数。这个特性的典型应用就是提供泛型代码处理任意数量任意类型的参数。而具有这种特性的模板被称为可变参数模板。
下面是一个简单的例子:
#include <iostream>
void print() { }
template <typename T, typename... Types>
void print(T firstArg, Types... args)
{
std::cout << firstArg << '\n'; // print first argument
print(args...); // call print() for remaining arguments
}
通过明确说明第一个参数,然后我们打印第一个参数,然后递归调用print(),将函数参数包(function parameter pack)中的参数一一打印。当参数包为空的时候,我们调用第一个不接受任何参数的print()结束递归。
当然也可换一个风格,重载可变参数模板和普通函数模板。
#include <iostream>
template <typename T>
void print(T arg)
{
std::cout << arg << '\n';
}
template <typename T, typename... Types>
void print(T firstArg, Types... args)
{
print(firstArg); // call print() for the first arguement
print(args...); // call print() for remaining argument
}
使用两个函数来打印参数从某方面来说显得冗长又繁琐。为了简化代码,可以使用C++11引进的sizeof... 操作符。关于其用法我们可以看一个简单的例子。
template <typename T, typename... Types>
void print(T firstArg, Types... args)
{
std::cout << sizeof...(Types) << '\n'; //print number of remaining types
std::cout << sizeof...(args) << '\n'; // print number of remaining types
}
我们可以对模板参数包(template parameter pack)或函数参数包(function parameter pack)使用sizeof... ,两者结果相同。
这个例子可能会引导我们丢弃那个结束递归的函数而写出这样的代码:
template <typename T, typename... Types>
void print(T firstArg, Types... args)
{
std::cout << firstArg << '\n';
if (sizeof...(args) > 0)
print(args...);
}
很遗憾,看似聪明的技巧实际上却不能通过编译。被实例化的代码是否有效是运行时决定的(run-time decision), 然而函数调用时的实例化却是编译时的事情(compile-time decision)。也就是说,if 语句是否执行是运行时的事情,而编译的时候每一次函数调用都会被实例化。因此,当我们调用print()打印最后一个参数时,print(args...)仍然会实例化,而我们并没有提供不接受任何参数的print()函数,那么就产生了错误。
但是,C++17提供的编译时 if 语句可以完美地解决这个问题。(篇幅原因,这里不介绍compile-time if statement 的原理)
template <typename T, typename... Types>
void print(T firstArg, Types... args)
{
std::cout << firstArg << '\n';
if constexpr(sizeof...(args) > 0)
print(args...); // code only available if sizeof...(args)>0 (since C++17)
}