C++17之折叠表达式

相关文章系列

深入理解可变参数(va_list、std::initializer_list和可变参数模版)

目录

1.介绍

2.应用

2.1.使用折叠表达式

2.2.支持的运算符

2.3.使用折叠处理类型

3.总结


1.介绍

        折叠表达式是C++17新引进的语法特性。使用折叠表达式可以简化对C++11中引入的参数包的处理,从而在某些情况下避免使用递归。折叠表达式共有四种语法形式。分别为一元的左折叠和右折叠,以及二元的左折叠和右折叠。

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代表初始值:不含未展开的形参包且在顶层不含优先级低于转型(正式而言,是 转型表达式)的运算符的表达式注意开闭括号也是折叠表达式的一部分。

这里的括号是必需的。但是,圆括号和省略号(...)不必用空格分隔。

初始值在右边的为右折叠,展开之后从右边开始折叠。而初始值在左边的为左折叠,展开之后从左边开始折叠。
不指定初始值的为一元折叠表达式,而指定初始值的为二元折叠表达式。

例如:

template<typename... Args>
bool all(Args... args) { return (... && args); }
template<typename... 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()

注意:如果用作初值或形参包 的表达式在顶层具有优先级低于转型的运算符,那么它可以加括号,如:

template<typename... Args>
int sum(Args&&... args)
{
//  return (args + ... + 1 * 2);   // 错误:优先级低于转型的运算符return (args + ... + (1 * 2)); // OK
}

2.应用

2.1.使用折叠表达式

下面的函数返回所有传递参数的和:

#include <iostream>
#include <string>//[1]
template<typename First>  
First foldSum1(First&& value)  
{  return value;  
}//[2]
template<typename First, typename... Rest>  
First foldSum1(First&& first, Rest&&... rest)  
{  return first + foldSum1(std::forward<Rest>(rest)...);  
}//[3]
template<typename... T>
auto foldSum2(T... args)
{return (... + args); // ((arg1 + arg2) + arg3) ...
}//[4]
template<typename 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)在C++17之前,求和函数foldSum1的实现必须分成两个部分。其中[1]部分的foldSum1函数用于处理一个参数的情况。[2]部分的foldSum1函数用于处理两个及以上参数的情况。当参数个数大于一个时,[2]部分的foldSum1函数将前两个参数相加,然后递归调用自身。当参数个数只有一个时,[1]部分的foldSum1函数将此参数返回,完成求和。foldSum1(58, 25, 128, -10) = 58+foldSum1(25, 128, -10) = 58+25+foldSum1(128, -10) = 58+25+128+foldSum1(-10) = 58+25+128-10 = 201。

2)而在C++17之后,由于有了折叠表达式这个新特性,求和函数foldSum1不再需要处理特殊情况,实现大为简化。对于foldSum2(58, 25, 128, -10) = (((58+25)+128) -10) = 201。

还请注意,折叠表达式参数的顺序可能不同,而且很重要:

(... + args)

的结果是

((arg1 + arg2) + arg3) ...

也可以如:

(args + ...)

其结果是

(arg1 + (arg2 + arg3)) ...

上面foldSum2定义的函数不允许在添加值时传递空参数包,像下面调用会出现错误:

 于是可改为:

//[3]
template<typename... T>
auto foldSum2(T... args)
{return (0 + ... + args); // ((arg1 + arg2) + arg3) ...
}

从概念上讲,我们添加0作为第一个操作数还是最后一个操作数并不重要:

//[3]
template<typename... 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;template<typename... Ts>
void printAll(Ts&&... mXs)
{(cout << ... << mXs) << endl;
}template<typename 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()) << endl
= cout << endl
= 空行

3)forArgs函数实现了依次使用多个参数调用某个单参数函数的功能。该函数的实现采用了一元右折叠。

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()

上面是将折叠应用在函数中,下面将讨论将折叠使用在类中,作为类的基类进行调用

