可变参数模板和普通模板的语义是一样的,只是写法上稍有区别,声明可变参数模板时需要在typename或class后面带上省略号…:
1
   | template<typename... Types>
   | 
 
其中,…可接纳的模板参数个数是0个及以上的任意数量,需要注意包括0个。
若不希望产生模板参数个数为0的变长参数模板,则可以采用以下的定义:
1
   | template<typename Head, typename... Tail>
   | 
 
本质上,…可接纳的模板参数个数仍然是0个及以上的任意数量,但由于多了一个Head类型,由此该模板可以接纳1个及其以上的模板参数。
函数模板的使用
在函数模板中,可变参数模板最常见的使用场景是以递归的方法取出可用参数:
1 2 3 4 5 6 7
   | void print() {}
  template<typename T, typename... Types> void print(const T& firstArg, const Types&... args) { 	std::cout << firstArg << " " << sizeof...(args) << std::endl; 	print(args...); }
  | 
 
通过设置…,可以向print函数传递任意个数的参数,并且各个参数的类型也是任意。也就是说,可以允许模板参数接受任意多个不同类型的不同参数。这就是不定参数的模板,格外需要关注的是,…三次出现的位置。
如果如下调用print函数:
如此调用会递归将3个参数全部打印。细心的话会发现定义了一个空的print函数,这是因为当使用可变参数的模板,需要定义一个处理最后情况的函数,如果不写,会编译错误。这种递归的方式,是不是觉得很惊艳!
在不定参数的模板函数中,还可以通过如下方式获得args的参数个数:
假设,在上面代码的基础上再加上一个模板函数如下,那么运行的结果是什么呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
   | #include <iostream>
  void print() {}
  template<typename T, typename... Types> void print(const T& firstArg, const Types&... args) { 	std::cout << firstArg << " " << sizeof...(args) << std::endl; 	print(args...); }
  template <typename... Types> void print(const Types&... args) {   std::cout << "print(...)" << std::endl; }
  int main(int argc, char *argv[]) { 	print(2, "hello", 1);
  	return 0; }
   | 
 
现在有一个模板函数接纳一个参数加上可变参数,还有一个模板函数直接接纳可变参数,如果调用print(2, “hello”, 1),会发现这两个模板函数的参数格式都符合。是否会出现冲突、不冲突的话会执行哪一个呢?
运行代码后的结果为:
从结果上可以看出,程序最终选择了一个参数加上不定参数的模板函数。也就是说,当较泛化和较特化的模板函数同时存在的时候,最终程序会执行较特化的那一个。
再比如一个例子,std::max函数只可以返回两个数的较大者,如果多个数,就可以通过不定参数的模板来实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   | #include <iostream>
  template <typename T> T my_max(T value) {   return value; }
  template <typename T, typename... Types> T my_max(T value, Types... args) {   return std::max(value, my_max(args...)); }
  int main(int argc, char *argv[]) {   std::cout << my_max(1, 5, 8, 4, 6) << std::endl;
  	return 0; }
   | 
 
类模板的使用
除了函数模板的使用外,类模板也可以使用不定参数的模板参数,最典型的就是tuple类了。其大致代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
   | #include <iostream>
  template<typename... Values> class tuple; template<> class tuple<> {};
  template<typename Head, typename... Tail> class tuple<Head, Tail...>   : private tuple<Tail...> {   typedef tuple<Tail...> inherited;   public:     tuple() {}     tuple(Head v, Tail... vtail) : m_head(v), inherited(vtail...) {}     Head& head() {return m_head;}     inherited& tail() {return *this;}   protected:     Head m_head; };
  int main(int argc, char *argv[]) { 	tuple<int, float, std::string> t(1, 2.3, "hello"); 	std::cout << t.head() << " " << t.tail().head() << " " << t.tail().tail().head() << std::endl;
  	return 0; }
   | 
 
根据代码可以知道,tuple类继承除首之外的其他参数的子tuple类,以此类推,最终继承空参数的tuple类。继承关系可以表述为:
1 2 3 4 5 6 7 8 9 10
   | tuple<>       ↑ tuple<std::string>   string "hello"       ↑ tuple<float, std::string>   float 2.3       ↑ tuple<int, float, std::string>   int 1
   | 
 
接下来考虑在内存中的分布,内存中先存储父类的变量成员,再保存子类的变量成员,也就是说,对象t按照内存分布来说;
1 2 3 4 5 6 7
   | ┌─────────┐<---- 对象指针 |  hello  | |─────────| |  2.3    | |─────────| |  1      | └─────────┘
   | 
 
这时候就可以知道下一句代码的含义了:
1
   | inherited& tail() {return *this;}
  | 
 
tail()函数返回的是父类对象,父类对象和子类对象的内存起始地址其实是一样的,因此返回*this,再强行转化为inherited类型。
当然,上面采用的是递归继承的方式,除此之外,还可以采用递归复合的方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
   | template<typename... Values> class tup; template<> class tup<> {};
  template<typename Head, typename... Tail> class tup<Head, Tail...> {   typedef tup<Tail...> composited;   public:     tup() {}     tup(Head v, Tail... vtail) : m_head(v), m_tail(vtail...) {}     Head& head() {return m_head;}     composited& tail() {return m_tail;}   protected:     Head m_head;     composited m_tail; };
   | 
 
两种方式在使用的过程中没有什么区别,但C++11中采用的是第一种方式(递归继承)。
在上面的例子中,取出tuple中的元素是一个比较复杂的操作,需要不断地取tail,最终取head才能获得。标准库的std::tuple,对此进行简化,还提供了一些其他的函数来进行对tuple的访问。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
   | #include <iostream> #include <tuple>
  int main(int argc, char *argv[]) {   std::tuple<int, float, std::string> t2(1, 2.3, "hello");   std::get<0>(t2) = 4;                         std::cout << std::get<0>(t2) << " " << std::get<1>(t2) << " " << std::get<2>(t2) << std::endl;    
    auto t3 = std::make_tuple(2, 3.4, "World");               std::cout << std::tuple_size<decltype(t3)>::value << std::endl;       std::tuple_element<1, decltype(t3)>::type f = 1.2;          
  	return 0; }
   | 
 
参考:https://blog.csdn.net/qq_38410730/article/details/105247065
C++标准库元组(tuple)源码浅析