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://xiahunao.cn/news/2812636.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

StarRocks之监控管理(内含DashBoard模板)

先看下最终效果图 架构 Prometheus 是一个拥有多维度数据模型的、灵活的查询语句的时序数据库。它可以通过 Pull 或 Push 采集被监控系统的监控项,存入自身的时序数据库中。并且通过丰富的多维数据查询语言,满足用户的不同需求。 Grafana 是一个开源的 Metric 分析及可视化系…

如何避免在绩效考核评分时“脸盲”

对于大型企业来说&#xff0c;如何保证在绩效考核评分时准确快速地回忆起员工的日常表现&#xff0c;已经成为困扰管理者的难题。由于大型企业员工数量众多且业务模块繁杂&#xff0c;公司领导很难对每个员工的工作情况都了如指掌。这导致在绩效考核评分时&#xff0c;公司领导…

Keepalived介绍、架构和安装

Keepalived介绍、架构和安装 文章目录 Keepalived介绍、架构和安装1.Keepalived&#xff08;高可用性服务&#xff09;1.1 Keepalived介绍1.2 Keepalived 架构1.3 Keepalived 相关文件 2.Keepalived安装2.1 主机初始化2.1.1 设置网卡名和ip地址2.1.2 配置镜像源2.1.3 关闭防火墙…

网安入门18-XSS(靶场实战)

HTML实体化编码 为了避免 XSS 攻击&#xff0c;会将<>编码为<与>&#xff0c;这些就是 HTML 实体编码。 编码前编码后不可分的空格 < (小于符号)< > (大于符号)> & (与符号)&amp;″ (双引号)&quot;’ (单引号)&apos;© (版权符…

ADS-B Receiver Board Mode-S Beast

目录 Introduction Specifications Driver Installation Data Format AVR Data binary format Introduction Mode-S Beast is a high-performance ADS-B receiver board that can receive and decode ADS-B, Mode-S and Mode-A/C signals. The Mode-S Beast allows you…

最全SCADA介绍:SCADA软件架构、特点、应用

这篇文章&#xff0c;我们将深入介绍SCADA系统和SCADA软件。 SCADA系统是工业生产中的一个重要部分&#xff0c;因为它们有助于监控过程数据以及控制过程&#xff0c;并轻松检测过程中的任何问题&#xff0c;从而减少停机时间。 SCADA系统的定义 SCADA是Supervisory Control…

08 string类的使用

为什么要学习string类 c语言中的字符串 c语言中&#xff0c;字符串是以\0结尾的一些字符的集合&#xff0c;为了操作方便&#xff0c;c标准库提供了一些str系列的函数&#xff0c;但是这些库函数与字符串是分离开的&#xff0c;不符合OOP的思想&#xff0c;而且底层空间需要自…

Laravel04 eloquent

eloquent 1. eloquent2. 创建eloquent model 以及 取数据 1. eloquent 文档地址&#xff1a; https://learnku.com/docs/laravel/8.x/eloquent/9406 下面是我们&#xff0c;通过laravel的DB类从数据库中获取了post记录&#xff0c;那么有没有可能我们直接获取一个post对象&am…

Ansible group模块 该模块主要用于添加或删除组。

目录 创建组验证删除组验证删除一个不存在的组 常用的选项如下&#xff1a; gid  #设置组的GID号 name  #指定组的名称 state  #指定组的状态&#xff0c;默认为创建&#xff0c;设置值为absent为删除 system  #设置值为yes&#xff0c;表示创建为系统组 创建组 ansib…

Netty权威指南——基础篇2(NIO编程)备份

1 概述 与Socket类和ServerSocket&#xff0c;NIO也提供了SocketChannel和ServerSocketChannel两种不同的套接字通道实现。这两种新增的通道都支持阻塞和非阻塞两种模式。阻塞模式使用简单&#xff0c;但性能和可靠性都不好&#xff0c;非阻塞模式则正好相反。一般来说&#xf…

电机效率MAP图

