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

文章目录

    • 一, std::expected 基础概念
      • 1.1 什么是 std::expected?
      • 1.2 优势
      • 与 `std::optional` 和 `std::variant` 的区别
    • 二, 函数式写法的功能和应用
      • 2.1 `transform` : 对"成功值"进行映射
        • 基本用法
        • 完全返回不同类型
      • 2.2 `and_then` : 对"成功值"进行连续计算
      • 2.3 `transform_error` : 对"错误值"进行映射
      • 2.4 `or_else` : 对"错误值"进行连续计算
      • 小结
    • 三, 总结

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


一, std::expected 基础概念

1.1 什么是 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 优势

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

std::optionalstd::variant 的区别

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


二, 函数式写法的功能和应用

2.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: 10
  with_transform(10, 0);  // 输出: Error: Division by zero
  return 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.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;
          });

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

2.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
}

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

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

auto handleFileError = [](const std::string& err) {
    if (err == "File not found") {
        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;
}

小结

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Leetcode】732. 我的日程安排表 III

文章目录 题目思路代码复杂度分析时间复杂度空间复杂度 结果总结 题目 题目链接&#x1f517; 当 k k k 个日程存在一些非空交集时&#xff08;即, k k k 个日程包含了一些相同时间&#xff09;&#xff0c;就会产生 k k k 次预订。 给你一些日程安排 [startTime, endTime…

Tableau数据可视化与仪表盘搭建-数据连接

连接数据有三种类型 第一种&#xff0c;连接到本地文件&#xff0c;例如Excel&#xff0c;csv&#xff0c;JSON等 第二种&#xff0c;连接到数据库&#xff0c;例如MySQL 注意&#xff1a;连接到数据库要安装对应的数据库的驱动的 连接本地文件

Chapter4.2:Normalizing activations with layer normalization

文章目录 4 Implementing a GPT model from Scratch To Generate Text4.2 Normalizing activations with layer normalization 4 Implementing a GPT model from Scratch To Generate Text 4.2 Normalizing activations with layer normalization 通过层归一化&#xff08;La…

搭建开源版Ceph分布式存储

系统&#xff1a;Rocky8.6 三台2H4G 三块10G的硬盘的虚拟机 node1 192.168.2.101 node2 192.168.2.102 node3 192.168.2.103 三台虚拟机环境准备 1、配置主机名和IP的映射关系 2、关闭selinux和firewalld防火墙 3、配置时间同步且所有节点chronyd服务开机自启 1、配置主机名和…

GPIO、RCC库函数

void GPIO_DeInit(GPIO_TypeDef* GPIOx); void GPIO_AFIODeInit(void); void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct); void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct); //输出 读 uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx,…

使用JMeter玩转tidb压测

作者&#xff1a; du拉松 原文来源&#xff1a; https://tidb.net/blog/3f1ada39 一、前言 tidb是mysql协议的&#xff0c;所以在使用过程中使用tidb的相关工具连接即可。因为jmeter是java开发的相关工具&#xff0c;直接使用mysql的jdbc驱动包即可。 二、linux下安装jmet…

Launcher3主页面加载显示流程分析

布局结构 抓取布局后&#xff0c;可以看到每个图标是一个DoubleShadowBubbleTextView&#xff0c;父布局是CellLayout、workspace。 我们可以在CellLayout添加子view打印出调用堆栈信息&#xff0c;可以整体上看页面加载显示流程。 主要类 Launcher.java&#xff1a;主界面&…

开发培训:慧集通(DataLinkX)iPaaS集成平台-基于接口的连接器开发(不需要认证机制)

一、开发一个简单的应用0源&#xff0c;本实例中对接的应用不需要接口认证 1、【连接管理-自建】新建应用源&#xff0c;保存并发布 代码示例 return {$$ - >//日志打印$$.$Log.info(日志打印) } 二、使用应用&#xff0c;建立应用连接 1、实例创建&#xff0c;【连接管理…

pikachu靶场--目录遍历和敏感信息泄露

pikachu靶场—目录遍历和敏感信息泄露 目录遍历 概述 在web功能设计中,很多时候我们会要将需要访问的文件定义成变量&#xff0c;从而让前端的功能便的更加灵活。 当用户发起一个前端的请求时&#xff0c;便会将请求的这个文件的值(比如文件名称)传递到后台&#xff0c;后台再…

机器学习详解(13):CNN图像数据增强(解决过拟合问题)

