C++17编程之 折叠表达式


当在C++17中使用模板或变参模板时,您可能需要将模板参数包中的多个值组合为单个值,例如求和、平均值、最大值等等。在这种情况下,您可以使用C++17中引入的折叠表达式。

折叠表达式是一种新的语言特性,它使得可以对包含若干个参数的可变参数模板进行操作,同时将其所有参数合并成一个值。这个新特性的语法形式是((pack op)… ),其中pack是一个参数包,op是一个二元操作符。这个语法形式可以用于多种操作符,例如加、乘、逗号等。 例如,如果您有一个求和函数模板,可以像这样定义它:

template<typename... Args>   
auto sum(Args... args) {
    return (args + ...); 
} 

在这里,表达式 (args + …) 称为折叠表达式,其中+是一个二元操作符。这个函数将参数包args中的所有参数相加,并返回它们的总和。 您可以使用折叠表达式在变量模板中计算一个参数包的大小,例如:

template<typename... Args> 
constexpr std::size_t size_of_pack = (sizeof(Args) + ...); 

这个变量模板可以用来计算参数包args中所有类型的大小之和,并将其作为一个std::size_t类型的常量返回。

语法形式

折叠表达式共有四种语法形式,分别为一元的左折叠和右折叠,以及二元的左折叠和右折叠。

一元左折叠(unary left fold)
( ... op pack )
一元左折叠(... op E)展开之后变为 ((E1 op E2) op ...) op En

一元右折叠(unary right fold)
( pack op ... )
一元右折叠(E op ...)展开之后变为 E1 op (... op (EN-1 op En))

二元左折叠(binary left fold) 
( init op ... op pack )
二元左折叠(I op ... op E)展开之后变为 (((I op E1) op E2) op ...) op En

二元右折叠(binary right fold) 
( pack op ... op init )
二元右折叠(E op ... op I)展开之后变为 E1 op (... op (EN1 op (EN op I)))

(1)语法形式中的op代表运算符,pack代表参数包,init代表初始值。
(2)不指定初始值的为一元折叠表达式,而指定初始值的为二元折叠表达式。
(3)初始值在右边的为右折叠,展开之后从右边开始折叠。而初始值在左边的为左折叠,展开之后从左边开始折叠。
(4)当一元折叠表达式中的参数包为空时,只有三个运算符(&& || 以及逗号)有缺省值,其中&&的缺省值为true,||的缺省值为false,逗号的缺省值为void()。
fold expression支持32种操作符:

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

使用实例

(1) 一元右折叠

从表达式右边开始fold,看它是left fold还是right fold我们可以根据参数包和…所在的位置来判断,当…在参数包args右边的时候就是right fold,示例如下:

template<typename... Args>
auto add_val(Args&&... args) 
{
    return (args + ...);
}
auto t = add_val(1,2,3,4); // return 1 + (2 + (3 + 4))

(2) 一元左折叠

当…在参数包args左边的时候就是left fold

template<typename... Args>
auto add_val(Args&&... args) 
{
    return (... + args);
}
auto t = add_val(1,2,3,4); // return ((1 + 2) + 3) + 4

注意事项:虽然对于操作符 ‘+’ 来讲,一元左折叠和一元右折叠结果一样。不过,并不是所有的操作符都这样,因为它们的结合顺序和优先级可能会影响结果。比如,如果你用减法或除法作为一元右折叠的操作符,那么你得到的结果可能和你预期的不一样。
例如,下面的代码定义了一个函数模板,分别使用一元左折叠和一元右折叠来计算参数包中所有元素的差:

template <typename ... Args >
auto right_sub_val(Args&&... args) {
  return (args - ...); // 一元右折叠
}

template <typename ... Args >
auto left_sub_val(Args&&... args) {
  return (... - args); // 一元左折叠
}

auto right_result = right_sub_val(1,2,3,4);  // 1 - (2 - (3 - 4)) = -2
auto left_reust = left_sub_val(1,2,3,4);	// ((1 - 2) - 3) - 4 =  -8

可以看出对于 ‘-’ 操作符 的一元右折叠 和 一元左折叠的结果就不一样了。

(3) 二元右折叠

二元右折叠是从右到左依次对参数包中的每个元素和一个初始值或者后一个结果进行运算,例如:(pack op … op init)。当参数包args在最左边,初始值在最右边的时候,就是二元右折叠。

template<typename... Args>
auto sub_right(Args&&... args)
{
    return (args - ... - 0);
}
auto t = sub_right(123);	//1 - (2 - (3 - 0)) = 2 

(4) 二元左折叠

二元左折叠是从左到右依次对参数包中的每个元素和一个初始值或者前一个结果进行运算,例如:(init op … op pack)。当初始值在最左边,参数包args在最右边的时候,就是二元左折叠。

template<typename... Args>
auto sub_left(Args&&... args) 
{
    return (0 - ... - args);
}
auto t = sub_left(123);// ((0 - 1) - 2) - 3 = -6
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值