Second try.
This time rather than get()
and operator bool
make the handle be a range operator.
The only difference in the promise is: std::suspend_never initial_suspend() noexcept {return {};}
. Here return std::suspend_never
instead of std::std::suspend_always
forcing the co-routine to run to the first yield point before the owner takes control back.
#include <iostream>
#include <coroutine>
struct Handle
{
struct Promise;
using CoRoutineHandle = std::coroutine_handle<Promise>;
struct Promise
{
// Place to store the output value from yield and return.
int output;
// This function is used to build the return object from the function.
Handle get_return_object() noexcept {return Handle{CoRoutineHandle::from_promise(*this)};}
// Returning std::suspend_never by initial_suspend means we run to the first yield point (or exit the co-routine).
// This means the output value will have an initial value already set
// assuming we hit a co_yield.
std::suspend_never initial_suspend() noexcept {return {};}
// This is called by co_yield.
// Using std::suspend_always means the control is returned to the caller.
std::suspend_always yield_value(int value) noexcept {output = value;return {};}
// Return std::suspend_always to make sure
// the co-routine does not exit prematurely and it is easy to detect that
// we have reached the exit with promise.done()
std::suspend_always final_suspend() noexcept {return {};}
// This is called if there was an exception.
// Needed but not used.
void unhandled_exception() noexcept {}
};
// Constructor keeps track of the handle.
CoRoutineHandle handle;
Handle(CoRoutineHandle handle)
: handle(handle)
{}
// Need to manually destroy the handle.
// When we have finihsed using the Promise
~Handle()
{
handle.destroy();
}
// Simplest iterator I could write.
// Iterator are only every equal if they are both at then end.
class Iter
{
CoRoutineHandle handle;
bool atEnd;
public:
// Note: The creator of the iterator
// Way want this to be begin. But
// if the co-routine is done there are no
// more values then the begin is the end.
Iter(CoRoutineHandle handle, bool isEnd = false)
: handle(handle)
, atEnd(isEnd || handle.done())
{}
// I just find it more intuative to read comparisons like this.
// rather than make it a member the friend test just looks simpler.
friend bool operator==(Iter const& lhs, Iter const& rhs)
{
return lhs.atEnd && rhs.atEnd;
}
// ++ moves to the next value.
// If this is the `final_suspend` point then this is now
// the end value and so not valid.
void operator++()
{
handle.resume();
atEnd = handle.done();
}
// Handle always contains a valid value.
// as we let the co-routine run to the first yield.
// Note: if there are no yield poitns hit then
// the iterator has already been marked as "atEnd"
// Thus calling this is UB.
int operator*()
{
return handle.promise().output;
}
};
Iter begin() {return {handle};};
Iter end() {return {handle, true};}
};
namespace std
{
template<typename... Args>
struct coroutine_traits<Handle, Args...>
{
using promise_type = Handle::Promise;
};
}
Handle myLazyRange(int b, int e)
{
while (b < e) {
co_yield b++;
}
}
Now it can be used like this:
int main()
{
Handle range = myLazyRange(10, 15);
for(auto val: range) {
std::cout << val << "\n";
}
}