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);  //201
	auto s1 = foldSum1(std::string("abcdefg "), std::string("1234567890 "), std::string("!"));//"abcdefg 1234567890 !"

	auto i2 = foldSum2(58, 25, 128, -10);  //201
	auto s2 = foldSum2(std::string("abcdefg "), std::string("1234567890 "), std::string("!"));//"abcdefg 1234567890 !"

	auto i3 = foldSum3(58, 25, 128, -10);  //201
	auto 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"); //7878116789
    printAll(); // 空行
    forArgs([](auto a){cout << a;}, 78, 7811.0, "6789"); //7878116789
    forArgs([](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; //输出:false
    return 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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

自定义el-upload 上传文件

前言 最近在做一个文件上传的功能&#xff0c;后端接口写好了、发现前端上传文件的页面不会写……&#xff08;我很笨的&#xff09;然后我就找啊找发现element有个组件是<el-upload/>能直接上传文件。我就想直接用拿来改改改成自己想要的&#xff0c;可是就是这样我花了…

【C++】拷贝构造函数(深拷贝和浅拷贝)

使用场景 在C类中&#xff0c;我们在类的成员变量内定义了一个指针。这时我们需要去创建它的拷贝构造函数&#xff0c;否则编译器会为这个类创建默认的拷贝构造函数&#xff0c;而默认拷贝构造函数会导致浅拷贝问题&#xff1b;浅拷贝可能会会导致内存泄漏问题&#xff0c;也可…

MATLAB Function转C代码实战

文章目录 前言1. 准备工作2. 使用MATLAB Coder2.1 确定输入输出的类型2.2 MATLAB Coder过程 3. 代码调整和优化4. 编译和测试5. 性能分析和优化结语 前言 在科学与工程领域&#xff0c;MATLAB&#xff08;Matrix Laboratory&#xff09;是一种广泛使用的高级技术计算软件&…

一个Post请求入门NestJS的路由与控制器

​ NestJS的控制器 控制器负责处理传入请求并向客户端返回响应。 控制器的目的是接收应用的特定请求。路由机制控制哪个控制器接收哪些请求。 通常&#xff0c;每个控制器都有不止一条路由&#xff0c;不同的路由可以执行不同的操作。 在使用了脚手架的项目中&#xff0c;我…

【激光SLAM】基于图优化的激光SLAM 方法(Grid-based)

文章目录 Graph-based SLAM数学概念 非线性最小二乘(Non-Linear Least Square)解决的问题误差函数线性化流程 非线性最小二乘在SLAM中的应用图的构建&#xff08;SLAM前端&#xff09;误差函数误差函数的线性化固定坐标系构建线性系统求解 Cartographer介绍 Graph-based SLAM …

如何在本地部署密码管理软件bitwarden并结合cpolar实现远程同步

文章目录 1. 拉取Bitwarden镜像2. 运行Bitwarden镜像3. 本地访问4. 群晖安装Cpolar5. 配置公网地址6. 公网访问Bitwarden7. 固定公网地址8. 浏览器密码托管设置 Bitwarden是一个密码管理器应用程序&#xff0c;适用于在多个设备和浏览器之间同步密码。自建密码管理软件bitwarde…

上门服务系统|上门服务小程序|上门服务软件开发

随着移动互联网技术的普及&#xff0c;上门服务小程序系统成为现代企业数字化转型的关键一环。这一系统为消费者提供了更加便捷、高效以及个性化的服务体验&#xff0c;同时也为企业带来了更广阔的商业机会。让我们来看看上门服务小程序系统的优势和功能。 首先&#xff0c;上门…

数据安全治理实践路线(下)

数据安全运营阶段通过不断适配业务环境和风险管理需求&#xff0c;持续优化安全策略措施&#xff0c;强化整个数据安全治理体系的有效运转。 数据安全运营 1. 风险防范 数据安全治理的目标之一是降低数据安全风险&#xff0c;因此建立有效的风险防范手段&#xff0c;对于预防…

使用Docker部署MinIO并结合内网穿透实现远程访问本地数据

文章目录 前言1. Docker 部署MinIO2. 本地访问MinIO3. Linux安装Cpolar4. 配置MinIO公网地址5. 远程访问MinIO管理界面6. 固定MinIO公网地址 前言 MinIO是一个开源的对象存储服务器&#xff0c;可以在各种环境中运行&#xff0c;例如本地、Docker容器、Kubernetes集群等。它兼…

Redis高可用三主三从集群部署

文章目录 &#x1f50a;博主介绍&#x1f964;本文内容使用宝塔面板搭建集群规划配置验证 使用docker搭建使用脚本搭建&#x1f4e2;文章总结&#x1f4e5;博主目标 &#x1f50a;博主介绍 &#x1f31f;我是廖志伟&#xff0c;一名Java开发工程师、Java领域优质创作者、CSDN博…

【JS】事件绑定方法自带一个形参e“function(e)”,what is e?

在学习js的时候 我跳过了一部分章节的内容&#xff0c;导致现在学习react的时候很多内容都不知所措&#xff0c;因为这些教程都是建立在它认为你js所有内容都掌握的前提下&#xff0c;当然这是我自身的原因。需要反省。 下面是正题&#xff1a; 我们知道js有很多事件&#…

Linux设备模型(五) - uevent kernel实现

1. Uevent的功能 Uevent是Kobject的一部分&#xff0c;用于在Kobject状态发生改变时&#xff0c;例如增加、移除等&#xff0c;通知用户空间程序。用户空间程序收到这样的事件后&#xff0c;会做相应的处理。 该机制通常是用来支持热拔插设备的&#xff0c;例如U盘插入后&…

mac flutter 配置

下载Flutter Sdk Start building Flutter Android apps on macOS - Flutter 中文文档 - Flutter 中文开发者网站 - Flutter 下载后解压放到一个文件夹 /Users/zhiyu/Documents/gitflutter/flutter3.19.1/ 环境变量中要用到 配置 Android 开发 下载 Android Studio 和应用工具…

软件运维维保服务方案-套用模板

软件运维维保方案-套用模板 项目情况 1.1 项目背景简述项目的来源、目的和重要性。说明项目的规模、预算和预期目标。 1.2 项目现状分析当前系统/软件的运行状态、存在的问题和潜在风险。提供最近一次的维护报告或相关统计数据。服务简述 2.1 服务内容明确运维服务的具体内容&…

三、系统知识笔记-计算机系统基础知识

一、计算机系统概述 计算机系统是指用于数据管理的计算机硬件、软件及网络组成的系统。 它是按人的要求接收和存储信息&#xff0c;自动进行数据处理和计算&#xff0c;并输出结果信息的机器系统。 冯诺依曼体系计算机结构&#xff1a; 1.1计算机硬件组成 冯诺依曼计算机结…

大模型实战营第二期——4. XTuner 大模型单卡低成本微调实战

github地址&#xff1a;InternLM/tutorial-书生浦语大模型实战营文档地址&#xff1a;XTuner 大模型单卡低成本微调实战视频地址&#xff1a;XTuner 大模型单卡低成本微调实战Intern Studio: https://studio.intern-ai.org.cn/console/instance 这个人的研究方向是眼科的AI&am…

分披萨 - 华为OD统一考试(C卷)

OD统一考试&#xff08;C卷&#xff09; 分值&#xff1a; 100分 题解&#xff1a; Java / Python / C 题目描述 “吃货”和“馋嘴”两人到披萨店点了一份铁盘(圆形)披萨&#xff0c;并嘱咐店员将披萨按放射状切成大小相同的偶数个小块。 但是粗心服务员将披萨切成了每块大小…

matlab 线性四分之一车体模型

1、内容简介 略 57-可以交流、咨询、答疑 路面采用公式积分来获得&#xff0c;计算了车体位移、非悬架位移、动载荷等参数 2、内容说明 略 3、仿真分析 略 线性四分之一车体模型_哔哩哔哩_bilibili 4、参考论文 略

Kubernetes基础(二十五)-Kubernetes GC原理

1 K8s 的垃圾回收策略 当给k8s一个资源对象设置OwnerReference的时候&#xff0c;删除该资源对象的owner, 该对象也会被连带删除。这个时候用的就是k8s的垃圾回收机制。 k8s目前支持三种回收策略&#xff1a; 1&#xff09;前台级联删除&#xff08;Foreground Cascading De…

中英文互译赫尔辛基大学翻译模型安装与测试

引子 近期接到一个文本中英互译的任务&#xff0c;一直以为这种翻译应该很成熟&#xff0c;各种商用版本很多。那么开源的一定也不少&#xff0c;经过网络搜索发现&#xff0c;近两年还真的出现了很多优秀的开源翻译项目。找到了赫尔辛基大学开源免费的多语言翻译模型&#xff…