std::expected不是异常替代的银弹,而是为显式错误处理、避免throw开销且需静态类型判定的场景设计;它要求E为语义明确的错误类型,构造需显式区分成功/失败,访问前必须判别状态,and_then支持链式错误传播,但无or_else且E须满足错误码约束。

std::expected 在 C++23 中不是“替代异常”的银弹,而是为明确错误路径、避免 throw 开销且需静态可判定返回类型的场景设计的——它只适合你**主动选择不抛异常**、且调用方必须显式处理成功/失败分支的情况。
std::expected 的构造和基本判别
它本质是 std::variant<t e></t> 的语义增强封装,但强制要求 E 是错误类型(通常为 std::error_code 或自定义枚举),且禁止 T 和 E 相同类型(避免歧义)。构造时必须明确指定成功或失败:
std::expected<int, std::error_code> parse_int(std::string_view s) {
try {
return std::stoi(std::string(s)); // 构造 success 分支
} catch (const std::exception&) {
return std::make_unexpected(std::errc::invalid_argument); // 构造 error 分支
}
}
- 不能直接写
return 42;—— 必须用return std::expected<int, E>(42);或更推荐的return 42;(隐式转换仅在返回类型已知时成立,但易误读,建议显式用std::expected<T,E>(value)) - 检查状态用
e.has_value()(true 表示成功)、e.error()(仅当 !has_value() 时合法,否则未定义行为) - 取值用
e.value()(失败时抛std::bad_expected_access<E>),更安全的是先判别再访问
链式调用:用 and_then 处理连续可能失败的操作
类似 Rust 的 and_then,它把前一个 std::expected 的成功值传给下一个函数,并自动传播错误。适用于“读配置 → 解析端口 → 连接 socket”这类线性依赖链:
auto connect_to_host = [](std::string host) -> std::expected<int, std::error_code> {
return socket_connect(host.c_str());
};
auto result = parse_int("8080")
.and_then([&](int port) {
return connect_to_host("localhost");
}); // result 类型仍是 std::expected<int, std::error_code>
-
and_then接收的 lambda 必须返回std::expected<U, E>(U可与原T不同,E必须相同) - 如果任意一步失败,后续 lambda 不执行,
result直接携带第一个失败的error() - 没有
or_else(C++23 标准未提供),错误恢复需手动用if (!e.has_value()) { ... }
与传统错误处理方式的关键差异和坑
它不兼容异常传播模型,也不等价于 std::optional + 额外错误码。常见误用点:
立即学习“C++免费学习笔记(深入)”;
- 不要用
std::expected<T, bool>——E应表达错误原因,bool无法携带上下文;应选std::error_code或enum class errc { not_found, permission_denied }; - 不能和
std::exception_ptr混用:std::expected<T, std::exception_ptr>合法但违背设计初衷——既然已有异常机制,就不该用expected去包裹它 - 移动语义敏感:
std::expected的value()和error()返回左值引用,若内部对象是 move-only 类型(如std::unique_ptr),多次调用会触发重复移动,导致未定义行为;务必一次取用后不再访问 - 编译器支持尚不统一:GCC 13+、Clang 16+ 默认启用,MSVC 19.35+ 需
/std:c++23且部分特性(如and_then)可能延迟实现
最易被忽略的是错误类型的语义约束——E 必须满足 std::is_error_code_enum_v<E> 或能隐式转换为 std::error_code,否则编译失败;而多数人直接塞个 int 或 std::string 就卡在这一步。


