template<typename... 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()
{MultiT<CTest1,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.使用折叠处理类型

通过使用类型特征,可以判断类或者函数中传入的参数类型是否相同,如:

template<typename T1, typename... TN>
constexpr bool isHomogeneous(T1, TN...)
{return (std::is_same_v<T1, TN> && ...);
}
int main()
{std::cout<<boolalpha<<isHomogeneous(12,4434,true)<<std::endl; //输出:falsereturn 0;
}

上面函数调用isHomogeneous(12,4434,true),返回的表达式扩展为:

std::is_same_v<int, int> && std::is_same_v<int, bool>

结果为假,而对:

isHomogeneous("q24214", "", "c3252352", "!");

来说结果为真,因为所有传入的实参类型被推导为const char*(注意,实参类型会退化,因为调用实参是按值传递的)。

3.总结

        折叠表达式是一个强大的工具,但也需要谨慎使用。它可以使代码更简洁、更易于阅读,但也可能会使代码更难以理解。在使用折表达式之前,确保你理解了它的工作原理,并考虑是否有其他更直观的方法可以达到相同的效果。

参考:折叠表达式(C++17 起) - cppreference.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/493085.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【MySQL】学习连接查询和案例演示

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-vycqHoIbdg9sSKEo {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

我在使用 Copilot 时遇到了许可证验证错误。

如果使用的是 Copilot&#xff0c;并收到以下错误消息&#xff0c;请按以下步骤进行操作&#xff1a; We encountered a problem validating your Copilot license. For more information, see https://aka.ms/copilotlicensecheck 请确保使用的是正确的帐户 请确保已使用具…

pop链构造 [NISACTF 2022]babyserialize

打开题目 题目源代码如下 <?php include "waf.php"; class NISA{public $fun"show_me_flag";public $txw4ever;public function __wakeup(){if($this->fun"show_me_flag"){hint();}}function __call($from,$val){$this->fun$val[0];…

SD-WAN案例:总部(MPLS)与分支(普通宽带)的互联互通

某制造业企业面临着总部采用MPLS专线而分支机构使用普通宽带的网络互联挑战。这种情况下&#xff0c;如何降低网络成本&#xff0c;提高网络效率成为当前亟需解决的问题。本文将介绍该企业如何通过部署SD-WAN实现互联互通。 网络痛点及需求分析&#xff1a; 该企业主要痛点包括…

Linux系列讲解 —— 【Vim编辑器】在Ubuntu18.04中安装新版Vim

平时用的电脑系统是Ubuntu18.04&#xff0c;使用apt安装VIM的默认版本是8.0。如果想要安装新版的Vim编辑器&#xff0c;只能下载Vim源码后进行编译安装。 目录 1. 下载2. 编译3. 安装4. 遇到的问题4.1 打开vim后&#xff0c;文本开头有乱码现象。4.2 在Vim编辑器中&#xff0c;…

Jquery中的事件与动画

文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 本章目标 使用常用简单事件制作网页特效使用鼠标事件制作主导航特效使用hover()方法制作下拉菜单特效使用鼠标事件及动画制作页面特效 一.Jquery事件概述 二.基础事件 鼠标事件 演示案例&…

Easy-Jmeter: 性能测试平台

目录 写在开始1 系统架构2 表结构设计3 测试平台生命周期4 分布式压测5 压力机管理6 用例管理6.1 新增、编辑用例6.2 调试用例6.3 启动测试6.4 动态控量6.5 测试详情6.6 环节日志6.7 实时数据6.8 测试结果 7 测试记录7 用例分析8 系统部署8.1普通部署8.2容器化部署 写在最后 写…

人工智能产生的幻觉问题真的能被看作是创造力的另一种表现形式吗?

OpenAI的首席执行官山姆奥特曼&#xff08;Sam Altman&#xff09;曾声称&#xff0c;人工智能产生的“幻觉”其实未尝不是一件好事&#xff0c;因为实际上GPT的优势正在于其非凡的创造力。 目录 一.幻觉问题的概念 二.幻觉产生的原因 三.幻觉的分类 四.减轻AI的幻觉问题到…

全面介绍HTML的语法!轻松写出网页

文章目录 heading(标题)paragraph(段落)link(超链接)imagemap(映射)table(表格)list(列表)layout(分块)form(表单)更多输入:datalistautocompleteautofocusmultiplenovalidatepatternplaceholderrequired head(首部)titlebaselinkstylemetascriptnoscript iframe HTML&#xff…

外包干了3个月,技术倒退1年。。。

先说情况&#xff0c;大专毕业&#xff0c;18年通过校招进入湖南某软件公司&#xff0c;干了接近6年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试&#xf…

缓存一致性问题的解决策略

缓存一致性问题的背景和概念介绍 在一个系统中&#xff0c;我们通常使用数据库来存储数据&#xff0c;以保证数据的持久性。但是&#xff0c;由于数据库的读写速度相对较慢&#xff0c;如果每次请求都直接访问数据库&#xff0c;会降低系统的响应速度。为了提高系统的性能&…

Spring Cloud学习

1、什么是SpringCloud Spring cloud 流应用程序启动器是基于 Spring Boot 的 Spring 集成应用程序&#xff0c;提供与外部系统的集成。Spring cloud Task&#xff0c;一个生命周期短暂的微服务框架&#xff0c;用于快速构建执行有限数据处理的应用程序。Spring cloud 流应用程…