变参模板

Posted by persuez on February 28, 2019

C++ 11 的一个新特性是用关键字...提供了变参模板,这将可以很好地帮助我们写递归,如C++ 11 新增的 tuple 类就是使用了变参模板。其中...可以出现的位置有三个,先看一个例子:


基础

void print()
{
  // 什么都不做
}
template  <typename T, typename... Types>
void print(const T& firstArg, const Types&... args)
{
  cout <<  firstArg  <<  endl;
  print(args...);
}

如果调用print(7.5, "hello", bitset<16>(377), 42)将得到以下结果:

7.5
hello
0000000101111001
42

现在先说明...出现的三个位置,从上往下:

  1. template parameters
  2. function parameters Types
  3. function parameters

现在来理解这段代码: ...我们可以理解为一包(pack)参数,那么 template parameters 就可以理解为定义了一包模板参数,这包参数的类型可以不相同; function parameters 和前面的 firstArg 参数可以理解为一个参数 + 一包类型可变的参数;function parameters 可以理解为一包参数。那么 print 函数先输出了第一个参数 firstArg,然后调用 print 自身,参数为 args...,这时候args...又可以解释为一个参数 + 一包其余的参数,然后递归调用void print(const T& firstArg, const Types&... args)函数,到最后第二步时,args… 这包参数中就剩下 1 个参数,这时依然解释为一个参数 + 一包其余的参数,只不过这一包剩余的参数的数量为 0,所以当输出 firstArg 后,这时调用的 print 函数是void print(),这也是为什么需要一个参数为空的 print 函数。因为此时 sizeof...(args) == 0(sizeof… 运算符可以求出 args 这包参数的具体数量)。


泛化与特化

先说以下两个函数能否共存:

template <typename Types...>
void print(const Types&... args);

template  <typename T, typename... Types>
void print(const T& firstArg, const Types&... args);

答案是能够共存,那么如果调用print(7.5, "hello", bitset<16>(377), 42)将会调用哪个呢,答案是下面那个,因为它相较于上面那个更加特化。我的理解是参数类型越具体表示越特化。

递归继承

标准库中的 tuple 就是使用了递归继承,借用侯捷老师的 PPT,一图以蔽之: tuple 继承图