当前位置: 首页 > 工具软件 > ListFold > 使用案例 >

C++17 fold expression

苗阳
2023-12-01

1.简介

C++11 增加了一个新特性变参模板(variadic template),它可以接受任意个模版参数,参数包不能直接展开,需要通过一些特殊的方法,比如函数参数包的展开可以使用递归方式或逗号表达式,在使用的时候有点难度。

C++17 解决了这个问题,通过折叠表达式(fold expression)简化对参数包的展开。

2.语法形式

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

一元左折叠(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 (EN−1 op (EN op I)))

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

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

3.使用实例

(1)一元右折叠
从表达式右边开始fold,看它是left fold还是right fold我们可以根据参数包…所在的位置来判断,当参数包…在操作符右边的时候就是right fold,在左边的时候就是left fold。示例如下:

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

auto t = add_val(1,2,3,4); //10

(2)一元左折叠
对于+这种满足交换律的操作符来说,left fold和right fold是一样的,比如上面的例子你也可以写成left fold。

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

auto t = add_val(1,2,3,4); //10

对于不满足交换律的操作符来说就要注意了,比如减法,下面的right fold和left fold的结果就不一样。

template<typename... Args>
auto sub_val_right(Args&&... args) {
    return (args - ...);
}


template<typename... Args>
auto sub_val_left(Args&&... args) {
    return (... - args);
}

auto t = sub_val_right(2,3,4); //(2-(3-4)) = 3
auto t1 = sub_val_left(2,3,4); //((2-3)-4) = -5

(3)二元右折叠
二元fold的语义和一元fold的语义是相同的,参数包…在左即二元左折叠,参数包…在右即右折叠。下面看一个二元右折叠的例子。

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

(4)二元左折叠

template<typename... Args>
auto sub_one_right(Args&&... args) {
    return (args - ... - 1);
}
auto t = sub_one_left(2,3,4);// (((1-2)-3)-4) = -8

(5)comma fold
在C++17之前,我们经常使用逗号表达式结合列表初始化的方式对参数包进行展开,比如像下面这个例子:

template<typename T>
void print_arg(T t) {
    std::cout << t << std::endl;
}

template<typename... Args>
void print2(Args... args) {
	int a[] = { (print_arg(args), 0)... };
	//或者
    //std::initializer_list<int>{(print_arg(args), 0)...};
}

这种写法比较繁琐,用fold expression就会变得很简单了。

//unary right fold
template<typename... Args>
void print3(Args... args) {
    (print_arg(args), ...);
}

//unary left fold
template<typename... Args>
void print3(Args... args) {
    (..., print_arg(args));
}

unary right fold 和 unary left fold,对于 comma 来说两种写法是一样的,参数都是从左至右传入 print_arg 函数。当然,我们也可以通过 binary fold 实现:

template<typename ...Args>
void printer(Args&&... args) {
	(std::cout << ... << args) << '\n';
}

注意,下面的写法是不合法的,根据binary fold的语法,参数包…必须在操作符中间。

template<typename ...Args>
void printer(Args&&... args) {
    (std::cout << args << ...) << '\n';
}

参考文献

C++17中那些值得关注的特性(上)
C++17尝鲜:fold expression(折叠表达式)

 类似资料: