std::expected 本身不处理逻辑跳转,关键靠 and_then 和 or_else 链式调用实现可预测的错误传播;transform 仅映射值、不参与控制流,易致编译失败或异常崩溃,应改用类型安全的 and_then。

std::expected 本身不处理逻辑跳转,它只是让“跳转”变得可预测、可追踪、可组合——关键在 and_then 和 or_else 的链式使用方式,而不是靠它自动跳过失败步骤。
为什么 transform 会破坏错误传播链
常见错误现象是把 transform 当成“不管成功失败都执行”的万能转换器。但它对 std::unexpected 值不做任何处理,直接透传错误,而 lambda 仍会被调用(如果值存在);更危险的是,lambda 内部若抛异常,整个链就崩了。
-
transform只做值映射,不参与错误控制流——它不检查has_value()就直接调用 lambda,一旦上游返回std::unexpected,lambda 参数类型不匹配,编译失败 - 正确做法是用
and_then:它只在has_value() == true时调用 lambda,并要求 lambda 返回std::expected,否则编译报错 - 示例对比:
open_file("x.json").transform(parse_json)❌ 编译不过(parse_json接收int,但open_file返回std::expected<int std::error_code></int>)open_file("x.json").and_then(parse_json)✅ 类型安全,失败时短路
and_then 链式调用的三个硬性约束
链式调用不是写得长就有用,and_then 对 lambda 返回类型、错误类型一致性、移动语义有明确要求,漏掉任一条件都会编译失败或运行时 UB。
- lambda 必须返回
std::expected<u e></u>,且E必须与外层一致(比如都是std::error_code),不能混用std::string和枚举 - 中间步骤若返回不可移动类型(如
std::ifstream),必须包装为std::unique_ptr<:ifstream></:ifstream>或自定义 RAII 类,否则and_then内部移动构造失败 - 不要在 lambda 中捕获局部
std::error_code引用——and_then可能延迟执行,引用早已失效;应直接构造std::unexpected(ec)
如何避免嵌套过深又不失错误上下文
写成 a().and_then(b).and_then(c).and_then(d) 看似线性,但一旦某步需分支处理(比如文件不存在就用默认配置),强行塞进 and_then 会导致逻辑扭曲或重复构造 std::unexpected。
- 优先提取公共错误类型,例如统一用
enum class io_errc { not_found, permission_denied, io_error },再通过std::make_error_code转换,避免跨平台errno解释歧义 - 复杂分支用
or_else替代 if-else:比如read_config().or_else([](auto ec) { return fallback_config(); }),保持链式结构不中断 - 真正需要多路分发时,别硬撑链式——用
if (result.has_value()) { ... } else { switch (result.error()) { ... } }更直白,也更容易调试
最易被忽略的一点:std::expected 的错误对象不是“日志”,而是控制流的一部分。你把它设计得多精细,后续每层 and_then 就得多一份类型适配负担。轻量级错误码(std::error_code)+ 明确的枚举语义,比堆一个带 std::string 和堆栈跟踪的错误对象更可靠——尤其当它要跨 DLL 边界或进嵌入式环境时。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











