std::expected需c++23支持,编译需-g++13+/clang16+/msvc19.35+、-std=c++23及#include ;t/e须可移动且e≠t,错误需显式检查,无自动传播,性能优于异常但有空间与构造开销。

std::expected 编译失败:缺少头文件或标准版本不对
用 std::expected 时第一反应往往是“找不到标识符”,不是你写错了,而是它根本没被编译器认出来。C++23 才把它正式纳入标准库,但很多项目仍默认用 C++17 或 C++20 编译,std::expected 就直接不可见。
实操建议:
- 确认编译器支持:GCC 13+、Clang 16+、MSVC 19.35+(VS 2022 17.5+)才完整支持 C++23 的
std::expected - 显式启用 C++23:编译时加
-std=c++23(GCC/Clang),MSVC 加/std:c++23 - 包含正确头文件:
#include <expected></expected>—— 注意不是<optional></optional>或<variant></variant> - 别用
std::experimental::expected:那是 TS 版本,接口不兼容,且已被废弃
std::expected 的 T 和 E 类型约束很实际
std::expected 不是万能容器,它的模板参数有明确限制:两个类型都必须是可移动的(movable),且 E 不能是 void 或与 T 相同;T 可以是 void(表示“无返回值,只关心错误”)。
常见错误现象:
- 定义
std::expected<int int></int>→ 编译报错:static_assert失败,“E must not be the same as T” - 用非 movable 类型如
std::array<int></int>作T→ 移动构造可能被禁用,导致std::expected构造失败 - 把
std::expected<void std::string></void>当成“只有错误”的结果类型 → 合法,但取值要用.value()前必须先.has_value()判断,否则未定义行为
使用场景提示:适合替代 std::optional<t></t> + 单独错误码,也比抛异常更轻量,尤其在系统调用、文件 IO、解析函数中。
std::expected 的错误传播容易漏掉 .error() 检查
很多人以为 std::expected 能自动“冒泡”错误,像 Rust 的 ? 运算符那样——但它没有。你必须显式检查、提取、转发,否则错误就静默丢失了。
实操建议:
- 别直接解包:
auto res = may_fail(); int x = res.value();—— 如果res.has_value() == false,.value()抛std::bad_expected_access - 安全写法优先用
if (res)或if (res.has_value()),再取.value();出错分支用else处理res.error() - 想链式调用?得自己封装辅助函数,比如
and_then风格的扩展(标准库没提供),或用第三方如tl::expected的接口做过渡 - 注意
.error()返回的是const E&,修改错误对象需先 move 出来再构造新expected
和异常比,std::expected 的性能开销在哪
用 std::expected 主要图它零成本抽象:没有栈展开、无运行时异常表开销、错误值内联存储。但它不是免费的 —— 空间和构造逻辑有隐含代价。
关键点:
- 内存布局:
std::expected<t></t>至少占max(sizeof(T), sizeof(E)) + 1字节(额外 1 字节存状态标记),比单纯返回T多一点空间 - 构造开销:如果
T和E都有非平凡构造函数,std::expected必须在内部 union 上做 placement new,比裸返回多一次分支判断和可能的构造调用 - 移动语义依赖:若
T移动代价高(如大字符串),而你频繁返回expected<bigstring err></bigstring>,性能可能反不如异常(异常只在失败路径触发) - 编译器优化友好:现代编译器对
if (e) { ... } else { ... }分支预测良好,但过度嵌套的expected层叠会阻碍内联
真正容易被忽略的是:它不解决控制流复杂度问题。一个函数返回 expected,调用方就得处理,层层传递时逻辑分散,比集中 try/catch 更难一眼看清错误流向。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











