* calling into the coroutine from the caller uses the caller's stack (i.e. it just pushes on another stack frame). * the lack of a coroutine stack ("stackful" coroutine) means that the coroutine can only "yield" a value from a single method; it cannot call a function F, and then have F yield back to the original coroutine's caller. * In other words: you can think of it like a "coroutine with one stack frame for coroutine yielding semantics"
The compiler does some magic to slice up the coroutine into segments (between co_ statements) and stores state that must persist between these segments in a coroutine frame (in addition to the "slice" that the coroutine should continue at once it is called again).
The real tricky part is the lack of library support. From what I've seen, it seems like a naming convention is all that defines what various coroutine functions are, e.g. `promise` or `promise_type`. This is very much like iterators, which can be written via structural subtyping: anything that has the special static type values and/or methods can be used as one.
I think this co_awaiting is the most confusing part for folks: in most languages with stackful coroutines, it makes sense how one can
1. call "co_await" on a coroutine at a high-level API boundary 2. have that coroutine execute normal code all the way down to some expensive part (e.g. I/O) 3. at that particular point "deep" down in the expensive parts, that single point can just call "yield" and give control all the way back to the user thread that co_awaited at 1, usually with some special capability in the runtime.
I believe the way you can do this in C++20 concepts is to co_yield a promise all the way back to the originating "co_await" site, but I may be confused about this still...
It's totally clear to me why they didn't choose this for C++: keeping track of some heap-allocated stack frames could prove unwieldy.
I wish more effort went into explaining and promoting coroutines. Right now the advice seems to be "be brave and DIY, or just use cppcoro until we figure this out".
Why is this a big deal in C++? Am I missing anything that makes c++ coroutines less powerful than other mainstream solutions? Or are people comparing its power with e.g. Lisp or go?
It is sort of a big deal because the discussion of wether adding stackful or stackless coroutines in C++ was an interminable and very visible one. Stackless won.
Can I use them like `yield` in Python+Twisted, i.e. to implement single-threaded, cooperative parallelism? It would not expect to be able to plainly call a function with a regular call, and have that function `yield` for me - but can I await a function, which awaits another, and so on?
As far as I understand, C++20 coroutines are just a basis and you can build a library on top of it. It is possible to build single-threaded async code (python or JS style) as well as multi-threaded async code (C# style where a coroutine can end up on a different context), right?
Is there anything like an event loop / Twisted's `reactor` already available yet?
I'm really looking forward to rewrite some Qt callback hell code into async...
You can use coroutines for what you say, but there are no execution contexts (like a thread pool or an event loop) in C++20 standard library in which to execute coroutines, so to do networking and async I/O you need to use a library or DIY. Hopefully standard execution will come later as part of the Executors proposal.
You can currently use C++ native coroutines with the ASIO library, but this is probably subject to quite a bit of API churn in the future:
https://www.boost.org/doc/libs/1_75_0/doc/html/boost_asio/ov...
You can also wrap things like ASIO yourself. I did this in 2018 when I was learning about C++ coroutines to create a simple telnet based chatroom:
https://github.com/heavenlake/coro-chat/blob/master/chat.cpp
Note that this code is likely garbage by todays standards. One thing i can't remember is why i needed to use the cppcoro library in the end.
This is confusing, because it begs the question "same as what?" In fact, you can migrate a coroutine across threads, or even create a thread specifically for the purposes of resuming a coroutine.
But I suppose it is true that from the point at which you resume a coroutine to the point at which it suspends itself, it will use a particular stack for any function calls made within the coroutine. That's why you can't suspend a coroutine from within a function call, because the function call will use the stack.
There is a proposal for executors that would add event loops to C++. In the meantime boost.Asio (also available outside of boost) is one of the best options (the standard proposal was originally based on Asio, and Asio is tracking the standard proposal closely).
Whether a coroutine is stackful or stackless is largely an implementation detail that has some tradeoffs either way, in either case coroutine semantics can allow you to write efficient asynchronous code imperatively or do your callback-to-async transformation.
A lot of languages, including C#, python, C++, rust and many others specify the stackless-ness of coroutines. So it is not just an implementation detail. You could argue that specifying these semantics is a problem, and I would actually agree, but obviously it is not something many, if not most, language designers agree.
last time I tried it was pretty easy to make C++ coroutines fit into Qt's event loop.
For a stackless coroutine the compiler normally has to build a state machine to represent the possible execution contexts, but if the state machine includes itself then it has indeterminate size. Normally you solve this by boxing the state machine at yield points and using dynamic dispatch to call or resume a pending coroutine - which may be significantly slower than using a stackful coroutine to begin with (in which case, the stack is allocated on the heap up front, leading to a higher price to spawn the coroutine, but lower to resume or yield).
With a state machine transform the `resume()` method on a coroutine is a state transformation, it doesn't necessarily know what is "forward" or "backward" in the control flow graph. There are some tricky bits though, since tail recursive functions can have multiple exits but single entries. A recursive coroutine might have multiple exits and multiple entries, so it's not always clear what is "forward" and what is "backward."
Well, they are obviously not stack frames because they do not follow a stack discipline, but they certainly are activation frames. I guess that's the point you were trying to make?
Is there no setjmp/longjmp happening? Are C++ 20 coroutines all just compiler slight-of-hand similar to duff's device with no real context switching?
Why? C/C++ already has stackful coroutines. And that seems extremely wasteful unless you know you'll run the coroutines at the same time... with single threaded stackful coroutines, you'd get two stacks and only ever use one at a time. that wastes megabytes, requires heavier context switching, makes cache misses more likely, etc.
The advantage is that the worst case frame size is bounded, small and computable at compiletime.
Stackfull coroutines are of course more powerful and have, IMHO, better ergonomics.
- C++20 coroutines are stackless, meaning that multiple coroutines will share a single OS thread stack. This is non-obvious when you first look in to them them because coroutines look just like functions. The compiler does all the work of ensuring your local variables are captured and allocated as part of the coroutine yield context, but these yield contexts are not stack frames. Every coroutine you invoke from a coroutine has its own context that can outlive the caller.
- C++20 coroutine functions are just ordinary functions, and can be called from C code. In fact, coroutine contexts can be cast to and from void* to enable their use from C.
- There's currently no generic utility in the C++20 standard library for using C++20 coroutines to defer work. std::future<> only works on threaded code, and you can't really use std::function<>. See Lewis Bakers 'task' class for a usable mechanism https://github.com/lewissbaker/cppcoro#taskt