查看原文
其他

C++17 折叠表达式

CPP开发者 2021-06-06

(给CPP开发者加星标,提升C/C++技能)

来源:地球在动
https://blog.csdn.net/ding_yingzi/article/details/79973809

【导读】:C++11 提供了可变模板参数包, 使函数可以接受任意数量的参数. 但在 C++11中展开参数包稍显麻烦, 而 C++17 的折叠表达式使得展开参数包变得容易,

其基本语法是使用 (...) 的语法形式进行展开。


以下是正文


支持的操作符


折叠表达式支持 32 个操作符:

 +, -, *, /, %, ^, &, |, =, <,
>, <<, >>, +=, -=, *=, /=, %=, ^=, &=, |=, <<=,
>>=,==, !=, <=, >=, &&, ||, ,, .*, ->*.


折叠分类


C++17 的折叠表达式根据参数包的位置分为左折叠和右折叠,根据操作的对象数量分为一元折叠和二元折叠。


例 1: 左折叠

template <typename ... Ts>auto sum(Ts ... ts){ return (... + ts);}
int the_sum {sum(1, 2, 3, 4, 5)}; // the_sum 的值为 15
std::string a {"Hello "};std::string b {"World"};std::string the_string {sum(a, b)}; // the_string 的值为 "Hello World"

例 2: 右折叠

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

例 1 中, 参数包 ... 位于操作符的左侧,故尔称为左折叠。 如果 ...位于操作符右侧,则称为右折叠,如例 2 所示。就例 1 与例 2 而言,左折叠与右折叠的效果是相同的。


对于 

int the_sum {sum(1, 2, 3, 4, 5)};

左折叠的展开方式为

1 + (2 + (3 + (4 + 5))),

右折叠的展开方式为

(((1 + 2) + 3) + 4) + 5

在例 1 与 例 2 中,如果参数包包含的参数数量为 0,即为空包,会产生编译错误,如

int the_sum {sum()};

 大致的错误输出如下

In instantiation of 'auto sum(Ts ...) [with Ts = {}]':error: fold of empty expansion over operator+ return (... + ts);

若要解决空参数包的编译错误,针对例 1,可以加上一个数值 0,可以解决编译错误又可以使得语义不变,这个 0 相当于缺省值。通过加上一个数值,折叠就变成了二元折叠,如例 3 所示。


例 3: 二元折叠

template <typename ... Ts>auto sum(Ts ... ts){ // 二元右折叠 return (ts + ... + 0);
// 二元左折叠 // return (0 + ... + ts);}

此时对于 

int the_sum {sum(1, 2, 3, 4, 5)};

折叠的展开方式为 

1 + (2 + (3 + (4 + (5 + 0))))

空参数包


空参数包就是参数包中不含任何参数。对于大多数操作符,空参数包将会引发编译错误。对于 && 或 ||,空参数包是合法的,其中 && 的展开结果为 true,||
的展开结果为 false。在逗号 , 操作符中,空参数包也合法,展开为 void()。


其它例子


例 4: 计算指定区间内包含指定数值的个数

template <typename R, typename ... Ts>auto count(const R& range, Ts ... ts){ return (std::count(std::begin(range), std::end(range), ts) + ...);}
...
std::vector<int> v {1, 2, 3, 4, 5};count(v, 2, 5); // returns 2count(v, 100, 200); // returns 0count("abcdefg", 'x', 'y', 'z'); // returns 0count("abcdefg", 'a', 'd', 'f'); // returns 3

例 5: 检查插入多个元素是否成功

template <typename T, typename ... Ts>bool insert_all(T &set, Ts ... ts){ return (set.insert(ts).second && ...);}
...
std::set<int> my_set {1, 2, 3};insert_all(my_set, 4, 5, 6); // Returns true, my_set 值为 {1, 2, 3, 4, 5, 6}insert_all(my_set, 7, 2, 8); // Returns false, my_set 值为 {1, 2, 3, 4, 5, 6, 7} // 插入 2 时出错, 8 不会被插入

最后


1. 对于一元右折叠 (E op …) 具体展开为 E1 op (… op (EN-1 op EN))。


2. 对于一元左折叠 (… op E) 具体展开为 (( E1 op E2) op …) op En。


3. 对于二元右折叠 (E op … op I) 具体展开为 E1 op (… op (EN-1 op (EN op I)))。


4. 对于二元左折叠 (I op … op E) 具体展开为 (((I op E1) op E2) op …) op E2。


左折叠与右折叠的语义并非总是相同的。比如对于加法和乘法,左折叠与右折叠的语义是相同的,但是对于减法与除法,其语义是不同的。


例 6: 左右折叠不同语义

template<typename... Args>auto left_sub(Args&&... args) { return (... - args);}
template<typename... Args>auto right_sub(Args&&... args) { return (args - ...);}
...
auto a = left_sub(2, 3, 4); // ((2 - ) -3 ) - 4) = -5auto b = right_sub(2, 3, 4); // (2 - (3 - 4)) = 3


- EOF -


推荐阅读  点击标题可跳转

1、C++ 元编程之求解全局最短路径

2、C++ 高阶操作:模板元编程

3、C++20 协程初探


关于 C++17 折叠表达式,欢迎在评论中和我探讨。觉得文章不错,请点赞和在看支持我继续分享好文。谢谢!


关注『CPP开发者』

看精选C++技术文章 . 加C++开发者专属圈子

↓↓↓


点赞和在看就是最大的支持❤️

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存