c++oding="utf-8" ?>
std::expected 不是 optional 的升级版,它专为携带错误上下文的业务异常设计,而 optional 仅表示值是否存在;expected 要求错误类型可复制/移动且非 void,不支持隐式转 bool,强制显式错误检查,并天然支持分类错误和链式传播。

std::expected 不是 optional 的升级版,它解决的是完全不同的问题:optional 回答“有没有值”,expected 回答“成功没?如果没,为什么?”——业务异常逻辑必须带上下文,否则调用方只能猜错因。
std::expected 为什么比 std::optional 更适合业务异常
用 std::optional 表达失败,本质是把错误语义硬塞进空值语义里。比如除零返回 std::optional,失败时是 std::nullopt,但调用方根本不知道这是参数非法、溢出、还是配置加载失败。
-
std::expected明确携带错误原因,比如std::unexpected("division by zero at line 42") -
std::expected要求错误类型E可复制/可移动,且不能是void(否则该用std::optional) -
std::optional支持隐式转bool,std::expected不支持——必须显式调用.has_value()或用结构化绑定,避免静默忽略错误 - 业务函数若存在多种可分类失败路径(如“文件不存在”“权限不足”“JSON 格式错误”),
std::expected天然支持枚举类错误类型,std::optional完全无法表达差异
and_then 链式调用失效的常见原因
链式传播失败的前提是每一步都返回 std::expected。一旦中间某步返回裸值、bool 或 std::optional,and_then 就会编译失败,报错类似 "no matching function for call to 'and_then'"。
- 正确写法:
parse_json(s).and_then(validate).and_then(save_to_db),三个函数都返回std::expected - 错误写法:
parse_json(s).and_then([](auto&& j) { return validate(j) ? true : false; })—— 返回bool,类型不匹配 - 别在
and_then的 lambda 里抛异常;若构造std::unexpected时其E类型构造函数抛异常,会直接穿透出去,破坏预期控制流 - 纯转换操作(如
int → std::string)该用transform,不是and_then;后者专用于“下一步也可能失败”的场景
封装文件打开时最容易漏掉的三件事
很多人以为 std::expected<:fstream std::string> 封装完构造函数就完了,其实底层状态机比表面复杂得多。
- 必须检查
f.is_open()—— 即使没抛异常,std::fstream构造也可能失败并静默置位failbit - 捕获顺序不能错:
std::filesystem::filesystem_error必须在std::ios_base::failure之前,否则会被后者吞掉(前者继承自后者) - 仅靠
is_open() == false无法区分“路径不存在”“不是普通文件”“权限拒绝”,得配合std::filesystem::status()或errno补充判断
C++23 标准下 std::expected 的真实兼容性底线
它不是语法糖,是带严格约束的类型。当前(2026 年 4 月)完整支持需同时满足:
- C++23 标准 +
-std=c++23 - GCC 13+ / Clang 16+ / MSVC 19.35+
- 标准库实现必须完整(libstdc++、libc++、MSVC STL 均已达标,但旧版本或嵌入式 STL 可能缺失)
-
std::expected合法,但and_then在此情形下无法推导返回类型,慎用;推荐统一用std::expected,哪怕T是std::monostate
最易被忽略的一点:所有 I/O 函数(包括 std::fstream、std::filesystem::ifstream)至今仍不原生返回 std::expected,你写的每一行封装,都是手动把异常/状态码映射成 std::unexpected——没有自动转换,也没有运行时魔法。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











