C++17: 用折叠表达式实现一个IsAllTrue函数

news2024/9/16 10:51:25

前言

让我们实现一个 IsAllTrue 函数,支持变长参数,可传入多个表达式,必须全部计算为true,该函数才返回true。

本文记录了逐步实现与优化该函数的思维链,用到了以下现代C++新特性知识,适合对C++进阶知识有一定了解的人。这样一种从实际问题来学习和运用知识的过程还是挺有趣的,特此整理分享一下。

  1. 可变长参数模板 (C++11)
  2. 折叠表达式 (C++17)
  3. 条件编译 if constexpr (C++17)
  4. 类型萃取 type traits (C++11)
  5. 完美转发std::forward (C++11)
  6. 结构化绑定 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});

这个方法的实现简单易用,但是对于代码有更高追求的人并不满足于此,以上实现存在如下问题:

  1. 传入参数是一个初始化列表,需要写大括号{},不够优雅。
  2. 调用函数前计算了每一个条件表达式,但实际任意一个为false,即可返回,可能存在如下问题:
    1. 不必要的函数调用带来一定计算开销;
    2. 当前后表达式存在依赖关系时,比如 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 {
        // 否则,将其转换为bool
        return 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++知识掌握程度要求较高,过多使用也会导致代码体积膨胀。

  1. 条件编译if constexpr
    • 这个关键字用于在编译时判断是否满足条件。如果 T 是可调用对象(例如 lambda 或函数对象),则调用它并返回结果。
    • 如果 T 不是可调用对象,则将其转换为 bool
  2. 类型萃取std::is_invocable_v
    • 这是一个用于判断类型 T 是否可调用的特性。如果 T 是可调用对象,则 std::is_invocable_v<T> 返回 true
    • 需要包含 <type_traits> 头文件
  3. 完美转发 std::forward
    • std::forward<T>(value) 确保参数的完美转发,保留其左值或右值性质。
  4. 可变长参数模板:支持可变数量的参数包,语法用 T ... args表示。
  5. 折叠表达式
    • 使用了C++17中的折叠表达式 ,它会对参数从左到右进行求值。
    • 简化了可变长参数模板的使用,提供了一种简洁而直观的方式来对参数包进行展开和操作,从而避免了递归或显式循环的繁琐。
  6. 结构化绑定 std::bind :可绑定参数args到一个函数f,并返回一个可调用对象。

参考

  1. https://en.cppreference.com/w/cpp/language/fold

如果本文对您有帮助,请点赞、关注!

公众号:七昂的技术之旅
在这里插入图片描述

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

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

相关文章

2025最新:如何打造公司财务管理系统?Java SpringBoot实现,一步到位管理企业财务!

✍✍计算机毕业编程指导师** ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java…

STM32F407VET6开发板RT-Thread的移植适配

前言 最近购买了一块 STM32F407VET6开发板【魔女】&#xff0c;http://www.stm32er.com/ 通过原理图了解到&#xff0c;开发板板载 CMSIS-DAP 调试下载口&#xff0c;例程部分大部分以裸机程序为主 目标&#xff1a;打算移植适配到 RT-Thread 适配 RT-Thread RT-Thread 支持…

基于准静态自适应环型缓存器(QSARC)的taskBus万兆吞吐实现

文章目录 概要整体架构流程技术名词解释技术细节1. 数据结构2. 自适应计算队列大小3. 生产者拼接缓存4. 高效地通知消费者 小结1. 性能表现情况2. 主要改进3. 源码和发行版 概要 准静态自适应环形缓存器&#xff08;Quasi-Static Adaptive Ring Cache&#xff09;是taskBus用于…

【Python报错已解决】 No Python at ‘C:\Users...\Python Python39\python.exe’

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 前言一、问题描述1.1 报错示例1.2 报错分析1.3 解决思路 二、解决方法2.1 方法一&#xff1a;检查Python安装路径2.2 …

苍穹外卖随记(一)

黑马苍穹外卖逻辑和细节的问题和解决 1.后端查询到员工的日期信息&#xff0c;将信息进行json化传给前端时发生&#xff1a;前端收到的是不标准的日期json串。 解决&#xff1a;1.注解进行json格式化&#xff08;JsonFormat&#xff09;2. 在spring MVC中&#xff0c;通过消息…

如何验证VMWare WorkStation的安装?

如何验证VMWare WorkStation的安装&#xff1f; 右击"网络"&#xff0c;点击 打开"网络和Internet设置"&#xff0c;点击更改适配器选项&#xff0c;如果出现VMNet1和VMNet8&#xff0c;则说明安装成功。

内网穿透(cpolar实现)

目录 一、介绍 二、内网穿透工具cpolar实现 1.下载与安装 2.cpolar指定authtoken 3.获取临时域名 4.验证临时域名有效性 一、介绍 内网穿透&#xff0c;即 NAT 穿透&#xff08;Network Address Translation Traversal&#xff09;&#xff0c;是一种网络技术&#xff0…

Python爬虫使用实例-wallpaper

1/ 排雷避坑 &#x1f95d; 中文乱码问题 print(requests.get(urlurl,headersheaders).text)出现中文乱码 原因分析&#xff1a; <meta charset"gbk" />解决方法&#xff1a; 法一&#xff1a; response requests.get(urlurl,headersheaders) response.en…

java基础-IO(4)管道流 PipedOutputStream、PipedInputStream、PipedReader、PipedWriter

管道 提到管道&#xff0c;第一印象就是水管或者地下管道&#xff0c;一节接着一节&#xff0c;形如下图。 管道流 "流"从上一个管道 ——-> 下一个管道。又细分为字节流和字符流。 字节流&#xff08;PipedOutputStream、PipedInputStream&#xff09; 如果…

SSM框架介绍

SSM通常指的是三个开源框架的组合&#xff0c;即Spring、SpringMVC&#xff08;Spring Web MVC&#xff09;和MyBatis&#xff0c;这三个框架经常一起使用来开发Java企业级应用&#xff0c;特别是在Web应用开发中非常流行。 SSM框架介绍 Spring 简介&#xff1a;Spring是一个…

谷粒商城のNginx

文章目录 前言一、Nginx1、安装Nginx2、相关配置2.1、配置host2.2、配置Nginx2.3、配置网关 前言 本篇重点介绍项目中的Nginx配置。 一、Nginx 1、安装Nginx 首先需要在本地虚拟机执行&#xff1a; mkdir -p /mydata/nginx/html /mydata/nginx/logs /mydata/nginx/conf在项目…

ModuleNotFoundError: No module named ‘mmcv.transforms‘

不得已的解决方法&#xff1a; mmcv升级到2.0.0即可解决 升级后自然又面临一系列不兼容问题&#xff01; 官方文档查漏补缺

【STM32】呼吸灯实现

对应pwm概念可以去看我的博客51实现的呼吸灯 根据对应图我们可知预分频系数为999&#xff0c;重装载值为2000&#xff0c;因为设置内部时钟晶振频率为100MHZ &#xff0c;1s跳 100 000000次 &#xff0c;跳一次需要1/100 000000s 20ms0.02s 对应跳的次数为 我们使用通用定时器…

九,自定义转换器详细操作(附+详细源码解析)

九&#xff0c;自定义转换器详细操作&#xff08;附详细源码解析&#xff09; 文章目录 九&#xff0c;自定义转换器详细操作&#xff08;附详细源码解析&#xff09;1. 基本介绍2. 准备工作3. 自定义转换器操作4. 自定义转换器的注意事项和细节5. 总结&#xff1a;6. 最后&…

【前端学习】AntV G6-07 深入图形与图形分组、自定义节点、节点动画(上、中)

课程链接 AntV G6&#xff1a;深入图形与图形分组、自定义节点、节点动画&#xff08;上&#xff09;_哔哩哔哩_bilibili AntV G6&#xff1a;深入图形与图形分组、自定义节点、节点动画&#xff08;中&#xff09;_哔哩哔哩_bilibili 图形分组 Group | G6 (antgroup.com) 自…

ARM32开发——DMA内存到内存

&#x1f3ac; 秋野酱&#xff1a;《个人主页》 &#x1f525; 个人专栏:《Java专栏》《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 需求数据交互流程开发流程依赖引入DMA初始DMA传输请求完整代码 关心的内容DMA初始化DMA初始化DMA数据传输请求完整代码 DMA中断开启…

封装智能指针 qt实现登录界面

1.封装独占智能指针——unique_ptr #include <iostream> #include <utility> // For std::move// 命名空间 namespace custom_memory { template <typename T> class myPtr { public:// 使用初始化列表进行初始化explicit myPtr(T* p nullptr) noexcept : …

卡西莫多的诗文集2022-2024.9月6-校庆国庆专版定版

通过网盘分享的文件&#xff1a;卡西莫多的诗文集2022-2024.9月6-A5-校庆国庆专版-定版.pdf 链接: https://pan.baidu.com/s/1cpFK5k1baGXbSGxY30GL_A?pwdjgnt 提取码: jgnt 卡西莫多的诗文集2022-2024.9月6-校庆国庆专版&#xff0c;又稍作修改并勘误了一些错字&#xff0c;…

AWQ量化(Activation-aware Weight Quantization)

论文&#xff1a; AWQ: Activation-aware Weight Quantization for On-Device LLM Compression and Acceleration 中文解读&#xff1a; 深入理解AWQ量化技术 - 知乎 (zhihu.com) 动机&#xff1a;端侧设备用LLM&#xff0c;为了减少显存占用量&#xff0c;所以要用INT4量化&am…

【Jupyter Notebook】汉化

1.打开:Anaconda Prompt 2.输入:"activate Zhui01"(注意&#xff1a;Zhui01是刚创建的环境名字) activate Zhui01 3.输入:"pip install jupyterlab-language-pack-zh-CN" pip install jupyterlab-language-pack-zh-CN 4.打开:Jupyter Notebook 5.点击&q…