网站开发公司oa,建站宝盒建网站,wordpress获得链接,网站移动端建设相关文章系列 深入理解可变参数(va_list、std::initializer_list和可变参数模版) 目录
1.介绍
2.应用
2.1.使用折叠表达式
2.2.支持的运算符
2.3.使用折叠处理类型
3.总结 1.介绍 折叠表达式是C17新引进的语法特性。使用折叠表达式可以简化对C11中引入的参数包的处理17新引进的语法特性。使用折叠表达式可以简化对C11中引入的参数包的处理从而在某些情况下避免使用递归。折叠表达式共有四种语法形式。分别为一元的左折叠和右折叠以及二元的左折叠和右折叠。
1、一元右折叠(unary right fold) ( pack op ... ) 一元右折叠(E op ...)展开之后变为 E1 op (... op (EN-1 op EN)) 2、一元左折叠(unary left fold) ( ... op pack ) 一元左折叠(... op E)展开之后变为 ((E1 op E2) op ...) op EN 3、二元右折叠(binary right fold) ( pack op ... op init ) 二元右折叠(E op ... op I)展开之后变为 E1 op (... op (EN−1 op (EN op I))) 4、二元左折叠(binary left fold) ( init op ... op pack ) 二元左折叠(I op ... op E)展开之后变为 (((I op E1) op E2) op ...) op EN op代表运算符下列 32 个二元运算符之一 - * / % ^ | - * / % ^ | ! || , .* -*。在二元折叠中两个运算符必须相同。
pack代表参数包含有未展开的形参包且在顶层不含优先级低于转型正式而言是 转型表达式的运算符的表达式。
init代表初始值不含未展开的形参包且在顶层不含优先级低于转型正式而言是 转型表达式的运算符的表达式注意开闭括号也是折叠表达式的一部分。 这里的括号是必需的。但是圆括号和省略号(...)不必用空格分隔。 初始值在右边的为右折叠展开之后从右边开始折叠。而初始值在左边的为左折叠展开之后从左边开始折叠。 不指定初始值的为一元折叠表达式而指定初始值的为二元折叠表达式。
例如
templatetypename... Args
bool all(Args... args) { return (... args); }
templatetypename... Args
bool any(Args... args) {return (... || args);}bool b all(true, true, true, false);
// 在 all() 中一元左折叠展开成
// return ((true true) true) false;
// b 是 false
将一元折叠用于长度为零的包展开时只能使用下列运算符 1) 逻辑与。空包的值是 true 2) 逻辑或||。空包的值是 false 3) 逗号运算符,。空包的值是 void() 注意如果用作初值或形参包 的表达式在顶层具有优先级低于转型的运算符那么它可以加括号如 templatetypename... Args
int sum(Args... args)
{
// return (args ... 1 * 2); // 错误优先级低于转型的运算符return (args ... (1 * 2)); // OK
}
2.应用
2.1.使用折叠表达式
下面的函数返回所有传递参数的和:
#include iostream
#include string//[1]
templatetypename First
First foldSum1(First value)
{ return value;
}//[2]
templatetypename First, typename... Rest
First foldSum1(First first, Rest... rest)
{ return first foldSum1(std::forwardRest(rest)...);
}//[3]
templatetypename... T
auto foldSum2(T... args)
{return (... args); // ((arg1 arg2) arg3) ...
}//[4]
templatetypename First, typename... Rest
First foldSum3(First first, Rest... rest)
{ return (first ... rest);
}int main(void)
{auto i1 foldSum1(58, 25, 128, -10); //201auto s1 foldSum1(std::string(abcdefg ), std::string(1234567890 ), std::string(!));//abcdefg 1234567890 !auto i2 foldSum2(58, 25, 128, -10); //201auto s2 foldSum2(std::string(abcdefg ), std::string(1234567890 ), std::string(!));//abcdefg 1234567890 !auto i3 foldSum3(58, 25, 128, -10); //201auto s3 foldSum3(std::string(abcdefg ), std::string(1234567890 ), std::string(!));//abcdefg 1234567890 !return 0;
}
1在C17之前求和函数foldSum1的实现必须分成两个部分。其中[1]部分的foldSum1函数用于处理一个参数的情况。[2]部分的foldSum1函数用于处理两个及以上参数的情况。当参数个数大于一个时[2]部分的foldSum1函数将前两个参数相加然后递归调用自身。当参数个数只有一个时[1]部分的foldSum1函数将此参数返回完成求和。foldSum1(58, 25, 128, -10) 58foldSum1(25, 128, -10) 5825foldSum1(128, -10) 5825128foldSum1(-10) 5825128-10 201。
2而在C17之后由于有了折叠表达式这个新特性求和函数foldSum1不再需要处理特殊情况实现大为简化。对于foldSum2(58, 25, 128, -10) (((5825)128) -10) 201。
还请注意折叠表达式参数的顺序可能不同而且很重要:
(... args)
的结果是
((arg1 arg2) arg3) ...
也可以如
(args ...)
其结果是
(arg1 (arg2 arg3)) ...
上面foldSum2定义的函数不允许在添加值时传递空参数包像下面调用会出现错误 于是可改为
//[3]
templatetypename... T
auto foldSum2(T... args)
{return (0 ... args); // ((arg1 arg2) arg3) ...
}
从概念上讲我们添加0作为第一个操作数还是最后一个操作数并不重要:
//[3]
templatetypename... T
auto foldSum2(T... args)
{return (args ... 0); // ((arg1 arg2) arg3) ...
}
但是对于一元折叠表达式求值顺序很重要。对于二元折叠表达式也应该优选左折叠表达式
(val ... args); // preferred syntax for binary fold expressions
2.2.支持的运算符
在C中除了以下二元运算符所有的二元操作符都可以使用折叠表达式。如下所示. 、 -、 []。叠表达式可以使用逗号运算符这样就可以在一行调用多个函数如
#include iostream
using namespace std;templatetypename... Ts
void printAll(Ts... mXs)
{(cout ... mXs) endl;
}templatetypename TF, typename... Ts
void forArgs(TF mFn, Ts... mXs)
{(mFn(mXs), ...);
}int main()
{printAll(78, 7811.0, 6789); //7878116789printAll(); // 空行forArgs([](auto a){cout a;}, 78, 7811.0, 6789); //7878116789forArgs([](auto a){cout a;}); // 空操作return 0;
}
1) printAll函数实现了对不特定多数值的打印输出。该函数的实现采用了二元左折叠。
printAll(78, 7811.0, 6789)(cout ... pack(78, 7811.0, 6789) endl((cout 78) 7811.0) 6789 endl打印7878116789并换行
2) 当二元折叠表达式的参数包为空时其计算结果为该二元折叠表达式中所预设的初始值。
printAll()(cout ... pack()) endlcout endl空行
3forArgs函数实现了依次使用多个参数调用某个单参数函数的功能。该函数的实现采用了一元右折叠。
forArgs([](auto a){cout a;}, 78, 7811.0, 6789)([](auto a){cout a;}(pack(78, 7811.0, 6789)), ...)[](auto a){cout a;}(78), ([](auto a){cout a;}(7811.0), ([](auto a){cout a;}(6789)))打印7878116789
4当使用逗号的一元折叠表达式中的参数包为空时其计算结果为标准规定的缺省值void()。
forArgs([](auto a){cout a;})([](auto a){cout a;}(pack()), ...)void()
上面是将折叠应用在函数中下面将讨论将折叠使用在类中作为类的基类进行调用
templatetypename... T
class MultiT : private T...
{
public:
void print()
{(..., T::print());
}
};
class CTest1 {
public:void print() { std::cout CTest1::print()std::endl; }
};
class CTest2 {
public: void print() { std::cout CTest2::print()std::endl; }
};
class CTest3 {
public: void print() { std::cout CTest3::print()std::endl; }
};int main()
{MultiTCTest1,CTest2,CTest2 myTest;myTest.print();//输出结果为CTest1::print() CTest2::print() CTest3::print()return 0;
}
使用折叠表达式将其展开以便为每个基类调用print。也就是说折叠表达式的语句扩展为:
(CTest1::print() , CTest2::print()) , CTest3::print();
但是请注意由于逗号运算符的性质我们使用左折叠运算符还是右折叠运算符并不重要。函数总是从左到右调用。
(T::print() , ...);
括号只对调用进行分组以便第一个print()调用与其他两个print()调用的结果组合在一起如下所示:
CTest1::print() , (CTest2::print() , CTest3::print());
但是因为逗号运算符的求值顺序总是从左到右仍然是第一个调用CTest1::print()发生在括号内的两个调用组(CTest2::print(), CTest3::print())之前其中中间调用CTest2::print()仍然发生在右边调用CTest3::print()之前。然而由于左折叠表达式与结果的计算顺序相匹配所以当将左折叠表达式用于多个函数调用时再次建议使用它们。
2.3.使用折叠处理类型
通过使用类型特征可以判断类或者函数中传入的参数类型是否相同,如
templatetypename T1, typename... TN
constexpr bool isHomogeneous(T1, TN...)
{return (std::is_same_vT1, TN ...);
}
int main()
{std::coutboolalphaisHomogeneous(12,4434,true)std::endl; //输出falsereturn 0;
}
上面函数调用isHomogeneous(12,4434,true)返回的表达式扩展为
std::is_same_vint, int std::is_same_vint, bool
结果为假而对
isHomogeneous(q24214, , c3252352, !);
来说结果为真因为所有传入的实参类型被推导为const char*注意实参类型会退化因为调用实参是按值传递的。
3.总结 折叠表达式是一个强大的工具但也需要谨慎使用。它可以使代码更简洁、更易于阅读但也可能会使代码更难以理解。在使用折表达式之前确保你理解了它的工作原理并考虑是否有其他更直观的方法可以达到相同的效果。
参考折叠表达式(C17 起) - cppreference.com