当前位置: 首页 > news >正文

解读 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.mrgr.cn/news/82584.html

相关文章:

  • 跨站脚本攻击(XSS)详解
  • SQL 幂运算 — POW() and POWER()函数用法详解
  • 使用Python类库pandas操作Excel表格
  • 【渗透测试术语总结】
  • 当算法遇到线性代数(四):奇异值分解(SVD)
  • Swift Protocols(协议)、Extensions(扩展)、Error Handling(错误处理)、Generics(泛型)
  • Linux操作系统下,挂ILA
  • LeetCode -Hot100 - 53. 最大子数组和
  • 2025/1/4期末复习 密码学 按老师指点大纲复习
  • 鸿蒙MPChart图表自定义(六)在图表中绘制游标
  • 【MySQL基础篇重点】八、复合查询
  • leetcode刷题笔记
  • iOS 逆向学习 - iOS Architecture Cocoa Touch Layer
  • 组会 | DenseNet
  • HCIA-Access V2.5_7_3_XG(S)原理_关键技术
  • sql server期末复习
  • 内部类 --- (寄生的哲学)
  • 对计网大题的一些指正(中间介绍一下CDM的原理和应用)
  • springCloud 脚手架项目功能模块:Java分布式锁
  • 对一段已知行情用python中画出K线图~
  • 从零开始RTSP协议的实时流媒体拉流(pull)的设计与实现(一)
  • 《Android最全面试题-Offer直通车》目录
  • WPS表格技巧01-项目管理中的基本功能-计划和每日记录的对应
  • GIS算法基础知识点总结
  • C++11编译器优化以及引用折叠
  • 《计算机网络A》单选题-复习题库解析-3