介绍C++11中的可变参数模板和一些相关的模板知识
目录
概述
前言
可变模板参数的语法
函数
类
非类型模板参数和可变模板参数结合
函数参数包的展开
C++11中的展开方式
递归展开
逗号表达式搭配initializer_list展开
C++17中的折叠表达式
一元折叠表达式
二元折叠表达式
如何评价
类参数包的展开
概述
大家在C++中应该见过不少函数,它们既没有限制参数的类型,也没有限制参数的个数,比如<code>vector<T>::emplace()</code>,<code>make_unique<T>()</code>。它们都是利用C++11中的可变模板参数来实现的。对于这一新特性,需要掌握以下三点
可变模板参数的语法
参数包的展开
实践
前言
在讲可变模板参数之前,需要先讲C语言中的变长参数
C语言中的<code>va_arg</code>宏并不能判断出哪个参数参数包的末尾,所以只能通过自己设定结束位,并通过显式判断来截取有效的参数
而在C++11中,使用变长参数更简单了(好吧其实也不怎么算变长)
以C++11的标准来看,声明一个可变参数的模板函数有两种方法
函数参数包的展开
假设我们通过设计一个函数,能逐个输出它的参数
以上代码的递归过程为
<code>print(1, 2, "Mike", 3.21);</code>
<code>print(2, "Mike", 3.21);</code>
<code>print("Mike", 3.21);</code>
<code>print(3.21);</code>
<code>print();</code>

通过递归方式展开参数包,当所有参数包展开完毕后,自然为空,所以调用到非模板的递归中止函数
当然还可以使用模板递归中止函数,这种情况就不支持空参数包了
而在C++17中,对递归展开法进行了优化(前提是将<code>if</code>语句声明为常量表达式)
在C++11中通过递归展开参数包的缺点很明显,需要重载一个递归终止函数,同时还需要判定终止函数是否需要使用到模板;不仅如此,还需要确保带参数包版本的函数至少包含一个类型(<code>T fistst</code>),可以说是很不便了。下面介绍逗号表达式结合<code>initializer_list</code>的展开方法
这种搭配<code>initializer_list</code>的解法我愿称之为黑魔法,在构造初始化列表的同时完成了参数包的展开
以下示范一个求和模板函数,如果我们直接使用参数包进行操作而不展开它,那么我们将会得到报错
先来看看如何在C++17中利用折叠表达式<code>(...)</code>展开参数包实现一个求平均数的函数
对于一元折叠表达式而言,只有逗号运算符,<code>&&</code>和<code>||</code>操作允许空包,其它的如果出现空包则会编译出错
When a unary fold is used with a pack expansion of length zero, only the following operators are allowed: Logical AND (&&). The value for the empty pack is true Logical OR (||). The value for the empty pack is false The comma operator (,). The value for the empty pack is void()
二元折叠表达式,支持空包操作
对于<code>std::cout</code>的二元表达式而言,只能使用左折叠(因为输出必须以<code>std::cout</code>开头,而这也就代表了它是左折叠)
拓展:以下代码是在参数包展开完毕之后再输出<code>std::endl</code>,而不是每拆一次就输出一次
Fold expressions with arbitrary callable?
类参数包的展开
C++11中可以使用递归或逗号表达式来展开函数参数包,C++17中可以使用优化的递归或折叠表达式来展开。
C++11中的类参数包的展开需要运用到类模板的偏特化
此展开方式不支持0参数包,因此可以改写为以下方式