std::expected是C++23引入的值-错误二元容器,需#include <expected>并显式指定T和E类型(如std::expected<int, std::error_code>),构造须用std::make_expected、直接初始化或std::unexpect标记错误,取值前必须has_value()检查,不支持隐式bool转换,且E不可为void、须可移动。

std::expected在C++23中怎么声明和构造
它不是模板别名,而是标准库提供的一个值-错误二元容器,头文件是<expected>。声明时必须显式指定成功类型和错误类型,比如std::expected<int, std::error_code>或std::expected<std::string, int>——不能只写std::expected<int>,编译器会报错。
构造方式有三种常见路径:
- 用
std::make_expected(C++23起支持):例如auto res = std::make_expected<int, std::error_code>(42); - 直接初始化成功值:
std::expected<int, std::error_code> e{42}; - 构造错误状态:
std::expected<int, std::error_code> e{std::unexpect, std::errc::invalid_argument};——注意必须用std::unexpect标记,否则会被当作值构造
如何安全地取值或处理错误
不能像std::optional那样直接解引用;value()和error()都是带运行时检查的函数,失败时抛std::bad_expected_access。实际项目中更推荐先用has_value()判断,再分支处理:
if (res.has_value()) {
int x = res.value(); // 安全
} else {
std::error_code ec = res.error();
// 处理ec
}
也可以用and_then链式转换(类似Rust的and_then),但注意它只接受返回std::expected的可调用对象,且一旦上游失败,后续不会执行:
立即学习“C++免费学习笔记(深入)”;
res.and_then([](int x) { return std::expected<double, std::error_code>{x * 1.5}; });- 如果
res是错误态,结果仍是原错误,不会调用lambda
std::expected和std::variant、std::optional的区别在哪
它不是std::variant<T, E>的语法糖——关键区别在于语义约束:std::expected明确要求左侧是“成功值”,右侧是“错误类型”,编译器能据此优化访问逻辑,且提供transform/or_else等专用接口。
对比std::optional,后者无法表达“为什么失败”,而std::expected强制携带错误上下文。但要注意:std::expected不支持隐式转换到bool,不能写if (res),必须显式调用has_value()。
性能上,它和std::variant基本一致(通常是单字节tag + union),但比异常机制轻量得多——没有栈展开开销,适合高频I/O或系统调用封装场景。
容易踩的坑:错误类型必须可复制/可移动,且不能是void
如果错误类型是std::error_code或int没问题,但若定义成struct NoCopy { NoCopy(const NoCopy&) = delete; };,则std::expected<int, NoCopy>无法编译——因为内部需要存储并传递错误值。
另一个常见误用是把void当错误类型:std::expected<int, void>非法。标准规定错误类型不能是void,哪怕你只想表示“失败但无细节”。此时应改用std::expected<int, std::monostate>或std::expected<int, bool>。
另外,GCC 13+ 和 Clang 16+ 才完整支持std::expected;MSVC 19.35+ 支持,但早期版本需启用/std:c++23且仍有部分成员缺失。用之前务必检查__cpp_lib_expected宏。


















