相关文章推荐
不羁的手链  ·  浅析Docker运行安全 - ...·  1 年前    · 
难过的炒饭  ·  protobuf - 简书·  1 年前    · 

在C++11之前,类模板和函数模板只能含有固定数量的模板参数。C++11增强了模板功能,允许模板定义中包含0到任意个模板参数,这就是可变参数模板。可变参数模板的加入使得C++11的功能变得更加强大,而由此也带来了许多神奇的用法。

可变参数模板

可变参数模板和普通模板的语义是一样的,只是写法上稍有区别,声明可变参数模板时需要在 typename class 后面带上省略号 ...

template<typename... Types>

其中,...可接纳的模板参数个数是0个及以上的任意数量,需要注意包括0个

若不希望产生模板参数个数为0的变长参数模板,则可以采用以下的定义:

template<typename Head, typename... Tail>

本质上,...可接纳的模板参数个数仍然是0个及以上的任意数量,但由于多了一个Head类型,由此该模板可以接纳1个及其以上的模板参数

函数模板的使用

在函数模板中,可变参数模板最常见的使用场景是以递归的方法取出可用参数

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函数:

print(2, "hello", 1);

如此调用会递归将3个参数全部打印。细心的话会发现定义了一个空的print函数,这是因为当使用可变参数的模板,需要定义一个处理最后情况的函数,如果不写,会编译错误。这种递归的方式,是不是觉得很惊艳!

在不定参数的模板函数中,还可以通过如下方式获得args的参数个数:

sizeof...(args)

假设,在上面代码的基础上再加上一个模板函数如下,那么运行的结果是什么呢?

#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),会发现这两个模板函数的参数格式都符合。是否会出现冲突、不冲突的话会执行哪一个呢?

运行代码后的结果为:

yngzmiao@yngzmiao-virtual-machine:~/test/$ ./main 
hello 1

从结果上可以看出,程序最终选择了一个参数加上不定参数的模板函数。也就是说,当较泛化和较特化的模板函数同时存在的时候,最终程序会执行较特化的那一个

再比如一个例子,std::max函数只可以返回两个数的较大者,如果多个数,就可以通过不定参数的模板来实现:

#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类了。其大致代码如下:

#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类。继承关系可以表述为:

tuple<>
tuple<std::string>
  string "hello"
tuple<float, std::string>
  float 2.3
tuple<int, float, std::string>
  int 1

接下来考虑在内存中的分布,内存中先存储父类的变量成员,再保存子类的变量成员,也就是说,对象t按照内存分布来说;

┌─────────┐<---- 对象指针
|  hello  |
|─────────|
|  2.3    |
|─────────|
|  1      |
└─────────┘

这时候就可以知道下一句代码的含义了:

inherited& tail() {return *this;}

tail()函数返回的是父类对象,父类对象和子类对象的内存起始地址其实是一样的,因此返回*this,再强行转化为inherited类型。

当然,上面采用的是递归继承的方式,除此之外,还可以采用递归复合的方式:

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的访问。例如:

#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;                      // 修改tuple内的元素
  std::cout << std::get<0>(t2) << " " << std::get<1>(t2) << " " << std::get<2>(t2) << std::endl;    // 获取tuple内的元素
  auto t3 = std::make_tuple(2, 3.4, "World");         // make方法生成tuple对象
  std::cout << std::tuple_size<decltype(t3)>::value << std::endl;    // 获取tuple对象元素的个数
  std::tuple_element<1, decltype(t3)>::type f = 1.2;          // 获取tuple对象某元素的类型
	return 0;

                                    可变参数模板C++11新增的最强大的特性之一,它对参数高度泛化,能够让我们创建可以接受可变参数函数模板类模板。在C++11之前,类模板函数模板中只能包含固定数量的模板参数,可变模板参数无疑是一个巨大的改进,但由于可变参数模板比较抽象,因此使用起来需要一定的技巧。在C++11之前其实也有可变参数的概念,比如printf函数就能够接收任意多个参数,但这是函数参数的可变参数,并不是模板可变参数可变参数模板普通模板只可以采取固定数量的模板参数。然而,有时候我们希望模板可以接收任意数量的模板参数,这个时候可以采用可变参数模板。对于可变参数模板,其将包含至少一个模板参数包,模板参数包是可以接收0个或者多个参数的模板参数。相应地,存在函数参数包,意味着这个函数参数可以接收任意数量的参数。使用规则一个可变参数类模板定义如下:template<typename ... Types>
class Tu
                                    以下就是一个基本可变参数函数模板.//Args是一个模板参数包,args是一个函数形参参数包. //声明一个函数形参参数包Args...args,其中这个形参参数包中可以包含0到任意个模板参数. template < class . . . Args > void ShouList(Args . . . args) {注意上面的参数args前面有省略号,所以它就是一个可变模板参数,我们将带省略号的参数称为"参数包",它里面包含了0到N个(N>>0)个模板参数.
                                    递归终止函数的参数可以为0,1,2或者多个(一般用到0个或1个),当参数包中剩余的参数个数等于递归终止函数的参数个数时,就调用递归终止函数,则函数终止。在c++11之前,类模板函数模板只能含有固定数量的模板参数,c++11增加了可变模板参数特性允许模板定义中包含0到任意个模板参数。有两种展开参数包的方法(1)通过递归的模板函数来展开参数包;这种展开参数包的方式,不需要通过递归终止函数,而是直接在expand函数体内展开,printarg不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。...
                                    由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包。我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。的函数模板类模板,相比C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。
                                    可变参数模板模板编程时,模板参数(template parameter)的个数可变的情形,可变模板参数(variadic templates)是C++ 11新增的最强大的特性之一,它对参数进行高度泛化,它能表示0到任意个数、任意类型的参数。/*用宏的方式(/*用宏的方式)/*用宏的方式(/*用宏的方式,/*用宏的方式)/*用宏的方式+/*用宏的方式(/*用宏的方式,/*用宏的方式,/*用宏的方式)/*用宏的方式+/*用宏的方式add_2(/*用宏的方式。