Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the special value of `co_yield` in contrast to a simple stateful lambda in C++20?

From the well-known C++ coroutine library (search "Don't allow any use of co_await inside the generator coroutine." in the source file generator.hpp), and from my own experiments, I know that a coroutine using co_yield cannot use co_await meanwhile.

Since a generator using co_yield must be synchronous, then, what's the advantage of using co_yield over a simple stateful lambda?

For example:

#include <iostream>

generator<int> g()
{
    for (auto i = 0; i < 9; ++i)
    {
        co_yield i;
    }
}

int main()
{
    auto fn_gen = [i = 0] mutable { return i++; };

    // Lambda way
    for (auto i = 0; i < 9; ++i)
    {
        std::cout << fn_gen() << std::endl;
    }

    // co_yield way
    for (auto i : g())
    {
        std::cout << i << std::endl;
    }
}

What's the special value of co_yield in contrast to a simple stateful lambda in C++20?

Please See the Updated MWE: https://godbolt.org/z/x1Yoen7Ys

In the updated example, the output is totally unexpected when using co_await and co_yield in the same coroutine.

like image 816
xmllmx Avatar asked Dec 30 '21 18:12

xmllmx


People also ask

What are C++ coroutines good for?

Coroutines (C++20) A coroutine is a function that can suspend execution to be resumed later. Coroutines are stackless: they suspend execution by returning to the caller and the data that is required to resume execution is stored separately from the stack.

What does co_ await do?

The unary operator co_await suspends a coroutine and returns control to the caller.

Are coroutines asynchronous C++?

C++ coroutines can also be used for asynchronous programming by having a coroutine represent an asynchronous computation or an asynchronous task.

Does C++ have coroutines?

The main idea of coroutines is to have a function that preserves a state while releasing control back to the caller. Coroutines in C++ are a complex beast. The coroutine implementer should manage the frame to be created when yielding out, but we used an external library that manages this for us.


1 Answers

For trivial generators with minimal internal state and code, a small functor or lambda is fine. But as your generator code becomes more complex and requires more state, it becomes less fine. You have to stick more members in your functor type or your lambda specifier. You have bigger and bigger code inside of the function. Etc.

At the most extreme, a co_yield-based generator can hide all of its implementation details from the outside world, simply by putting its definition in a .cpp file. A stateful functor cannot hide its internal state, as its state are members of the type, which the outside world must see. The only way to avoid that is through type-erasure, such as with something like std::function. At which point, you've gained basically nothing over just using co_yield.

Also, co_await can be used with co_yield. Cppcoro's generator type explicitly hoses it, but cppcoro isn't C++20. You can write whatever generator you want, and that generator can support uses of co_await for specific purposes.

Indeed, you can make asynchronous generators, where sometimes you can yield a value immediately, and sometimes you can schedule the availability of a value with some asynchronous process. The code invoking your async generator can co_await on it to extract values from it, rather than treating it like a functor or an iterator pair.

like image 153
Nicol Bolas Avatar answered Oct 16 '22 16:10

Nicol Bolas