std::expected是C++23引入的标准化错误传播工具,本质为持有T或E的值语义容器,专用于预期会失败且调用方必须显式处理的场景(如文件打开、网络请求、解析操作),不适用于需栈展开的严重异常或跨库未定义行为场景。

std::expected 是什么,什么时候该用它
std::expected 是 C++23 引入的标准化错误传播工具,本质是一个持有 T 或 E(通常是 std::error_code 或枚举)的值语义容器。它不是替代 throw 的通用方案,而是专为「预期会失败、且调用方必须显式处理」的场景设计——比如文件打开、网络请求、解析操作。
它不适用于:
- 需要栈展开的严重异常(如内存耗尽)
- 跨库边界传递未定义行为(
std::expected拷贝/移动语义依赖T和E是否可复制) - 旧代码迁移:C++20 及更早标准无法直接使用,需自行实现或引入第三方(如
tl::expected)
如何链式调用多个返回 expected 的函数
关键在于避免重复检查 has_value(),用 and_then 实现流式转发:
假设你有三个函数:
立即学习“C++免费学习笔记(深入)”;
std::expected<https://www.php.cn/link/d3e4b9f9aa7aac3bd930abb82fab2d2b, std::error_code> parse_https://www.php.cn/link/d3e4b9f9aa7aac3bd930abb82fab2d2b(std::string_view s); std::expected<double, std::error_code> square(double x); std::expected<std::string, std::error_code> format_result(double x);
正确写法是:
auto result = parse_https://www.php.cn/link/d3e4b9f9aa7aac3bd930abb82fab2d2b("42")
.and_then([](https://www.php.cn/link/d3e4b9f9aa7aac3bd930abb82fab2d2b i) { return square(static_cast<double>(i)); })
.and_then([](double d) { return format_result(d); });
注意点:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
-
and_then只在当前expected含值时调用回调,否则直接透传错误;回调必须返回std::expected<U, E>,类型E必须与原expected的错误类型一致(或可隐式转换) - 不能混用不同类型错误:若
parse_https://www.php.cn/link/d3e4b9f9aa7aac3bd930abb82fab2d2b返回std::error_code,而square返回https://www.php.cn/link/d3e4b9f9aa7aac3bd930abb82fab2d2b错误码,编译失败 - 没有
map(变换值但不改变错误),只有and_then;如需纯映射,先解包再重新包装:e.transform([](https://www.php.cn/link/d3e4b9f9aa7aac3bd930abb82fab2d2b x) { return x * 2; })
错误类型不匹配时怎么统一处理
常见问题:不同函数用不同错误类型(std::errc、自定义枚举、std::string),导致 and_then 链断裂。
解决方式不是强行转换,而是提前收敛到一个公共错误类型:
- 推荐用
std::error_code作为底层载体,配合std::error_category定义自己的错误域 - 若必须混合,可用
std::variant<Err1, Err2, Err3>作E,但会失去std::error_code的语义和比较能力 - 临时方案:用
transform_error(C++23 未标准化,部分实现支持)或手动if (!e.has_value()) { return std::unexpected(...); }
例如:
std::expected<https://www.php.cn/link/d3e4b9f9aa7aac3bd930abb82fab2d2b, std::error_code> f1();
std::expected<double, MyError> f2();
<p>// 不可直接链:f1().and_then(<a href="https://www.php.cn/link/d3e4b9f9aa7aac3bd930abb82fab2d2b"></a> { return f2(); })
// 正确做法:f1().and_then(<a href="https://www.php.cn/link/d3e4b9f9aa7aac3bd930abb82fab2d2b"></a> -> std::expected<double, std::error_code> {
// auto res = f2();
// if (!res.has_value()) return std::unexpected(to_std_error_code(res.error()));
// return res.value();
// });</p>性能和 ABI 兼容性要注意什么
std::expected 是零成本抽象,但几个细节容易踩坑:
- 它内部通常用 union + 构造标志位实现,
T和E都必须是可平凡析构(trivially destructible)或至少满足is_nothrow_constructible_v,否则可能引发未定义行为 - 移动构造/赋值不保证 noexcept,尤其当
T或E的移动操作抛异常时;若用于容器(如std::vector<expected<T,E>>),会影响容器的强异常安全保证 - 目前主流标准库(libstdc++、libc++)对
std::expected的实现尚未完全 ABI 稳定,跨 GCC 版本或 Clang/libc++ 混合链接时慎用二进制分发
实际项目中,如果目标平台不支持 C++23,或者团队编译器版本参差,更稳妥的做法是用 tl::expected 并冻结其头文件版本,而不是依赖编译器内置实现。
流式错误处理真正难的不是语法,而是错误类型的收敛策略和跨模块错误语义对齐——这点比 and_then 写法重要得多。

















