std::expected链式调用需每个函数返回std::expected,用and_then串联,失败时短路透传错误;错误类型e须统一或经transform_error转换,禁止返回裸值或throw异常。

std::expected 的基本链式调用怎么写
直接用 and_then 实现函数链式传递,前提是每个环节都返回 std::expected。它不像 std::optional 那样只表示“有/无”,而是明确区分“值”和“错误”,所以链式逻辑必须显式处理失败路径。
常见错误是把普通函数(比如返回 int 或抛异常的)硬塞进链里——and_then 只接受返回 std::expected 的可调用对象,否则编译报错:no matching function for call to 'and_then'。
- 每个中间函数必须返回
std::expected<t e></t>,不能是T或std::expected<t void></t>(C++23 不支持 error type 为void) - 错误类型
E在整条链中最好统一,否则and_then内部类型推导会失败 - 如果某步想提前终止并返回错误,直接 return
std::unexpected{e},别 throw
示例:读文件 → 解析 JSON → 提取字段
auto load_and_parse = [](std::string_view path)
-> std::expected<json std::error_code> {
auto data = read_file(path); // 返回 expected<:string std::error_code>
if (!data) return std::unexpected(data.error());
auto j = json::parse(data.value()); // 假设 parse 返回 expected<json parse_error>
if (!j) return std::unexpected(std::error_code{});
return j.value();
};
auto result = std::expected<:string std::error_code>{"config.json"}
.and_then(load_and_parse)
.and_then([](const json& j) -> std::expected<int std::error_code> {
if (!j.contains("timeout"))
return std::unexpected(std::make_error_code(std::errc::invalid_argument));
return j["timeout"].get_int();
});
</int></:string></json></:string></json>
错误类型不一致时怎么合并或转换
链中不同步骤用不同错误类型(比如 std::error_code、std::string、自定义 enum)会导致 and_then 编译失败——模板参数无法统一推导。
根本原因:C++23 的 std::expected 没有内置错误映射机制,and_then 的返回类型必须严格匹配当前 expected 的 error type,否则类型不兼容。
- 最稳妥做法:从头约定一个公共错误类型,例如
enum class errc { file_not_found, parse_failed, missing_field } - 若已存在异构错误,可用
transform_error(C++23 引入)做一次转换,再接and_then - 注意
transform_error返回新expected,不是就地修改;且转换函数必须返回同 value type、新 error type 的expected
示例:把 std::exception_ptr 转成统一 errc
auto safe_call = [](auto f) -> std::expected<int errc> {
try { return f(); }
catch (const std::system_error& e) {
if (e.code().category() == std::generic_category())
return std::unexpected(errc::file_not_found);
return std::unexpected(errc::parse_failed);
}
};
auto step = std::expected<void std::exception_ptr>{}
.transform_error([](auto ep) -> std::expected<void errc> {
return std::unexpected(errc::parse_failed);
})
.and_then([]() { return std::expected<int errc>{42}; });
</int></void></void></int>
和传统异常处理相比,链式 expected 有什么实际代价
性能上没额外开销——std::expected 是零成本抽象,所有分支都是编译期确定的,and_then 展开后就是 if-else 嵌套,没有堆分配或 RTTI。
但开发体验上容易低估两点:
- 每一步都要手动检查
has_value()或依赖and_then的短路语义,漏掉某个return std::unexpected就可能静默吞掉错误 - 调试时没法像异常那样靠栈回溯定位源头;错误值一路传递,最终出错位置可能离真实问题很远
- IDE 对
and_then的跳转支持弱,点不到 lambda 体内,需手动展开看类型
建议在关键路径(如配置加载、协议解析)用 expected 链,在工具函数内部仍可用异常快速失败,外部再用 std::unexpected 包装。
哪些场景不适合用 and_then 链式调用
不是所有“可能失败”的流程都适合拉成一行 and_then。过度链式会让错误处理变得僵硬,尤其当某步失败需要差异化响应时。
- 需要根据错误类型做不同重试策略(比如网络超时重试,解析错误直接失败)→ 应该拆开,显式
if (!x) { ... } - 中间步骤要记录日志或打点,且不想污染纯函数签名 →
and_then里的 lambda 很难加副作用,容易变成“为了链式而链式” - 涉及资源获取(如打开文件句柄),失败时需保证 RAII 清理 →
and_then本身不提供作用域管理,得靠外部scope_exit或智能指针配合
真正适合链式的,是那些“全成功才有意义”的线性数据流,比如:验证 → 转换 → 标准化。其余情况,老老实实写 if 分支更清晰。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