在之前的文章卷积神经网络CNN之手语识别代码详解中&#xff0c;我们发现最后的训练和验证损失的曲线的波动非常大&#xff0c;而且验证集的准确率仍然落后于训练集的准确率&#xff0c;这表明模型出现了过拟合现象&#xff1a;在验证数据集测试时&#xff0c;模型对未见过的数据…

Word2Vec解读

Word2Vec: 一种词向量的训练方法 简单地讲&#xff0c;Word2Vec是建模了一个单词预测的任务&#xff0c;通过这个任务来学习词向量。假设有这样一句话Pineapples are spiked and yellow&#xff0c;现在假设spiked这个单词被删掉了&#xff0c;现在要预测这个位置原本的单词是…

#渗透测试#漏洞挖掘#WAF分类及绕过思路

免责声明 本教程仅为合法的教学目的而准备&#xff0c;严禁用于任何形式的违法犯罪活动及其他商业行为&#xff0c;在使用本教程前&#xff0c;您应确保该行为符合当地的法律法规&#xff0c;继续阅读即表示您需自行承担所有操作的后果&#xff0c;如有异议&#xff0c;请立即停…

电子应用设计方案85:智能 AI门前柜系统设计

智能 AI 门前柜系统设计 一、引言 智能 AI 门前柜系统旨在提供便捷、安全和智能的物品存储与管理解决方案&#xff0c;适用于家庭、公寓或办公场所的入口区域。 二、系统概述 1. 系统目标 - 实现无接触式物品存取&#xff0c;减少交叉感染风险。 - 具备智能识别和分类功能&am…

如何在不丢失数据的情况下从 IOS 14 回滚到 IOS 13

您是否后悔在 iPhone、iPad 或 iPod touch 上安装 iOS 14&#xff1f;如果你这样做&#xff0c;你并不孤单。许多升级到 iOS 14 beta 的 iPhone、iPad 和 iPod touch 用户不再适应它。 如果您在正式发布日期之前升级到 iOS 14 以享受其功能&#xff0c;但您不再适应 iOS 14&am…

线性代数考研笔记

行列式 背景 分子行列式&#xff1a;求哪个未知数&#xff0c;就把b1&#xff0c;b2放在对应的位置 分母行列式&#xff1a;系数对应写即可 全排列与逆序数 1 3 2&#xff1a;逆序数为1 奇排列 1 2 3&#xff1a;逆序数为0 偶排列 将 1 3 2 只需将3 2交换1次就可以还原原…

设计心得——流程图和数据流图绘制

一、流程图和数据流图 在软件开发中&#xff0c;画流程图和数据流图可以说是几乎每个人都会遇到。 1、数据流&#xff08;程&#xff09;图 Data Flow Diagram&#xff0c;DFG。它可以称为数据流图或数据流程图。其主要用来描述系统中数据流程的一种图形工具&#xff0c;可以将…

SpringBoot框架开发中常用的注解

文章目录 接收HTTP请求。RestController全局异常处理器Component依赖注入LombokDataBuildersneakyThrowsRequiredArgsConstructor 读取yml文件配置类注解 接收HTTP请求。 RequestMapping 接收HTTP请求。具体一点是 GetMapping PostMapping PutMapping DeleteMapping 一共…

ELK日志平台搭建 (最新版)

一、安装 JDK 1. 下载 JDK 21 RPM 包 wget https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.rpm2. 安装 JDK 21,使用 rpm 命令安装下载的 RPM 包&#xff1a; sudo rpm -ivh jdk-21_linux-x64_bin.rpm3. 配置环境变量 编辑 /etc/profile 文件以配置 JAVA_HO…

使用 Jupyter Notebook:安装与应用指南

文章目录 安装 Jupyter Notebook1. 准备环境2. 安装 Jupyter Notebook3. 启动 Jupyter Notebook4. 选择安装方式&#xff08;可选&#xff09; 二、Jupyter Notebook 的基本功能1. 单元格的类型与运行2. 可视化支持3. 内置魔法命令 三、Jupyter Notebook 的实际应用场景1. 数据…

AcWing-164.可达性统计(拓扑排序 + 位运算)

原题链接&#xff1a;164. 可达性统计 - AcWing题库 题目描述&#xff1a; 题目 输入格式 输出格式 数据范围 输入样例&#xff1a; 输出样例&#xff1a; 思路 AC代码&#xff1a; 题目描述&#xff1a; 题目 给定一张 &#x1d441; 个点 &#x1d440; 条边的有向无…