解读 C++23 std::expected 函数式写法

文章目录

    • `std::expected` 基础概念
      • 什么是 std::expected?
      • 优势
      • 与 `std::optional` 和 `std::variant` 的区别
    • 函数式写法的功能和应用
      • 1. `transform` : 对"成功值"进行映射
        • 基本用法
        • 完全返回不同类型
      • 2 `and_then` : 对"成功值"进行连续计算
      • 3 `transform_error` : 对"错误值"进行映射
      • 4 `or_else` : 对"错误值"进行连续计算
      • 5. 小结
    • 总结
    • 参考链接
    • 源码链接

C++23 带来了一个重要的新功能—std::expected, 它提供了一种现代化的错误处理方式, 用于表示操作成功的返回值或失败的错误状态. 相比于传统的异常和错误码处理, std::expected 提供了更安全, 更便为, 更类似函数式编程的解决方案. 这篇博客将进一步探索它的基础概念和函数式写法.


std::expected 基础概念

什么是 std::expected?

std::expected 是一个模板类:

template <typename T, typename E>
class std::expected;
  • T: 用于表示成功情况下的返回值类型.
  • E: 用于表示失败情况下的错误值类型.

std::expected 会保存操作的两种状态: 成功或错误. 你可以通过下列方法进行状态检查:

  • has_value() : 判断是否包含成功值.
  • error() : 返回错误值.
  • value() : 返回成功值, 如果存在错误, 则抱押异常.

以下是一个基础示例:

#include <expected>
#include <iostream>
#include <string>std::expected<int, std::string> divide(int a, int b) {if (b == 0) {return std::unexpected("Division by zero");}return a / b;
}int main() {if (auto result = divide(10, 2); result) {std::cout << "Result: " << *result << '\n';} else {std::cout << "Error: " << result.error() << '\n';}if (auto result = divide(10, 0); result) {std::cout << "Result: " << *result << '\n';} else {std::cout << "Error: " << result.error() << '\n';}return 0;
}

优势

  1. 明确的错误处理: 强制检查成功或失败状态.
  2. 类型安全: 避免优化常规问题和异常处理的问题.
  3. 提高可读性: 代码更为清晰明业.

std::optionalstd::variant 的区别

std::optional: 仅能表示值的存在或不存在, 无法描述失败的具体原因.
std::variant: 是多态类型容器, 可以容纳多种类型, 但不限定成功和错误的语义.
std::expected: 专门用于表示操作成功或失败, 语义明确, 适合函数式错误处理.


函数式写法的功能和应用

1. transform : 对"成功值"进行映射

基本用法

transform 可以将 expected<T, E> 中的成功值转换为另一个类型, 并返回新的 expected<U, E>.

如果存在错误, 就直接跳过转换, 保留原错误.

示例:

