C++20 introduces coroutines, which can be used to create generators and other similar things:
generator<int> counter(int max) {
for(int i = 0; i < max; i++) {
co_yield i;
}
}
Is there any way to create a coroutine such that the caller can provide a response that is returned by co_yield
once the coroutine is resumed? Let's call this a channel
instead of a generator.
This is an example of what I want to be able to do:
channel<int, int> accumulator(int initial) {
while(true) {
// Can channel be written so co_yield provides a response?
int response = co_yield initial;
initial += response;
}
}
Here, whenever the caller resumes the coroutine, it would provide a value which then gets returned from co_yield
once the coroutine is resumed, like so:
std::vector<int> accumulate(std::vector<int> values) {
channel<int, int> acc = accumulator(0);
std::vector<int> summed_values;
for(int v : values) {
// Get whatever value was yielded by the accumulator
int sum = acc.recieve();
// Do something with the value
summed_values.push_back(sum);
// Resume the accumulator, returning this value from co_yield:
acc.send(v);
}
return summed_values;
}
Can anyone provide some guidance or an example on how to do this? Coroutines are still very new to me. I have a bare-bones implementation of a channel
class, but I'm not sure what should be returned from yield_value
in order to achieve this.
The two locations in question I've marked (A)
and (B)
in the comments.
template <class Out, class In>
struct channel {
struct promise_type {
Out current_value;
auto yield_value(Out value) {
current_value = value;
// (A) What do I return here?
}
channel get_return_object() {
return {std::coroutine_handle<promise_type>::from_promise(*this)};
}
// We run up until the first value is ready
auto initial_suspend() noexcept { return std::suspend_never(); }
auto final_suspend() noexcept { return std::suspend_always(); }
void unhandled_exception() noexcept { std::terminate(); }
};
Out receive() {
return handle.promise().current_value;
}
void send(In response) {
// (B) What do I do here?
}
// Constructors, destructor and move assignment operator omitted for brevity
private:
std::coroutine_handle<promise_type> handle = nullptr;
};
The key is await_resume
, which is called on the awaiter (result of yield_value
) to obtain the result of co_yield
.
You also need to store the response somewhere. As Raymond Chen suggested in comment, you can put the value in a new data member of promise_type
.
So changes are:
Add a data member to promise_type
.
In response;
Define a customized awaiter to return that data member.
struct awaiter : std::suspend_always {
friend promise_type;
constexpr In await_resume() const { return m_p->response; }
private:
constexpr awaiter(promise_type* p) : m_p(p) {}
promise_type* m_p;
};
In (A)
, return the customized awaiter.
return awaiter(this);
In (B)
, set the data member, then resume the coroutine.
handle.promise().response = response;
handle.resume();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With