std::expected 不能直接用于异步链,因其无执行语义和 awaitable 接口;需配合 std::future 或自定义 awaitable 使用,手动实现 and_then/or_else,并注意移动语义、线程安全与错误传播。

std::expected 在异步链中无法直接传递异常状态
std::expectedstd::future 或 std::coroutine_handle。直接把 std::expected 当作异步结果返回(比如从 async_op() 返回 std::expected<int std::error_code></int>),会导致调用方无法感知“是否已完成”——它可能是个就绪值,也可能是个待决计算,但类型上没区别。
真正可行的组合方式是:用 std::expected 作为异步操作的「完成值载体」,配合显式调度或协程驱动。常见错误是试图写 auto res = co_await async_read(); // 返回 expected?不行,co_await 要求 awaitable —— 这会编译失败,因为 std::expected 没定义 operator co_await。
- ✅ 正确做法:让异步函数返回
std::future<:expected e>></:expected>或自定义 awaitable 类型(封装std::expected) - ⚠️ 注意:C++23 的
std::expected不提供and_then/or_else成员函数(不像 Rust 的Result),需手动展开或写辅助模板 - ❌ 避免把
std::expected塞进std::promise后再 get_future() —— 若E不可默认构造(如std::system_error),std::promise<:expected>></:expected>构造可能失败
手动实现 and_then / or_else 组合逻辑
没有内置链式方法,就得靠模式匹配 + lambda 拆包。关键点在于:保持值语义、避免拷贝、正确转发错误。
template<typename T, typename E, typename F>
auto and_then(std::expected<T, E>&& ex, F&& f)
-> std::expected<decltype(f(std::declval<T>())), E> {
if (ex.has_value()) {
return std::invoke(std::forward<F>(f), std::move(ex).value());
} else {
return std::unexpected(std::move(ex).error());
}
}
-
and_then接收右值引用,内部用std::move(ex).value()避免复制;若T是大对象(如std::vector),这点很关键 - 返回类型用
decltype推导,确保f返回std::expected或裸值(后者会被隐式包装) -
or_else同理,但对ex.error()操作,注意E是否支持移动(例如std::error_code可移动,std::exception_ptr也可) - 不要在 lambda 里 throw ——
std::expected不捕获异常,抛出会逃逸出组合链
与 std::future 组合时的线程安全陷阱
当异步操作返回 std::future<:expected e>></:expected>,后续用 and_then 组合时,std::future::then(如果编译器支持)或手动 wait_for + 拆包,都面临竞态风险。
立即学习“C++免费学习笔记(深入)”;
- ❌ 错误:多个线程同时调用
my_future.then([](auto&& f){ return and_then(f.get(), ...); })——std::future::get()是一次性消费操作,重复调用未定义行为 - ✅ 安全做法:用
std::shared_future复制后分发,或只在一个线程中消费原std::future - ⚠️ 注意
std::future的析构会阻塞等待(除非已share()或wait()过),在组合链中提前析构易导致意外同步延迟 - 如果底层异步库用回调(如 libuv、asio),建议把
std::expected作为回调参数传入,而非塞进std::future—— 更轻量,也避开 future 生命周期管理难题
协程中封装 expected 的 awaitable 实现要点
想在 co_await 中自然使用 std::expected,必须实现 awaitable。核心是让协程挂起直到异步操作完成,并把结果以 std::expected 形式交付。
struct expected_awaitable {
std::future<std::expected<int, std::error_code>> fut;
bool await_ready() const { return fut.wait_for(0s) == std::future_status::ready; }
void await_suspend(std::coroutine_handle<> h) { /* 注册回调触发 resume */ }
std::expected<int, std::error_code> await_resume() { return fut.get(); }
};
- 不能直接用
std::future::get()在await_resume里 —— 它会阻塞,违背协程非阻塞原则;必须用回调机制(如 asio 的post)唤醒协程 -
await_ready检查用wait_for(0s)而非valid(),因为valid()只说明 future 有效,不表示就绪 - 错误类型
E必须满足可迁移(MoveConstructible)且无抛异常的移动构造函数,否则std::expected移动时可能崩溃 - 别忘了为不同
T/E组合做模板特化,尤其当E是std::exception_ptr时,需额外处理捕获上下文
最麻烦的不是写 awaitable,而是确保整个链路里每个环节都尊重 std::expected 的值语义边界 —— 它不自动传播错误,也不自动调度,所有“组合”动作都得你亲手缝合,漏掉一次 move 或多调一次 get,问题就藏在运行时。


















