co_yield本身不构成生成器,它只是协程挂起点;要实现for(auto x:gen)可用的生成器,必须手动实现promise_type、iterator和容器包装,标准库至今未提供跨平台std::generator。

直接说结论:co_yield 本身不构成生成器,它只是协程挂起点;要实现一个能用于 for (auto x : gen) 的生成器,必须手动补全 promise_type、iterator 和容器包装三部分——标准库至今(2026年4月)仍未提供跨平台的 std::generator。
为什么 co_yield 单独写就编译不过
常见错误是这样写:
int fibonacci() {
int a = 0, b = 1;
while (true) {
co_yield a; // ❌ 编译报错:'co_yield' cannot be used in a non-coroutine function
int tmp = a;
a = b;
b += tmp;
}
}
原因很直接:co_yield 只能在协程函数里用,而协程函数的判定规则是:
- 函数体中至少出现一次
co_yield、co_await或co_return - 返回类型必须定义嵌套的
promise_type,且该类型满足协程 traits(如提供get_return_object、initial_suspend等) - 返回类型不能是
int、void、std::vector<T>这类“普通”类型
换句话说,co_yield 不是语法糖,它是协程状态机的触发器,没有配套的 promise 和 handle,它连编译都过不了。
立即学习“C++免费学习笔记(深入)”;
最简可用的 Generator 类骨架
下面这个 Generator<int> 能跑通 for (auto x : fib(10)),不含迭代器优化,但结构清晰、无冗余:
#include <coroutine>
#include <exception>
template <typename T>
struct Generator {
struct promise_type {
T value_;
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
Generator get_return_object() { return Generator{handle_type::from_promise(*this)}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
std::suspend_always yield_value(T v) {
value_ = std::move(v);
return {};
}
};
using handle_type = std::coroutine_handle<promise_type>
handle_type coro_;
explicit Generator(handle_type h) : coro_(h) {}
~Generator() { if (coro_) coro_.destroy(); }
struct iterator {
handle_type h_;
bool operator!=(const iterator& other) const { return h_.done() != other.h_.done(); }
T operator*() const { return h_.promise().value_; }
iterator& operator++() {
h_.resume();
return *this;
}
};
iterator begin() {
if (coro_ && !coro_.done()) coro_.resume();
return iterator{coro_};
}
iterator end() { return iterator{nullptr}; }
};
关键点:
-
yield_value必须返回一个 awaitable(如std::suspend_always),否则挂起失败 -
begin()里调一次resume()是为了走到第一个co_yield,否则for循环会立刻结束 -
end()返回一个空 handle,靠done()判断终止,这是范围 for 的约定
co_yield 函数怎么写才对
协程函数签名和内容必须严格匹配上面的 Generator<T> 定义:
Generator<int> fib(int n) {
int a = 0, b = 1;
for (int i = 0; i < n; ++i) {
co_yield a; // ✅ 正确:产出值并挂起
int tmp = a;
a = b;
b = tmp + b;
}
}
使用方式:
for (int x : fib(5)) {
std::cout << x << " "; // 输出:0 1 1 2 3
}
注意:
- 函数返回类型必须是
Generator<T>,不能是auto或别名(类型推导无法满足协程 trait) - 每次
co_yield后,协程挂起,局部变量(a、b、i)全部保留在协程帧中,下次恢复时继续用 - 如果想提前退出(比如异常或 break),必须确保
coro_.destroy()被调用,否则内存泄漏
容易被忽略的编译与链接细节
即使代码逻辑正确,也会在构建阶段失败:
- GCC 需加
-fcoroutines -std=c++20,Clang 对应-stdlib=libc++ -std=c++20 -fcoroutines-ts(注意 TS 后缀) - libcoro 或 libc++ 的 coroutine 支持需完整,某些旧版系统 libc++ 缺少
std::suspend_always特化 - MSVC 2022 17.9+ 默认支持,但需关闭 `/permissive-`,否则可能误报 promise 接口不全
- 不要试图用
std::shared_ptr<Generator>包装返回值——协程 handle 本身已管理内存,多一层智能指针反而干扰生命周期
最麻烦的其实是 promise_type 的成员函数签名:漏掉 noexcept(如 final_suspend)、返回类型写错(比如 yield_value 返回 void)、或忘了 unhandled_exception,都会导致编译器静默降级为普通函数,然后报一堆无关错误。


















