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

std::expected在C++23中怎么声明和构造
它不是模板别名,而是标准库提供的一个值-错误二元容器,头文件是<expected></expected>。声明时必须显式指定成功类型和错误类型,比如std::expected<int std::error_code></int>或std::expected<:string int></:string>——不能只写std::expected<int></int>,编译器会报错。
构造方式有三种常见路径:
- 用
std::make_expected(C++23起支持):例如auto res = std::make_expected<int std::error_code>(42);</int> - 直接初始化成功值:
std::expected<int std::error_code> e{42};</int> - 构造错误状态:
std::expected<int std::error_code> e{std::unexpect, std::errc::invalid_argument};</int>——注意必须用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的可调用对象,且一旦上游失败,后续不会执行:
res.and_then([](int x) { return std::expected<double std::error_code>{x * 1.5}; });</double>- 如果
res是错误态,结果仍是原错误,不会调用lambda
std::expected和std::variant、std::optional的区别在哪
它不是std::variant<t e></t>的语法糖——关键区别在于语义约束: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></int>无法编译——因为内部需要存储并传递错误值。
另一个常见误用是把void当错误类型:std::expected<int void></int>非法。标准规定错误类型不能是void,哪怕你只想表示“失败但无细节”。此时应改用std::expected<int std::monostate></int>或std::expected<int bool></int>。
另外,GCC 13+ 和 Clang 16+ 才完整支持std::expected;MSVC 19.35+ 支持,但早期版本需启用/std:c++23且仍有部分成员缺失。用之前务必检查__cpp_lib_expected宏。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











