C++17新特性:使用折叠表达式实现辅助函数

简介

自C++11起,加入了变长模板参数包,能让函数结构任意数量的参数。有时,这些参数都组合成一个表达式,从中得 出函数结果。C++17中使用折叠表达式,可以让这项任务变得更加简单。
首先,实现一个函数,用于将所有参数进行累加:
1.声明该函数:

template <typename ... Ts> auto sum(Ts ... ts);

2.那么现在我们拥有一个参数包ts ,并且函数必须将参数包展开,然后使用表达式进行求和。如果我们对这些参数进行某个操作(比如:加法),那么为了将这个操作应用于该参数包,就需要使用括号将表达式包围:

template<typename ... Ts> auto sum(Ts ... ts){
return (ts + ...);
}

3.现在我们可以调用这个函数:

int the_sum {sum(1, 2, 3, 4, 5)}; // value: 15

4.这个操作不仅对int 类型起作用,我们能对任何支持加号的类型使用这个函数,比如std::string :

std::string a{"Hello "};
std::string b{"World"};


std::cout << sum(a, b) << '\n'; // output: Hello World

工作流

这里只是简单的对参数集进行简单的递归,然后应用二元操作符+ 将每个参数加在一起。这称为折叠操作。C++17中添加了折叠表达式,其能用更少的代码量,达到相同的结果。
其中有种称为一元折叠的表达式。C++17中的折叠参数包支持如下二元操作符: + - * / % ^ & | = < > <<

这样的话,在我们的例子中表达式(ts+…) 和(…+ts) 等价。不过,对于某些其他的例子,这就所有不同了—— 当… 在操作符右侧时,称为有“右折叠”;当… 在操作符左侧时,称为”左折叠“。
我们sum例子中,一元左折叠的扩展表达式为 1+(2+(3+(4+5))) ,一元右折叠的扩展表达式为
(((1+2)+3)+4)+5 。根据操作符的使用,我们就能看出差别。当用来进行整数相加,那么就没有区别。

扩展

如果在调用sum函数的时候没有传入参数,那么可变参数包中就没有可以被折叠的参数。对于大多数操作来说,这将 导致错误(对于一些例子来说,可能会是另外一种情况,我们后面就能看到)。这时我们就需要决定,这时一个错误, 还是返回一个特定的值。如果是特定值,显而易见应该是0。
如何返回一个特定值:

template <typenme ... Ts> auto sume(Ts ... ts){
return (ts + ... + 0);
}

sum() 会返回0, sum(1, 2, 3) 返回(1+(2+(3+0))) 。这样具有初始值的折叠表达式称为二元折叠。
当我们写成(ts + … + 0) 或(0 + … + ts) 时,不同的写法就会让二元折叠表达式处于不同的位置(二元右折叠或二元左折叠)。下图可能更有助于理解左右二元折叠:

为了应对无参数传入的情况,我们使用二元折叠表达式,这里标识元素这个概念很重要——本例中,将0加到其他数 字上不会有任何改变,那么0就一个标识元素。因为有这个属性,对于加减操作来说,可以将0添加入任何一个折叠表 达式,当参数包中没有任何参数时,我们将返回0。从数学的角度来看,这没问题。但从工程的角度,我们需要根据 我们需求,定义什么是正确的。
同样的原理也适用于乘法。这里,标识元素为1:

product(2, 3) 的结果是6, product() 的结果是1。
逻辑操作符and(&&) 和or(||) 具有内置的标识元素。&& 操作符为true, || 操作符为false。对于逗号表达式来说,其标识元素为void() 。
为了更好的理解这特性,让我们可以使用这个特性来实现的辅助函数。匹配范围内的单个元素
如何告诉函数在一定范围内,我们提供的可变参数至少包含一个值:

辅助函数中使用STL中的std::count 函数。这个函数需要三个参数:前两个参数定义了迭代器所要遍历的范围,第三个参数则用于与范围内的元素进行比较。 std::count 函数会返回范围内与第三个参数相同元素的个数。
在我们的折叠表达式中,我们也会将开始和结束迭代器作为确定范围的参数传入std::count 函数。不过,对于第三个参数,我们将会每次从参数包中放入一个不同参数。最后,函数会将结果相加返回给调用者。
可以这样使用:

如我们所见, matches 辅助函数十分灵活——可以直接传入vector 或string 直接调用。其对于初始化列表也同样适用,也适用于std::list , std::array , std::set 等STL容器的实例。
检查集合中的多个插入操作是否成功
我们完成了一个辅助函数,用于将任意数量参数插入std::set 实例中,并且返回是否所有插入操作都成功完成:

那么这个函数如何工作呢? std::set 的insert 成员函数声明如下:

手册上所述,当我们使用insert 函数插入一个元素时,该函数会使用一个包含一个迭代器和一个布尔值的组对作为返回值。当该操作成功,那么迭代器指向的就是新元素在set 实例中的位置。否则,迭代器指向某个已经存在的元素,这个元素与插入项有冲突。
我们的辅助函数在完成插入后,会访问.second 区域,这里的布尔值反映了插入操作成功与否。如果所有插入操作都为true,那么都是成功的。折叠标识使用逻辑操作符 && 链接所有插入结果的状态,并且返回计算之后的结果。
可以这样使用它:

需要注意的是,当在插入3个元素时,第2个元素没有插入成功,那么&& 会根据短路特性,终止插入剩余元素:

检查所有参数是否在范围内
当要检查多个变量是否在某个范围内时,可以多次使用查找单个变量是否在某个范围的方式。这里我们可以使用折叠 表达式进行表示:

表达式(min <= ts && ts <= max) 将会告诉调用者参数包中的每一个元素是否在这个范围内。我们使用&& 操作符对每次的结果进行处理,从而返回最终的结果。
如何使用这个辅助函数:

这个函数也是很灵活的,其只需要传入的参数类型可以进行比较,且支持<= 操作符即可。并且该规则对于
std::string 都是适用的:

将多个元素推入vector中
可以编写一个辅助函数,不会减少任何结果,又能同时处理同一类的多个操作。比如向std::vector 传入元素:

需要注意的是,使用了逗号操作符将参数包展开,然后推入vector中。该函数也不惧空参数包,因为逗号表达式具有 隐式标识元素, void() 可以翻译为什么都没做。

猜你喜欢

转载自blog.csdn.net/EBDSoftware/article/details/128483322