天天看点

C++ 11中的可变模板参数

介绍C++11中的可变参数模板和一些相关的模板知识

目录

​​概述​​

​​前言​​

​可变模板参数的语法​

​​函数​​

​​类​​

​​非类型模板参数和可变模板参数结合​​

​函数参数包的展开​

​C++11中的展开方式​

​​递归展开​​

​​逗号表达式搭配initializer_list展开​​

​C++17中的折叠表达式​

​​一元折叠表达式​​

​​二元折叠表达式​​

​​如何评价​​

​类参数包的展开​

概述

大家在C++中应该见过不少函数,它们既没有限制参数的类型,也没有限制参数的个数,比如​<code>​vector&lt;T&gt;::emplace()​</code>​,​<code>​make_unique&lt;T&gt;()​</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++ 11中的可变模板参数

通过递归方式展开参数包,当所有参数包展开完毕后,自然为空,所以调用到非模板的递归中止函数

当然还可以使用模板递归中止函数,这种情况就不支持空参数包了

而在C++17中,对递归展开法进行了优化(前提是将​<code>​if​</code>​语句声明为常量表达式)

在C++11中通过递归展开参数包的缺点很明显,需要重载一个递归终止函数,同时还需要判定终止函数是否需要使用到模板;不仅如此,还需要确保带参数包版本的函数至少包含一个类型(​<code>​T fistst​</code>​),可以说是很不便了。下面介绍逗号表达式结合​<code>​initializer_list​</code>​的展开方法

这种搭配​<code>​initializer_list​</code>​的解法我愿称之为黑魔法,在构造初始化列表的同时完成了参数包的展开

以下示范一个求和模板函数,如果我们直接使用参数包进行操作而不展开它,那么我们将会得到报错

C++ 11中的可变模板参数

先来看看如何在C++17中利用折叠表达式​<code>​(...)​</code>​展开参数包实现一个求平均数的函数

对于一元折叠表达式而言,只有逗号运算符,​<code>​&amp;&amp;​</code>​和​<code>​||​</code>​操作允许空包,其它的如果出现空包则会编译出错

When a unary fold is used with a pack expansion of length zero, only the following operators are allowed: Logical AND (&amp;&amp;). 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>​,而不是每拆一次就输出一次

C++ 11中的可变模板参数
​​Fold expressions with arbitrary callable?​​

类参数包的展开

C++11中可以使用递归或逗号表达式来展开函数参数包,C++17中可以使用优化的递归或折叠表达式来展开。

C++11中的类参数包的展开需要运用到类模板的偏特化

此展开方式不支持0参数包,因此可以改写为以下方式

继续阅读