#include <expected>
#include <iostream>
#include <string>std::expected<int, std::string> divide(int a, int b) {if (b == 0) {return std::unexpected("Division by zero");}return a / b;
}void with_transform(int a, int b) {auto result = divide(a, b).transform([](int value) {return value * 2;  // 成功值 * 2});if (result) {std::cout << "Success: " << *result << '\n';  // Success: 10} else {std::cout << "Error: " << result.error() << '\n';}
}int main() {with_transform(10, 2);  // 输出: Success: 10with_transform(10, 0);  // 输出: Error: Division by zeroreturn 0;
}
完全返回不同类型

transform 也支持将成功值转换为全新的类型:

auto process = divide(10, 2).transform([](int value) {return std::to_string(value);      // int -> string}).transform([](const std::string& str) {return str.size();                 // string -> size_t});
// 结果为 expected<size_t, std::string>

2 and_then : 对"成功值"进行连续计算

如果需要在成功值上再调用一个返回 std::expected<...> 类型的函数, 可以使用 and_then.

示例:

std::expected<std::string, std::string> intToString(int x) {return std::to_string(x);
}auto result = divide(10, 2).and_then([](int value) {return intToString(value); // 调用另一个返回 expected的函数});
// result 类型为 expected<std::string, std::string>

如果需要连续计算, 可通过链式调用:

  // 可以连续计算多次auto finalResult =divide(10, 2).and_then([](int value) {return divide(value, 2);  // 再次除法}).and_then([](int newValue) -> std::expected<int, std::string> {if (newValue == 2) {return std::unexpected("We don't like the value 2!");}return newValue * 10;});

任何一步出现错误, 后续操作都会被跳过.

3 transform_error : 对"错误值"进行映射

transform_error 用于对错误状态中的值进行转换:

auto result = divide(10, 0).transform_error([](const std::string& err) {return "[Transformed Error] " + err;});if (!result) {std::cout << result.error() << std::endl;// 输出: [Transformed Error] Division by zero
}

4 or_else : 对"错误值"进行连续计算

对错误值进行连续操作, 可以使用 or_else:

#include <expected>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>std::expected<std::string, std::string> openFile(const std::string& file) {std::ifstream inFile(file, std::ios::in);namespace fs = std::filesystem;// 检查文件是否存在if (!fs::exists(file)) {return std::unexpected("File does not exist.");}if (!inFile.is_open()) {return std::unexpected("Failed to open file: ");  // 返回错误信息}// 读取文件内容std::ostringstream content;content << inFile.rdbuf();return content.str();  // 返回文件内容
}int main() {auto handleFileError =[](const std::string& err) -> std::expected<std::string, std::string> {if (err == "File does not exist.") {return "Default file content";  // 试图读取默认文件}return std::unexpected(err);};auto content = openFile("somefile.txt").or_else(handleFileError);if (!content) {std::cerr << "Error: " << content.error() << std::endl;} else {std::cout << "Success: " << *content << std::endl;}return 0;
}

5. 小结

transform: 对成功值做"映射" (map), 从 expected<T, E> 得到 expected<U, E>.
and_then: 对成功值做"继续计算" (flatMap), 从 expected<T, E> 得到 expected<U, E>, 而不是嵌套的 expected<expected<...>>.
transform_error: 对错误值做"映射", 从 expected<T, E> 得到 expected<T, E2>.
or_else: 对错误值做"继续计算", 从 expected<T, E> 得到新的 expected<T, E>.

这些函数式的组合子让我们在处理多步骤, 且随时可能失败的逻辑时, 代码既能保持简洁, 可读, 又不会丢失错误信息. 任何一步返回错误, 后面的步骤都自动跳过, 错误将直接沿着链路返回给调用端. 这种写法在实际项目中非常有用, 也能减少传统层层 if 检查或异常捕获的繁琐, 使得逻辑更加清晰.


总结

C++23 中的 std::expected 与之配契的函数式结构, 不仅使得代码更为简洁, 还能最大化降低错误处理的应用过过. 通过链式写法, 与成功和失败相关的各种操作可以以一种清晰的方式表达. 日后在处理多步骤, 随时可能失败的计算时, 它将成为你工具箱中不可我缺的一环.

参考链接

  • std::expected - cppreference.com - C++参考手册

源码链接

源码链接

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

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

相关文章

Web安全扫盲

1、建立网络思维模型的必要 1 . 我们只有知道了通信原理&#xff0c; 才能够清楚的知道数据的交换过程。 2 . 我们只有知道了网络架构&#xff0c; 才能够清楚的、准确的寻找漏洞。 2、局域网的简单通信 局域网的简单通信&#xff08;数据链路层&#xff09; 一般局域网都通…

领域驱动设计(4)—绑定模型与实现

&#xff08;4&#xff09;—绑定模型与实现 模式&#xff1a;MODEL-DRIVEN DESIGN为什么模型对用户至关重要?模式&#xff1a;HANDS-ON MODELER 很多项目设计之初只考虑到模型如何设计&#xff0c;没有将模型如何实现、数据关系如何存储这些实现考虑在内&#xff0c;往往设计…

@MapperScan

简介&#xff1a; MapperScan注解是MyBatis框架在Spring Boot中的一个重要集成注解 作用&#xff1a; MapperScan主要作用是告诉Spring框架在启动时扫描指定的包路径&#xff0c;并将该路径下的所有MyBatis的Mapper接口批量注入到Spring容器中。这样&#xff0c;开发者就可以…

Linux驱动开发(18):linux驱动并发与竞态

并发是指多个执行单元同时、并行执行&#xff0c;而并发的执行单元对共享资源(硬件资源和软件上的全局变量、静态变量等)的访问 则很容易导致竞态。对于多核系统&#xff0c;很容易理解&#xff0c;由于多个CPU同时执行&#xff0c;多个CPU同时读、写共享资源时很容易造成竞态。…

009:传统计算机视觉之边缘检测

本文为合集收录&#xff0c;欢迎查看合集/专栏链接进行全部合集的系统学习。 合集完整版请参考这里。 本节来看一个利用传统计算机视觉方法来实现图片边缘检测的方法。 什么是边缘检测&#xff1f; 边缘检测是通过一些算法来识别图像中物体之间或者物体与背景之间的边界&…

QML使用Popup实现弹出Message

方案一&#xff1a;popup import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15ApplicationWindow {visible: truewidth: 640height: 480title: qsTr("Top Message Popup Example")ColumnLayout {anchors.centerIn: parentspacing: 10Butt…

idea java.lang.OutOfMemoryError: GC overhead limit exceeded

Idea build项目直接报错 java: GC overhead limit exceeded java.lang.OutOfMemoryError: GC overhead limit exceeded 设置 编译器 原先heap size 设置的是 700M , 改成 2048M即可

webpack5基础(上篇)

一、基本配置 在开始使用 webpack 之前&#xff0c;我们需要对 webpack 的配置有一定的认识 1、5大核心概念 1&#xff09;entry &#xff08;入口&#xff09; 指示 webpack 从哪个文件开始打包 2&#xff09;output&#xff08;输出&#xff09; 制视 webpack 打包完的…

boot-126网易邮件发送

【SpringBoot整合JavaMail发送邮件】 一 . Java Mail基本概念 1.SMTP Simple Mail Transfer Protocol:简单邮件传输协议&#xff0c;用于发送邮件的协议。 2.POP3 Post office Protocol 3:邮局通讯协议第三版&#xff0c;用于接收邮件的标准协议。 3.IMAP Internet Message Acc…

《学校一卡通管理系统》数据库MySQL的设计与实现

引言:学校一卡通管理系统旨在为学校提供一个高效的数字化管理平台,集中管理学生和教职工的账户、充值、消费、查询等日常事务。通过该系统,学生可以便捷地进行充值、消费及查看余额,管理员则可以高效地管理用户账户、充值记录、消费记录等数据。系统采用MySQL数据库,通过视…

【ArcGISPro/GeoScenePro】检查多光谱影像的属性并优化其外观

数据 https://arcgis.com/sharing/rest/content/items/535efce0e3a04c8790ed7cc7ea96d02d/data 操作 其他数据 检查影像的属性 熟悉检查您正在使用的栅格属性非常重要。

音视频入门基础:MPEG2-PS专题(4)——FFmpeg源码中,判断某文件是否为PS文件的实现

一、引言 通过FFmpeg命令&#xff1a; ./ffmpeg -i XXX.ps 可以判断出某个文件是否为PS文件&#xff1a; 所以FFmpeg是怎样判断出某个文件是否为PS文件呢&#xff1f;它内部其实是通过mpegps_probe函数来判断的。从《FFmpeg源码&#xff1a;av_probe_input_format3函数和AVI…

[Python学习日记-74] 面向对象实战2——选课系统

[Python学习日记-74] 面向对象实战2——选课系统 简介 开发要求 实现&#xff1a;选课系统 简介 在前面的《年会答题系统》当中我们介绍了面向对象软件开发的一些流程&#xff0c;当然这一流程只是涵括了大部分的&#xff0c;目前在业界也没有一个统一的标准&#xff0c;每个…

用户注册模块(芒果头条项目进度4)

1 创建⽤户模块⼦应⽤ 1.1 在项⽬包⽬录下 创建apps的python包。 1.2 在apps包下 创建应⽤userapp $ cd 项⽬包⽬录/apps $ python ../../manage.py startapp userapp 1.3 配置导包路径 默认情况下导包路径指向项⽬根⽬录 # 通过下⾯语句可以打印当前导包路径 print(sys.pa…

5. C语言 常量与宏定义

本章目录: 前言一、什么是常量&#xff1f;1. 整型常量2. 浮点型常量3. 字符常量4. 字符串常量 二、如何定义常量&#xff1f;1. 使用 #define 宏定义2. 使用 const 关键字 三、#define 和 const 的区别四、使用建议1. 使用场景区分2. 避免宏定义的潜在问题 五、特殊用法与小技…

随机置矩阵列为0[矩阵乘法pytorch版]

文章目录 1. 举例&#xff1a;2. python 代码 1. 举例&#xff1a; A [ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 ] , r a n d [ 0 , 5 , 2 ] → A [ 0 1 0 3 4 0 6 7 0 9…

Elasticsearch:利用 AutoOps 检测长时间运行的搜索查询

作者&#xff1a;来自 Elastic Valentin Crettaz 了解 AutoOps 如何帮助你调查困扰集群的长期搜索查询以提高搜索性能。 AutoOps 于 11 月初在 Elastic Cloud Hosted 上发布&#xff0c;它通过性能建议、资源利用率和成本洞察、实时问题检测和解决路径显著简化了集群管理。 Au…

uniapp实现后端数据i18n国际化

1.在main.js配置请求获取到数据再设置到i18n中&#xff0c; 我这里是通过后端接口先获取到一个多个数据的的json链接&#xff0c;通过链接再获取数据&#xff0c;拿到数据后通过遍历的方式设置i18n //接口数据示例&#xff1a;{"vi": "http://localhost:8899/…

关于Flutter应用国际化语言的设置

目录 1. Locale配置 2. 用户切换/启动自动加载缓存里面的locale 由于最近在开发app国际化设置的时候遇到一些问题&#xff0c;所以做出一些总结。 1. Locale配置 具体的初始化配置可以参考文档&#xff1a;i18n | Flutter 中文文档 - Flutter 中文开发者网站 - Flutter 值得…

基层医联体医院患者历史检验检查数据的快速Python编程分析

​​​​​​​ 一、引言 1.1 研究背景与意义 在当今数字化医疗时代,医疗数据呈爆炸式增长,涵盖患者的基本信息、病史、检验检查结果、治疗方案等各个维度。这些海量且复杂的数据蕴含着巨大价值,为精准医疗决策提供了关键依据。通过对患者历史检验检查数据的深入对比分析…