C++20 的 co_yield 仅将值传给 promise 对象,不自动生成迭代器;需手动实现 generator 类型、promise_type(含 yield_value)、iterator 及 begin/end 接口,否则无法用于范围 for。

co_yield 本质是让协程挂起并返回值,不是“生成器”语法糖
别被 Python 的 yield 带偏——C++20 的 co_yield 不自动构造迭代器或容器,它只把值传给协程的 promise 对象,后续怎么暴露、怎么消费,全靠你手动搭桥。常见错误是直接写 co_yield x; 就以为能用范围 for 遍历,结果编译报错:no viable conversion from 'generator<int>' to 'int'</int>,因为没实现 begin()/end()。
关键点在于:协程函数返回类型(比如 generator<int>)必须自己定义,且其内部 promise 类型要重载 yield_value,还要提供可迭代接口。
- promise 类型需继承自
std::coroutine_traits<generator<T>, ...>::promise_type或完全自定义 -
yield_value(T&& v)必须返回std::suspend_always或类似挂起句柄,否则协程不暂停 - 返回的
generator类必须包含iterator和sentinel,且支持operator bool()判断是否还有值
手写 generator<T> 要补全三个核心部件
标准库没提供 generator,得自己写。最小可用版本至少含三块:promise_type、iterator、generator 本体。漏掉任意一块,co_yield 就没法和 for-range 协同工作。
典型坑是 iterator 没实现 operator== 或 operator!=,导致 for (auto x : gen) { ... } 编译失败;或者 promise_type::get_return_object() 返回了临时对象,协程一启动就析构,后续调用 next() 直接 UB。
立即学习“C++免费学习笔记(深入)”;
-
promise_type中get_return_object()必须返回一个持有自身指针的generator(通常用generator{handle_type::from_promise(*this)}) -
iterator的operator++()必须调用coro.resume(),且 resume 后要检查coro.done()决定是否继续 -
generator构造函数接收std::coroutine_handle<promise_type>,并用std::move转移所有权,避免悬空 handle
示例片段(简化版):
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
struct generator {
struct promise_type {
int current_value;
auto get_return_object() { return generator{handle_type::from_promise(*this)}; }
auto yield_value(int v) { current_value = v; return suspend_always{}; }
auto initial_suspend() { return suspend_always{}; }
auto final_suspend() noexcept { return suspend_always{}; }
void unhandled_exception() { std::terminate(); }
};
// ... iterator 定义、begin()/end() 等
};
co_yield 在数据流场景下必须配合手动控制生命周期
真实数据流(比如从文件读 chunk、从网络收包、实时传感器采样)往往需要外部驱动:你不能只靠 for-range 自动拉取,得允许消费者随时暂停、恢复、丢弃。这时候 co_yield 的挂起点就是你的控制锚点。
常见错误是把耗时 I/O 放在协程体内同步执行,导致整个线程卡住;或者没处理好异常传播,一次 co_yield 后抛异常,promise 没捕获,协程直接崩溃。
- 所有阻塞操作(如
fread、recv)应替换为异步等价物(async_read+co_await),或交由外部调度器轮询 -
promise_type::unhandled_exception()必须显式处理,否则未捕获异常会终止协程,且无法通知消费者 - 如果数据流需支持多消费者,
generator本身不能共享 —— 每次调用协程函数都应产生新实例,避免状态污染
MSVC / Clang 对 co_yield 的 ABI 兼容性仍不稳定
别在跨编译器或跨 STL 版本的项目里直接传递 generator 对象。Clang 15+ 和 MSVC 19.34+ 虽支持,但 std::coroutine_handle 的内存布局、promise 对齐方式、甚至 suspend_always 的实现细节都有差异。常见现象是:Clang 编译的 generator 被 MSVC 编译的代码调用,运行时 coro.resume() 访问非法地址。
更隐蔽的坑是 STL 版本混用:libc++ 和 MSVC STL 对 coroutine_traits 的特化不一致,可能导致 co_yield 调用的 yield_value 根本没被选中。
- 生产环境建议封装一层 C 风格接口(如
bool next_int(generator_handle h, int* out)),避开 C++ 对象 ABI - 同一项目严格限定单个编译器 + 单个 STL 实现,CI 中加
static_assert校验sizeof(promise_type)和对齐 - 调试时用
coro.address()打印地址,确认 resume 前后 handle 指向同一内存块
协程的数据流本质是「带状态的懒求值函数」,它的复杂不在语法,而在你得亲手把 promise、handle、iterator、异常路径这四条线全拧紧。松一根,整条数据流就断在看不见的地方。


















