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.mzph.cn/news/704443.shtml

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

相关文章

合泰杯开发板HT66F2390入门教程(点亮LED灯)——获得成就:点灯大师

前言 前不久报名了合泰杯竞赛项目&#xff0c;然后手上也是有一个HT66F2390的开发板&#xff0c;我就打算先从点灯开始&#xff0c;学习一个新的芯片第一步都是先成为点灯大师。 一开始&#xff0c;我在网上搜寻了许多的代码示例&#xff0c;希望能够顺利实现LED的控制。然而&…

LeetCode第七题: 整数反转

题目描述 给你一个 32 位的有符号整数 x​ &#xff0c;返回将 x​ 中的数字部分反转后的结果。 如果反转后整数超过 32 位的有符号整数的范围 [−2^31, 2^31 − 1]​ &#xff0c;就返回 0。 假设环境不允许存储 64 位整数&#xff08;有符号或无符号&#xff09;。 示例 …

铭瑄科技——为星闪技术发展与应用带来新推力

随着智能化生活逐渐普及&#xff0c;无线通信不仅是不仅是信息时代的重要基础设施&#xff0c;而且是推动社会向智能化发展的核心力量之一&#xff0c;其中短距无线通信更是推动未来智能化发展的关键。 为积极推动未来硬件智能化、产业智能化发展&#xff0c;铭瑄正式宣布成为星…

黑马头条-day10

文章目录 app端文章搜索1、文章搜索1.1 ElasticSearch环境搭建1.2 索引库创建①需求分析②ES导入数据场景分析③创建索引和映射 1.3 索引数据同步①app文章历史数据导入ES②文章实时数据导入ES 1.4 文章搜索多条件复合查询①关键词搜索②搜索接口定义 2、搜索历史记录2.1 需求说…

积分商城管理系统的设计与实现

积分商城管理系统的设计与实现 获取源码——》公主号&#xff1a;计算机专业毕设大全

javascript给对象添加迭代器

迭代器是啥就自行百度了 为啥for…of可以遍历数组&#xff0c;为啥不能遍历对象&#xff0c;就是for…of会调用迭代器&#xff0c;而数组是内置了迭代器了&#xff0c;而对象没有内置&#xff0c;所以直接使用for…of遍历对象会报错&#xff0c;因此只用在对象的原型上面自定义…

YOLO算法改进Backbone系列之:EfficientViT

EfficientViT: Memory Effificient Vision Transformer with Cascaded Group Attention 摘要&#xff1a;视觉transformer由于其高模型能力而取得了巨大的成功。然而&#xff0c;它们卓越的性能伴随着沉重的计算成本&#xff0c;这使得它们不适合实时应用。在这篇论文中&#x…

一般情况下,硬件中使用Repeating Sequence出现波形很奇怪就是数据的周期频率和mcu运行的频率不一致导致的

一般情况下&#xff0c;出现波形很奇怪就是数据的周期频率和mcu运行的频率不一致导致的 把timer values 修改为0 1就好了&#xff0c;如果是0&#xff0c;0.1就不行&#xff0c;不会有下面的波形

YOLOv9中的“RepNCSPELAN4”结构!

RepNCSPELAN4结构出炉啦&#xff0c;收藏起来写论文用&#xff01; 1.代码&#xff1a; 代码路径&#xff1a;yolov9-main->models->common.py&#xff0c;代码如下&#xff1a; class RepNCSPELAN4(nn.Module):# csp-elandef __init__(self, c1, c2, c3, c4, c51): # …

使用EFCore连接SQLite

简介 在使用EFCore连接SQLite之前我们先来了解一下SQLite SQLite是一个轻量级、自包含、无服务器、零配置的事务性SQL数据库引擎&#xff0c;它支持SQL92标准的大多数查询语言并兼容ACID事务。具体如下&#xff1a; 轻量级&#xff1a;SQLite非常轻巧&#xff0c;它的库体积…

