std::expected是C++23引入的同步错误处理工具,仅适用于预期失败且需显式处理的场景;需满足编译器版本、-std=c++23、#include 、T/E可移动且不相同等约束,误用and_then/transform或error_code构造将导致错误丢失或语义错误。

std::expected 不是用来“替代异常”的通用错误处理方案,它只适合那些你明确预期会失败、且调用方必须显式响应的同步操作——比如打开文件、解析 JSON、读取配置。用错场景(比如在析构函数里、或想靠它捕获 std::ios_base::failure)反而会让代码更脆弱。
编译通不过?先确认 C++23 和头文件
第一反应不是写错了,而是编译器根本没见过 std::expected。它只在 C++23 标准中正式引入,不是实验特性。
- 编译器版本要够新:
g++13+、clang16+、MSVC 19.35+(VS 2022 17.5+) - 编译参数必须加:
-std=c++23(GCC/Clang)或/std:c++23(MSVC) - 头文件是
#include <expected></expected>,不是<optional></optional>或<variant></variant> - 别用
std::experimental::expected:那是旧 TS 版本,接口不兼容,已废弃
返回 std::expected<t e></t> 时,T 和 E 的类型不能乱选
模板参数有硬性约束,不是随便两个类型都能塞进去。
-
T和E都必须可移动(movable),否则编译失败 -
E不能是void,也不能和T相同(std::expected<int int></int>不合法) -
T可以是void(表示“只关心是否成功”,如std::expected<void std::error_code></void>),但E必须有意义 - 别直接用
std::ifstream作T:它不可移动,得换成std::unique_ptr<:ifstream></:ifstream>或自定义 RAII 封装类
and_then 不是 transform,用错就丢错误
链式调用里最容易混淆这两个函数,结果是错误被吞掉、日志查不到源头、调试抓瞎。
-
and_then只在有值时执行回调,且回调**必须返回另一个std::expected**;上游错误会原样透传 -
transform无论成功失败都执行回调,返回值会被自动包进std::expected,错误可能被覆盖或转成无关类型 - 错误示例:
read_file().transform(parse_json)—— 即使read_file()失败,parse_json仍会被调用,且返回一个空的std::expected - 正确写法:
read_file().and_then(parse_json)——parse_json只在文件读取成功后才调用
构造 std::error_code 时 category 漏不得
直接用裸数字 + std::generic_category() 看似能过编译,但跨平台时语义就错乱了。
- 系统级 I/O 错误(如
open()、read())该用std::system_category(),不是std::generic_category() - 优先使用
std::errc枚举:std::make_error_code(std::errc::no_such_file_or_directory) - 自定义错误必须注册独立的
std::error_category子类,不能复用标准 category - 对接 C API 时,先检查返回值是否为 -1,再读
errno,并立刻封装:std::error_code{errno, std::system_category()}
最常被忽略的一点:std::expected::value() 和 error() 都不标 noexcept,在析构函数或 noexcept 函数里调用它们,等于主动埋雷。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











