今天我们讲一个特殊的专题,如何使用可变模板和 tuple 来完成一些常见的功能,尤其是编译期计算。
可变模板
可变模板 [1] 是 C++11 引入的一项新功能,使我们可以在模板参数里表达不定个数和类型的参数。从实际的角度,它有两个明显的用途:
用于在通用工具模板中转发参数到另外一个函数
用于在递归的模板中表达通用的情况(另外会有至少一个模板特化来表达边界情况)
我们下面就来分开讨论一下。
转发用法
以标准库里的 make_unique 为例,它的定义差不多是下面这个样子:
template <typename T,
typename... Args>
inline unique_ptr<T>
make_unique(Args&&... args)
{
return unique_ptr<T>(
new T(forward<Args>(args)...));
}
这样,它就可以把传递给自己的全部参数转发到模板参数类的构造函数上去。注意,在这种情况下,我们通常会使用 std::forward,确保参数转发时仍然保持正确的左值或右值引用类型。
稍微解释一下上面三处出现的 ...:
typename... Args 声明了一系列的类型——class... 或 typename... 表示后面的标识符代表了一系列的类型。
Args&&... args 声明了一系列的形参 args,其类型是 Args&&。
forward<Args>(args)... 会在编译时实际逐项展开 Args 和 args ,参数有多少项,展开后就是多少项。
举一个例子,如果我们需要在堆上传递一个 vector<int>,假设我们希望初始构造的大小为 100,每个元素都是 1,那我们可以这样写:
make_unique<vector<int>>(100, 1)
模板实例化之后,会得到相当于下面的代码:
template <>
inline unique_ptr<vector<int>>
make_unique(int&& arg1, int&& arg2)
{
return unique_ptr<vector<int>>(
new vector<int>(
forward<int>(arg1),
forward<int>(arg2)));
}
如前所述,forward<Args>(args)... 为每一项可变模板参数都以同样的形式展开。项数也允许为零,那样,我们在调用构造函数时也同样没有任何参数。
递归用法
我们也可以用可变模板来实现编译期递归。下面就是个小例子:
template <typename T>
constexpr auto sum(T x)
{
return x;
}
template <typename T1, typename T2,
typename... Targ>
constexpr auto sum(T1 x, T2 y,
Targ... args)
{
return sum(x + y, args...);
}
在上面的定义里,如果 sum 得到的参数只有一个,会走到上面那个重载。如果有两个或更多参数,编译器就会选择下面那个重载,执行一次加法,随后你的参数数量就少了一个,因而递归总会终止到上面那个重载,结束计算。
要使用上面这个模板,我们就可以写出像下面这样的函数调用:
auto result = sum(1, 2, 3.5, x);
模板会这样依次展开:
sum(1 + 2, 3.5, x)
sum(3 + 3.5, x)
sum(6.5 + x)
6.5 + x