MiddleCodeCommonNot answered yet
How do you implement a minimal generator without std::generator?
Implement a lazy integer generator coroutine type without using std::generator, so that a coroutine using co_yield can be iterated value by value.
Constraints:
- The body must run lazily — nothing executes until the first
next(). - Each
co_yieldmust actually suspend the coroutine. - The coroutine frame must be destroyed exactly once (no leak, no double-free).
final_suspend()must benoexcept.
#include <coroutine>
struct Generator {
struct promise_type {
// your code here
};
std::coroutine_handle<promise_type> h;
bool next(); // resume, return whether more values remain
int value() const; // current yielded value
~Generator();
};
Write the implementation.
Define a type with a nested promise_type whose yield_value stores the value and returns suspend_always. Wrap a coroutine_handle, expose next()/value(), and destroy() the frame in the destructor.
- ✗Returning
suspend_neverfromyield_value, so the coroutine never actually pauses onco_yield - ✗Forgetting
destroy()in the destructor, leaking the heap frame - ✗Omitting
noexceptonfinal_suspend(), making the program ill-formed
- →How would you give the generator real iterators usable in a range-based
for? - →What does
std::generatoradd over this hand-rolled version in C++23?
A minimal generator
The promise_type defines behaviour; yield_value stores the element and suspends the coroutine.
#include <coroutine>
struct Generator {
struct promise_type {
int current;
Generator get_return_object() {
return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
std::suspend_always yield_value(int v) { current = v; return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
std::coroutine_handle<promise_type> h;
bool next() { h.resume(); return !h.done(); }
int value() const { return h.promise().current; }
~Generator() { if (h) h.destroy(); }
};
Generator counter() {
for (int i = 0; ; ++i)
co_yield i; // suspend_always: parks the frame, returns to caller
}
initial_suspend is suspend_always, so the body runs lazily — nothing happens until the first next(). yield_value returning suspend_always is what makes each co_yield actually pause. The destructor destroy()s the frame; coroutine_handle does not own it.