UE5 C++ Gas开发 学习记录(三)

添加AuraPlayerState,AuraAbilitySystemComponentBase和AuraAttributeSet 在Build.cs里添加 // Copyright Epic Games, Inc. All Rights Reserved. using UnrealBuildTool; public class MyGas : ModuleRules { public MyGas(ReadOnlyTargetRules Target) : base(Target) { P…

leetcode hot100打家劫舍三

本题是打家劫舍的变形&#xff0c;数据结构是树形。涉及到树的题目一定要想清楚树的遍历顺序&#xff08;前中后序&#xff09;。之后再考虑利用动态规划来解决。 动态规划是一直记录状态&#xff0c;我们可以根据动态规划的数组来记录变化的状态&#xff0c;最终求的自己想要…

Python字符串访问与拼接你搞懂了吗?

使用下标访问字符串&#xff0c;从0开始计数&#xff0c;-1表示最后一个字符。三种遍历字符串的方法&#xff1a;for循环、len()和enumerate()。字符串拼接只能是字符串之间使用&#xff0c;不能与数字拼接。 1.下标访问字符串 通过下标访问字符串的内容&#xff0c;下标从 0 …

Shell脚本介绍及脚本功能

文章目录 一、什么是shell二、hello word2.1 echo2.2第一个脚本 三、Bash的基本功能3.1别名3.2常用快捷键3.3输入输出3.4 输出重定向3.5 多命令执行3.6 管道符3.7 通配符和特殊符号 一、什么是shell Shell 是一个用 C 语言编写的程序&#xff0c;它是用户使用 Linux 的桥梁。S…

视频号视频下载教程:如何把微信视频号的视频下载下来

视频号下载相信不少人都多少有一些了解&#xff0c;但今天我们就来细说一下关于视频号视频下载的相关疑问&#xff0c;以及大家经常会问到底如何把微信视频号的视频下载下来&#xff1f; 视频号视频下载教程 视频号链接提取器详细使用指南&#xff0c;教你轻松下载号视频&…

Django后台管理(二)

一、自定义注册管理类介绍 官网:Django 管理站点 | Django 文档 | Django 注册模型除了使用 Django 默认的管理类admin,也可以自定义,比如: class StudentAdmin(admin.ModelAdmin):pass admin.site.register(Student, StudentAdmin)ModelAdmin 类是管理界面中模型的表示。…

功能富集分析 | GO| KEGG

写在前面 我们《复现SCI文章系列教程》专栏现在是免费开放&#xff0c;推出这个专栏差不多半年的时间&#xff0c;但是由于个人的精力和时间有限&#xff0c;只更新了一部分。后续的更新太慢了。因此&#xff0c;最终考虑后还是免费开放吧&#xff0c;反正不是什么那么神秘的东…

Linux环境下的性能分析 之 CPU篇(二)

2、CPU的使用情况分析 a、类似任务管理器的top & htop 说到对CPU的性能分析&#xff0c;大家一定不会忘记windows下那个最熟悉的工具&#xff1a;任务管理器。 有了这个玩意儿&#xff0c;我们就可以看到CPU的利用率&#xff0c;以及每一个进程所占用的CPU资源。那在Linu…

【论文精读】LLaMA1

摘要 以往的LLM&#xff08;Large Languages Models&#xff09;研究都遵从一个假设&#xff0c;即更多的参数将导致更好的性能。但也发现&#xff0c;给定计算预算限制后&#xff0c;最佳性能的模型不是参数最大的&#xff0c;而是数据更多的。对于实际场景&#xff0c;首选的…

Huggingface学习笔记

课程地址&#xff1a;【HuggingFace简明教程,BERT中文模型实战示例.NLP预训练模型,Transformers类库,datasets类库快速入门.】 什么是huggingface&#xff1f; huggingface是一个开源社区&#xff0c;提供了先进的NLP模型、数据集以及工具。 主要模型&#xff1a; 安装环境&…