std::expected是c++23新增类模板,需启用-std=c++23;构造分成功(如ok(42))和错误(需std::unexpect)两种方式;取值须先has_value()判别,链式用and_then()透传错误,非异常替代品,不支持隐式转换。

std::expected在C++23中怎么声明和构造
它不是模板别名,而是标准库新增的类模板 std::expected<t e></t>,其中 T 是成功值类型,E 是错误类型(通常用 std::error_code 或自定义枚举)。C++23才正式加入,编译器需启用 -std=c++23(GCC 13+、Clang 16+、MSVC 19.37+ 支持)。
常见构造方式:
-
std::expected<int std::error_code> ok(42);</int>—— 成功构造,隐式转换为std::expected -
std::expected<int std::error_code> err(std::unexpect, std::errc::invalid_argument);</int>—— 错误构造,必须用std::unexpect标记 -
std::expected<:string int> s("hello");</:string>—— 若T可隐式构造,直接传值;但若T和E类型可相互转换(比如都是int),会编译失败,必须显式用std::unexpect
如何安全提取值或错误(避免未定义行为)
std::expected 不像 std::optional 那样有 value_or() 的便捷兜底,直接调用 .value() 在 error 状态下会抛出 std::bad_expected_access<e></e> 异常 —— 这不是你想要的“函数式”风格,容易漏判。
推荐做法是先检查状态再取值:
- 用
.has_value()判断是否成功:if (res.has_value()) { use(res.value()); } - 用
.error()获取错误(仅在!has_value()时合法):else { handle(res.error()); } - 更紧凑写法:
auto&& [val, err] = std::tie(res.value(), res.error());❌ 不行 ——value()和error()不能同时调用;必须二选一
注意:.value() 返回的是 T&&(右值引用),若 T 是非拷贝类型,移动后原对象失效;建议按需用 std::move(res).value() 显式转移。
链式调用时怎么传递错误(类似 Rust 的 ? 操作符)
C++ 没有内置的传播语法,但可以用 .and_then() 实现类似效果:它接收一个返回 std::expected 的函数,仅在当前为 value 时调用,并转发结果;否则直接返回原 expected(错误透传)。
示例:
auto parse_int(std::string_view s) -> std::expected<int std::errc> {
try { return std::stoi(std::string(s)); }
catch (...) { return std::unexpect{std::errc::invalid_argument}; }
}
<p>auto read_config() -> std::expected<:string std::errc> { /<em> ... </em>/ }</:string></p>
<p>// 链式解析
auto res = read_config()
.and_then([](std::string s) { return parse_int(s); })
.and_then([](int n) { return n > 0 ? std::expected<int std::errc>(n) : std::unexpected(std::errc::result_out_of_range); });</int></p></int>
关键点:
-
.and_then()参数函数签名必须是T → std::expected<u e></u>,错误类型E必须与外层一致(否则编译失败) - 若想转换错误类型,得手动用
.transform_error(),例如把std::errc映射为自定义enum class ConfigError -
.transform()只处理 success 分支,不改变 error;.map()是旧提案名,C++23 标准里叫.transform()
和传统错误处理方式对比时要注意什么
它不是异常替代品,也不是 std::optional 的超集 —— std::expected<t void></t> 不合法,错误类型不能是 void;而 std::optional<t></t> 无法携带错误原因。
适用边界很明确:
- 适合 I/O、解析、配置加载等**预期可能失败且需区分错误种类**的场景;不适合逻辑 bug(该用断言或异常)
- 性能敏感路径慎用:每个
std::expected至少占sizeof(T) + sizeof(E)(对齐后),比裸指针或 errno 大;若T或E是大对象,要考虑移动成本 - 跨 ABI 边界(如 DLL 导出函数)不建议用:
std::expected是类模板,实现细节未标准化,不同 STL 版本可能二进制不兼容
最易被忽略的一点:它不自动参与隐式转换。比如函数返回 std::expected<int std::error_code></int>,你不能直接赋给 int 变量,也不能靠重载 operator bool() 判定——必须显式检查 has_value()。这点和 Rust 的 Result 行为一致,但和 C++ 程序员直觉相悖,容易写出崩溃代码。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











