C++17之折叠表达式

news2024/11/23 15:44:15

相关文章系列

深入理解可变参数(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

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

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

相关文章

原型设计工具Axure RP

Axure RP是一款专业的快速原型设计工具。Axure&#xff08;发音&#xff1a;Ack-sure&#xff09;&#xff0c;代表美国Axure公司&#xff1b;RP则是Rapid Prototyping&#xff08;快速原型&#xff09;的缩写。 下载链接&#xff1a;https://www.axure.com/ 下载 可以免费试用…

windows安装onlyoffice8.0

安装erlang 安装Erlang25.3 下载地址 设置环境变量 ERLANG_HOME C:\Program Files\Erlang OTP Path下设置%ERLANG_HOME%\bin 打开cmd输入erl不报错即可 安装rabbitmq rabbitmq和erlang对应的关系 下载地址 执行完exe文件后&#xff0c;找到安装目录下的sbin&am…

H12-821_77

77.如图所示的交换网络&#xff0c;所有交换机都运行了STP协议&#xff0c;当拓扑稳定后&#xff0c;在以下哪台交换机上修改配置BPDU的发送周期&#xff0c;可以影响STD配置BPDU的发送周期&#xff1f; A.STC B.SWD C.SWA D.SWB 答案&#xff1a;C 注释&#xff1a; 在根桥上…

MATLAB环境下基于超高斯全自动组织学图像的盲彩色反卷积方法

图像盲反卷积问题仅根据模糊图像估计清晰图像和模糊核&#xff0c;也是一个欠定问题且求解更加困难。但图像盲反卷积算法更实际&#xff0c;因为许多情况下&#xff0c;模糊核都是未知或部分已知的。求解盲反卷积问题需要为未知量选择适当的先验模型&#xff0c;以得到清晰图像…

二叉树与堆

目录 1.树概念及结构 1.1树的概念 1.2 树的相关概念 1.3 树的表示 1.4 树在实际中的运用&#xff08;表示文件系统的目录树结构&#xff09; 2.二叉树概念及结构 2.1概念 2.2现实中的二叉树&#xff1a; 2.3 特殊的二叉树&#xff1a; 2.4 二叉树的性质 2.5 二叉树的…

基于springboot+vue的二手图书交易平台(源码+论文)

文章目录 目录 文章目录 前言 一、功能设计 二、功能实现 前台系统功能模块分为 后台系统功能模块分为 三、库表设计 四、论文 前言 在互联网上所有产品的分类信息中&#xff0c;电子类的产品信息无疑是最丰富的&#xff0c;一大批电子资讯类网站从中国互联网诞生初期就开始为…

力扣模板题:回文链表

请牢记检测回文串的模板 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/ bool isPalindrome(struct ListNode* head) {int size0;struct ListNode* pointhead;while(point){size;pointpoint->next;}int arr…

每日学习-2月22日

知识点&#xff1a;二叉树 二叉树的性质&#xff1a; 二叉树的存储结构&#xff1a; 顺序存储&#xff1a; 用数组存储&#xff0c;tree[ 0 ]处空缺&#xff0c;以便编号与下标一致对标完全二叉树&#xff0c;按序号存储在相应索引的位置 i的左孩子&#xff1a;2i i的右孩…

代码随想录Leetcode416. 分割等和子集

题目&#xff1a; 代码(首刷看解析 2024年2月23日&#xff1a; class Solution { public:bool canPartition(vector<int>& nums) {/*因为数值 < 100, length < 200 , 数值 < 20000*/vector<int> dp(10001, 0);int sum accumulate(nums.begin(), num…

武汉建筑安全员ABC小题库不存在未雨绸缪“时间够”

武汉建筑安全员ABC小题库不存在未雨绸缪“时间够” 关于武汉三类人员&#xff08;安全员ABC&#xff09;考试小题库&#xff0c;一般都是考试时间出来&#xff0c;准考证下来了&#xff0c;匹配题库&#xff0c;好好看题&#xff08;认真练习即可&#xff09;&#xff0c;一般…

校园微社区微信小程序源码/二手交易/兼职交友微信小程序源码

云开发校园微社区微信小程序开源源码&#xff0c;这是一款云开发校园微社区-二手交易_兼职_交友_项目微信小程序开源源码&#xff0c;可以给你提供快捷方便的校园生活&#xff0c;有很多有趣实用的板块和功能&#xff0c;如&#xff1a;闲置交易、表白交友、疑问互答、任务兼职…

C++ 之LeetCode刷题记录(三十四)

&#x1f604;&#x1f60a;&#x1f606;&#x1f603;&#x1f604;&#x1f60a;&#x1f606;&#x1f603; 开始cpp刷题之旅。 目标&#xff1a;执行用时击败90%以上使用 C 的用户。 12. 整数转罗马数字 罗马数字包含以下七种字符&#xff1a; I&#xff0c; V&#xf…

操作系统-复试笔记

http://t.csdnimg.cn/PJLWh 操作系统基础 什么是操作系统&#xff1f; 操作系统&#xff08;Operating System&#xff0c;简称 OS&#xff09;是管理计算机硬件与软件资源的程序&#xff0c;是计算机的基石。操作系统本质上是一个运行在计算机上的软件程序 &#xff0c;用于…

Node.js中的错误处理和日志记录

在Node.js应用程序中&#xff0c;错误处理和日志记录是非常重要的方面。正确的错误处理和日志记录可以帮助我们更好地跟踪问题、排查 bug&#xff0c;并提供更好的用户体验。在本篇博客中&#xff0c;我将为大家介绍在Node.js中如何进行错误处理和日志记录&#xff0c;以及一些…

流式存储音频/视频

目录 流式存储音频/视频 1.1 具有元文件的万维网服务器 1.2 媒体服务器 1.3 实时流式协议 RTSP 使用 RTSP 的媒体服务器的工作过程 流式存储音频/视频 “存储”音频/视频文件不是实时产生的&#xff0c;而是已经录制好的&#xff0c;通常存储在光盘或硬盘中。 传统浏览器…

IO进程线程day8作业

信号灯集二次函数封装 sem.c #include<myhead.h>union semun {int val; /* Value for SETVAL */struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */unsigned short *array; /* Array for GETALL, SETALL */struct seminfo *__buf; /* B…

wps 365 批量修改.xlsx、.xls,单元格内容的格式为yyyy-mm-dd

xlsx、xls文件导入校验单元格内容格式有误&#xff0c;批量修改步骤如下 1.选中要修改内容的单元格列 2.选择数据-分列-分列&#xff0c;弹出“文本分列向导”弹窗 3.选择“下一步”——“下一步”到步骤3&#xff0c;在“列数据类型”中选中日期-YMD格式&#xff0c;点击“完成…

protobuf某音弹幕实战

声明:(如果侵犯到你的权益联系我&#xff0c;我会马上删除) 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;不提供完整代码&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由…

2.20 day2 QT

自由发挥登录窗口的应用场景&#xff0c;实现一个登录窗口界面 #include "widget.h"Widget::Widget(QWidget *parent): QWidget(parent) {//窗口相关设置this->setWindowTitle("登入页面"); //设置 窗口 标题this->setWindowIcon(QIcon("D:…

2024Python自动化测试面试必备知识点!

在准备 Python 自动化测试面试时&#xff0c;以下是一些必备的知识点&#xff0c;可以帮助您在面试中展现实力&#xff1a; 软件测试基础&#xff1a; 熟悉软件测试的基本概念&#xff0c;包括测试类型&#xff08;功能测试、性能测试、安全测试等&#xff09;、测试方法&#…