C++17: 用折叠表达式实现一个IsAllTrue函数
前言
让我们实现一个 IsAllTrue
函数,支持变长参数,可传入多个表达式,必须全部计算为true,该函数才返回true。
本文记录了逐步实现与优化该函数的思维链,用到了以下现代C++新特性知识,适合对C++进阶知识有一定了解的人。这样一种从实际问题来学习和运用知识的过程还是挺有趣的,特此整理分享一下。
- 可变长参数模板 (C++11)
- 折叠表达式 (C++17)
- 条件编译 if constexpr (C++17)
- 类型萃取 type traits (C++11)
- 完美转发
std::forward
(C++11) - 结构化绑定
std::bind
(C++11)
初级版本——基于初始化列表实现
可以使用初始化列表 std::initializer_list
存储多个bool变量,实现传入多个bool值的目的,这种方法实际上该函数只有一个参数,实现如下:
bool IsAllTrue(const std::initializer_list<bool>& conditions) {return std::all_of(conditions.begin(), conditions.end(), [](const bool a) {return a;});
}
使用方法如下:
int a = 1;
bool b = true;
auto c = []() {return true;}
IsAllTrue({a, b, c});
这个方法的实现简单易用,但是对于代码有更高追求的人并不满足于此,以上实现存在如下问题:
- 传入参数是一个初始化列表,需要写大括号{},不够优雅。
- 调用函数前计算了每一个条件表达式,但实际任意一个为false,即可返回,可能存在如下问题:
- 不必要的函数调用带来一定计算开销;
- 当前后表达式存在依赖关系时,比如
p && p →a
,如果p是指针且为空, 计算p→a
会导致程序崩溃。
对于不了解这个函数用法的人而言,使用这个实现是会存在一定风险的。所以我们需要想办法利用 &&
实现短路求值,以及对函数结果的延迟计算。
进阶版本——基于折叠表达式实现
折叠表达式(Fold expressions)
折叠表达式是C++17引入的新特性,可通过二元操作符折叠可变长参数模板中的参数包。这个特性的引入是为了简化C++11可变长参数模板的使用。
- 根据左右方向可分为左折叠和右折叠:
一元左折叠(Unary right fold)和一元右折叠(Unary left fold)形式如下:
( pack op... ) //一元右折叠,从右往左计算, 等同于(E1 op (... op (EN-1 op EN)))
( ... op pack ) //一元左折叠,从左往右计算, 等同于(((E1 op E2) op ...) op EN)
在大多数情况下,对于交换律成立的操作符(如 +
和 *
),左折叠和右折叠的结果是相同的。然而,对于非交换的操作符,结果可能不同,例如减法或除法。
- 根据是否有初始值可分为一元和二元:
二元折叠表达式分为:二元右折叠(Binary right fold)和 二元左折叠(Binary left fold)。
( pack op ... op init ) //二元右折叠
( init op ... op pack ) //二元左折叠
- 使用二元左折叠的例子
template<typename... Args>
void printer(Args&&... args)
{((std::cout<< args << " "), ...)<< "\n";
}
基于一元右折叠的IsAllTrue函数
基于 &&
运算符的一元右折叠(Unary right fold)实现IsAllTrue如下:
template<typename... Args>
bool IsAllTrue(Args... args) { return (std::forward<Args>(args) && ...);
}
- 注:折叠表达式的最外层括号是必须的。
但以上实现,该模板本质上仍只能支持变长的多个bool参数,这会导致先计算出bool值再传入,仍未实现函数结果的延迟计算。
使用type traits 进一步优化
如何可以实现延迟计算呢?首先我们可以明确下,传递给该函数的参数类型,可能是bool值、可以计算出bool值的表达式或可调用对象、可转换为bool值的指针和数值。
总体可分为两类,一类是可转换为bool的表达式,另一类是可计算出bool的可调用对象。
由于参数类型(bool、函数对象、指针等)和类型特征(是否可调用、是否可以转成bool)均是可以在编译期确定的。
为了避免在编译期把模板参数类型都推断为bool,可定义 IsTrue
函数模板定义表达式bool值的计算方式,使模板可以推断出原表达式自身的类型,从而可以延迟其计算过程。其中用到了编译期条件if constexpr
和 一种类型萃取是否可调用 std::is_invocable_v
,这两个均是C++17引入的特性。
如果具备可调用的特征,则进行函数调用并返回结果;否则,将其转换为bool值返回。实现如下:
template <typename T>
bool IsTrue(T&& value) {if constexpr (std::is_invocable_v<T>) {// 如果是可调用对象,调用它并返回结果return std::forward<T>(value)();} else {// 否则,将其转换为boolreturn static_cast<bool>(std::forward<T>(value));}
}
基于以上模板改写 IsAllTure
模板函数 :
template <typename... Args>
bool IsAllTrue(Args&&... args) {return (IsTrue(std::forward<Args>(args)) && ...);
}
该实现的本质是我们希望在用N个表达式传入该模板函数后,模板实例化为形同如下形式,从而可以实现短路机制:
static_cast<bool>(Expr1) && Expr2() && static_cast<bool>(Expr3) && ... && ExprN()
函数测试
对以上代码进行如下测试,注释为输出结果,可以看到,能够满足我们的需求:
auto lambdaTrue = []() { std::cout<<" lambda true"<<std::endl;return true;
};
auto lambdaFalse = []() { std::cout<<" lambda false"<<std::endl;return false;
};
class Foo {
public:int a;
};
Foo* p = nullptr;
IsAllTrue(true, lambdaTrue); // 输出lambda true
IsAllTrue(false, lambdaTrue); // 无输出,实现了短路机制以及延迟计算
IsAllTrue(p, p->a); // 正常运行,不会coredump
以上为了方便,均使用定义了无参lambda函数进行了测试。为了延迟一般含参函数的计算结果,能够方便传入带参数的函数对象,还可以基于std::bind
实现一个用于生成可调用对象的函数:
template <typename F, typename... Args>
auto make_callable(F&& f, Args&&... args) {return std::bind(std::forward<F>(f), std::forward<Args>(args)...);
}
比如:
bool less(int a, int b) {return a < b;
}IsAllTrue(true, make_callable(less, 1, 2));
完整测试代码:https://compiler-explorer.com/z/fTvq7Y36Y
知识总结
本文使用了以下C++知识实现了一个高效的IsAllTrue函数,优点为它的使用安全且较为高效,缺点在于代码实现较为复杂,对C++知识掌握程度要求较高,过多使用也会导致代码体积膨胀。
- 条件编译
if constexpr
:- 这个关键字用于在编译时判断是否满足条件。如果
T
是可调用对象(例如lambda
或函数对象),则调用它并返回结果。 - 如果
T
不是可调用对象,则将其转换为bool
。
- 这个关键字用于在编译时判断是否满足条件。如果
- 类型萃取
std::is_invocable_v
:- 这是一个用于判断类型
T
是否可调用的特性。如果T
是可调用对象,则std::is_invocable_v<T>
返回true
。 - 需要包含 <type_traits> 头文件
- 这是一个用于判断类型
- 完美转发
std::forward
:std::forward<T>(value)
确保参数的完美转发,保留其左值或右值性质。
- 可变长参数模板:支持可变数量的参数包,语法用
T ... args
表示。 - 折叠表达式
- 使用了C++17中的折叠表达式 ,它会对参数从左到右进行求值。
- 简化了可变长参数模板的使用,提供了一种简洁而直观的方式来对参数包进行展开和操作,从而避免了递归或显式循环的繁琐。
- 结构化绑定
std::bind
:可绑定参数args到一个函数f,并返回一个可调用对象。
参考
- https://en.cppreference.com/w/cpp/language/fold
如果本文对您有帮助,请点赞、关注!
公众号:七昂的技术之旅
相关文章:

C++17: 用折叠表达式实现一个IsAllTrue函数
前言 让我们实现一个 IsAllTrue 函数,支持变长参数,可传入多个表达式,必须全部计算为true,该函数才返回true。 本文记录了逐步实现与优化该函数的思维链,用到了以下现代C新特性知识,适合对C进阶知识有一定…...

【IPV6从入门到起飞】2-2 获取你的IPV6(Teredo隧道)
【IPV6从入门到起飞】2-2 获取你的IPV6(Teredo隧道) 1 打工人的忧伤2 Teredo介绍2.1 背景2.2 工作原理 3 Linux 服务器获取IPV63.1 安装3.2 设置开机自启动和启动3.3 开放防火墙 UDP 35443.4 查看IPV6以及ping包测试3.5 修改Teredo服务器3.6 重启服务3.7…...
Linux 安全弹出外接磁盘
命令行操作 首先,需要卸载硬盘上的所有分区,可以使用umount来卸载分区 清空系统缓存,将所有的数据写入磁盘 sync 列出已挂载的文件系统 使用lsblk或者df命令来查找要卸载的分区 lsblk or df -h确保没有文件正在使用 使用lsof 命令来…...
面试准备-6
NIO底层是用Selector、Channel和ByteBuffer来实现的。主线程在循环使用select方法进行阻塞等待,当有acceptable(可连接)、readable(可读)或者writable(可写)事件发生的时候,循环就会…...

context canceled 到底谁在作祟?
一、背景 在工作中,因报警治理标准提高,在报警治理的过程中,有一类context cancel报警渐渐凸显出来。 目前context cancel日志报警大致可以分为两类。 context deadline exceeded 耗时长有明确报错原因 context canceled 耗时短无明确报错…...
windows C++ 虚拟内存的按需调拨
虚拟内存的按需调拨 windows C 虚拟内存的按需调拨 文章目录 虚拟内存的按需调拨虚拟内存的按需调拨 虚拟内存的按需调拨 /*------------------------------------------------------------------------24-SEHAndMemory.cpp演示虚拟内存的按需调拨--------------------------…...

[杂项]pugi::xml获取xml中的注释节点
前言 想到学习xml时的一句话,xml中注释也会被算作一个节点。那么我们就可以通过 pugixml 把注释节点获取出来, <?xml version"1.0"?> <mesh name"mesh_root"><!--这是一个注释节点-->some text<
Spring Boot Admin集成与自定义监控告警
目录 一.Spring Boot Admin集成 1.引入依赖 2.添加配置 3.监控界面 二.Spring Boot Admin告警机制 1. 基本告警机制 2. 配置告警 2.1 triggers触发器讲解 3. 自定义通知 3.1 Instance 对象 三.Spring Boot Admin支持的监控属性 1.常见的Spring Boot Admin监控属性 …...

如何恢复回收站中已删除/清空的文件
回收站清空后如何恢复已删除的文件?是否可以恢复永久删除的文件?或者最糟糕的是,如果文件直接被删除怎么办?本文将向您展示清空回收站后恢复已删除数据的最佳方法。 回收站清空后如何恢复已删除的文件? “回收站清空后…...

玩短视频素材都是在哪里找的?推荐几个热门的短视频素材下载渠道
亲爱的短视频创作爱好者们,你是否在寻找视频素材时感到苦恼,觉得选择有限?别担心,今天我要为大家介绍几个超级实用的视频素材下载平台,帮助你的视频创作事半功倍! 蛙学网 我们首先要重点推荐的是蛙学网&am…...

ThinkPHP5 5.0.23-rce远程代码执行漏洞复现
启动环境,先关闭其他环境 启动 判断是否存在漏洞:访问/index.php?scaptcha页面,会出现报错 使用HackBar 插件发送 POST 请求 _method__construct&filter[]system&methodget&server[REQUEST_METHOD]dir 通过echo命令写入 Webshe…...

windows下安装并使用nvm
目录 一.准备工作:卸载node 卸载步骤 二.下载nvm 三.安装nvm 三.配置下载源【重要】 四.使用nvm安装node.js 五.nvm常用命令 六.卸载nvm 一.准备工作:卸载node 如果电脑上已经有node,那么我们需要先完全卸载node,再安装…...
mac m2 安装 nvm
踩坑-填坑 过程 红字都是 启动台-ohter-终端 里面直接输入就行了 /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)" brew -v 重启终端 brew uninstall nvm brew install nvm 成功提示 > Summary 🍺 /o…...

通信工程学习:什么是AN接入网络
AN接入网络 AN接入网络,全称Access Network,是电信部门业务节点与用户终端设备之间的实施系统。它可以部分或全部代替传统的用户本地线路网,并可包括复用、交叉连接和传输功能。以下是关于AN接入网络的详细解释: 一、AN接入网络的…...

MSCKF7讲:特征管理与优化
MSCKF7讲:特征管理与优化 文章目录 MSCKF7讲:特征管理与优化1 Feature.h2 OptimizationConfig3 initializePosition三角化LM优化3.1 计算归一化坐标深度初值generateInitialGuess① 理论推导② 代码分析 3.2 计算归一化误差cost① 理论推导② 代码分析 3…...
C# XML 使用教程
C# XML 使用教程 目录 C# XML 使用教程XML 是什么介绍组成XML 与 HTML 的区别 C# 中如何使用 XML序列化根元素子元素序列化方法 反序列化反序列化方法 序列化与反序列化实例 XML 是什么 介绍 可扩展标记语言 (Extensible Markup Language, XML) ,标准通用标记语言的…...

淘宝开放平台交易类API解析以及如何测试?
调用淘宝开放平台的订单接口,主要可以通过以下几种途径进行: 1. 直接使用淘宝开放平台提供的API接口 步骤概述: 注册淘宝开放平台账号:首先,你需要在淘宝开放平台注册一个开发者账号。创建应用:在注册并…...

基于聚类与LSTM对比特币价格深度分析与预测
1.项目背景 比特币作为全球最具影响力的加密货币之一,其价格受到多种复杂因素的共同作用,包括市场情绪、政策变化、大型机构的投资行为等,这些因素在不同的市场阶段对比特币价格波动产生直接或间接的影响。通过对比特币市场的深入分析&#…...

YOLOv9改进策略【Neck】| 使用CARAFE轻量级通用上采样算子
一、本文介绍 本文记录的是利用CARAFE上采样对YOLOv9的颈部网络进行改进的方法研究。YOLOv9采用传统的最近邻插值的方法,仅考虑子像素邻域,无法捕获密集预测任务所需的丰富语义信息,从而影响模型在密集预测任务中的性能。CARAFE通过在大感受…...

SpringMVC上
SpringMVC介绍 MVC模型 MVC全称Model View Controller,是一种设计创建Web应用程序的模式。这三个单词分别代表Web应用程序的三个部分: Model(模型):指数据模型。用于存储数据以及处理用户请求的业务逻辑。在Web应用…...
OpenLayers 可视化之热力图
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 热力图(Heatmap)又叫热点图,是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...

vscode(仍待补充)
写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh? debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...

【网络安全产品大调研系列】2. 体验漏洞扫描
前言 2023 年漏洞扫描服务市场规模预计为 3.06(十亿美元)。漏洞扫描服务市场行业预计将从 2024 年的 3.48(十亿美元)增长到 2032 年的 9.54(十亿美元)。预测期内漏洞扫描服务市场 CAGR(增长率&…...
oracle与MySQL数据库之间数据同步的技术要点
Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异,它们的数据同步要求既要保持数据的准确性和一致性,又要处理好性能问题。以下是一些主要的技术要点: 数据结构差异 数据类型差异ÿ…...

学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1
每日一言 生活的美好,总是藏在那些你咬牙坚持的日子里。 硬件:OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写,"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...

并发编程 - go版
1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
Python Einops库:深度学习中的张量操作革命
Einops(爱因斯坦操作库)就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库,用类似自然语言的表达式替代了晦涩的API调用,彻底改变了深度学习工程…...