The first 3 parts of the series explained step by step on how to create a coroutine with co_await operator. In the first part of the series, I laid out the foundational principles of C++20 coroutines and demonstrated how a simple coroutine can be created which stays in a suspended state. In the second part of the series, I demonstrated how the coroutine can be resumed through the gradually delved deeper into the underlying principles of coroutine and the order of the implicit calls that are made upon creation, suspension and resuming the coroutine with a lot of examples. Finally in part 3 of this series, I demonstrate how to pass data back and forth between coroutine and its caller and concluded the article with the infinite integer number generator coroutine.

In the fourth part of this series, I want to demonstrate the usage of co_yield operator and how to create a generic generator

9. Coroutine with co_yield

In section 7.3 I demonstrated how to pass a data from a coroutine to its caller. But that technique is quite clumsy and is not really necessary just when returning a data from the coroutine to the caller is required and not the other way round. An astute reader might remember the quote from cpp reference page in section 1 of this series:

2. keyword _co_yield_ to suspend execution returning a value …

Quoting the co_yieldsection from cpp reference page:

co_yield expr_ is equivalent to:_

_co_await promise.yield_value(expr)_

But before we check what this mean, lets check what gcc 10.2 compiler tells us (check here) when an integer is used with co_yield

ReturnObject foo(){co_yield 2;}

int main(){auto h =  foo();}
Compiler stderr
<source>: In function 'ReturnObject foo()':
<source>:26:5: error: no member named 'yield_value' ...
co_yield 2;

This indicates that ReturnObject::promise_type is missing the yield_value method. The parameter of yield_value must match the type that co_yield is expected to evaluate. The only restriction in the function signature of yield_value method is that it should return an awaiter/awaitable. Therefore the implementation of yield_value in our case looks like

struct ReturnObject {
    struct promise_type {
        int val_;
        ...
        std::suspend_always yield_value(int value){
            val_ = value;
            return {};
        }
    };
    ...
};

With just this much change, the expression co_yield 2 works and can be accessed in main without the need to do the convoluted magic with a custom awaiter as was demonstrated in section section 7.3** .**

using PromType = ReturnObject::promise_type;

ReturnObject foo(){
    co_yield 2;
    co_yield 3;
}
int main(){
    auto h =  foo();
    PromType* prom = &h.promise();
    std::cout << "From main: " << prom->val_ <<"\n";
    h();
    std::cout << "From main: " << prom->val_ <<"\n";
}

#coroutine #modern-cpp #cpp20 #cplusplus #c++

Painless C++ Coroutines-Part 4
1.90 GEEK