直接使用contourf&#xff0c;需要有[X,Y] meshgrid(x,y), 并用Zf(X,Y)来生成Z轴。但是如果一开始Z轴坐标就不是x,y用函数生成的&#xff0c;而是有个默认的测试数据&#xff0c;又该如何用来画MAP图呢? clc;clear;clf; data_ECO []; //具体数值可以自己填&#xff0c;此处…

240Hz高刷电竞显示器 - HKC VG253KM

&#x1f389;&#x1f389;&#x1f389; 各位电竞爱好者们&#xff0c;今天给大家带来一款神秘武器&#xff0c;一款能够让你在游戏中大展拳脚的高刷电竞显示器 - HKC VG253KM&#xff01;&#x1f525;&#x1f525;&#x1f525; 这款显示器&#xff0c;哎呀&#xff0c;真…

测试环境搭建整套大数据系统(七:集群搭建kafka(2.13)+flink(1.14)+dinky+hudi)

一&#xff1a;搭建kafka。 1. 三台机器执行以下命令。 cd /opt wget wget https://dlcdn.apache.org/kafka/3.6.1/kafka_2.13-3.6.1.tgz tar zxvf kafka_2.13-3.6.1.tgz cd kafka_2.13-3.6.1/config vim server.properties修改以下俩内容 1.三台机器分别给予各自的broker_id…

第40期 | GPTSecurity周报

GPTSecurity是一个涵盖了前沿学术研究和实践经验分享的社区&#xff0c;集成了生成预训练Transformer&#xff08;GPT&#xff09;、人工智能生成内容&#xff08;AIGC&#xff09;以及大语言模型&#xff08;LLM&#xff09;等安全领域应用的知识。在这里&#xff0c;您可以找…

SpringBoot:Invalid bound statement (not found)的原因和解决方案

&#x1f413; 报错信息&#xff1a; &#xff08;无效绑定声明&#xff09;找不到 解析&#xff1a; 你的mapper实例对象和对应的mapper.xml对象未找到 &#x1f413; 排查&#xff1a; 情况一&#xff1a; 1.排除相对应的mapper实例对象路径是否正确 查看相对应的mapper中…

一个完整的性能测试流程,究竟应该是什么样子的?

下午逛一个测试交流群时&#xff0c;聊起性能测试&#xff0c;然后某位群成员说“会用load runner不就是会做性能测试&#xff1f;” 当时觉得这话有点偏颇&#xff0c;虽然我也是一个性能测试道路上的摸索前进者&#xff0c;但是load runner≠性能测试&#xff0c;或者说&…

vue3自定义实现悬浮固定按钮组件

目录 一、需求描述二、代码解读三、结果展示 一、需求描述 需要5个固定的悬浮圆&#xff0c;居于页面的右侧。鼠标悬浮在圆上面会显示对应的文字提示其中包含返回顶部悬浮圆&#xff0c;当页面滑至底部时出现&#xff0c;点击页面滑到顶部。点击按钮会给出弹窗 二、代码解读…

谷歌最强轻量级开源大模型Gemma:小尺寸可商用,性能超越Llama-2,个人PC就能用

前言 谷歌近日发布了其最新的轻量级、开源AI模型——Gemma&#xff0c;这一举措无疑在AI领域引起了广泛的关注。不同于其他闭源大模型&#xff0c;Gemma的推出标志着谷歌在开放模型领域的重要一步&#xff0c;意图通过开放、共享的方式&#xff0c;加速AI技术的普及和应用。 G…

无法访问云服务器上部署的Docker容器(二)

说明&#xff1a;记录一次使用公网IP 接口地址无法访问阿里云服务接口的问题&#xff1b; 描述 最近&#xff0c;我使用Docker部署了jeecg-boot项目&#xff0c;部署过程都没有问题&#xff0c;也没有错误信息。部署完成后&#xff0c;通过下面的地址访问后端Swagger接口文档…

【踩坑】修复报错 you should not try to import numpy from its source directory

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhang.cn] 报错如下&#xff1a; 修复方法一&#xff1a; pip install pyinstaller5.9 修复方法二&#xff1a; pip install numpy1.24.1