- 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
* 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.
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.
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.
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).
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.
1: like most of C++ one would way
Basically how the library realizes this is opaque to the user and indeed it does things like reuse low level primitives available in each of the platforms. Basically, at it's lowest level it is simply syntactic sugar for callback based implementations common in platforms that have things like Futures, Promises, etc.
In fact it's easy to integrate any third party library that provides similar constructs and pretend that they are suspend functions. E.g. the suspendCancellableCoRoutine builder can take anything that has a success and fail callback and turn it into suspending code blocks. For example Spring supports coroutines and flows out of the box now and provides deep integration with its own reactive webflux framework. So, you can write a controller that is a suspend function that returns a flow and it just works as a nice reactive endpoint.
I actually would expect that these new C++ capabilities will also find their way into Kotlin's native coroutine implementation eventually. Kotlin native is still in Beta but seems popular for cross platform IOS and Android library development currently.
Kotlin's coroutine library took quite a while to get right for the Kotlin developers and evolved a lot between Kotlin 1.2 and 1.4. Parts of it are still labelled as experimental. Particularly, reactive flows are a relatively late addition and have already had a big impact on e.g. Android and server-side programming.
I believe that the design is both unneededly complex and too unflexible, but this is the only design that managed to get enough traction to get into the standard and it took forever. There are competing designs and improvements, we will have to see if they will make it into the standard.
I just sat down with the tutorial and banged out what I consider to be a holy grail for quality of life. Alas, it's about 50 lines of ugly boilerplate. I'm omitting it, in part because it's untested, but mostly because I'm embarrassed to share that much C++ in a public forum. If anybody close to the C++ standards is listening... please make this boilerplate not suck so hard.
template<typename T>
struct CoroutineIterator {
left for the reader!
hint: start with the ReturnObject5 struct in the article,
and add standard iterator boilerplate
}
//! yields the integers 0 through `stop`, except for `skip`
CoroutineIterator<int> skip_iter(int skip, int stop) {
for(int i=0;i<stop;i++)
if(i != skip)
co_yield i;
}
int main() {
for(auto &i : skip_iter(15, 20)) {
std::cout << i << std::endl;
}
} skip_iter(int skip, int stop)
for(int i=0;i<stop;i++)
Wouldn't work with complex types, boxed integers and so on? Because calling postfix ++ would be a function/method call?But for folks working on gamedev libs, high-performance async, etc would probably prefer making their own task/promise-type for hand-crafted customization.
It adds an additional foot-gun w.r.t. to e.g. by-reference parameters to functions and their lifetime. The function parameter might not be alive anymore after a "co_await" and it doesn't require any capture (like lambda) or has any hint about this being the case.
Then, the tooling isn't there yet (other than the missing standard library). Gdb doesn't show correct lines and can't print local variables when in coroutines. If there is a deadlock one can't see the suspended coroutine (and its call stack) holding the lock, etc.. Back to printf debugging...
Reference please? In what sense are they better, and what makes them better?
When a Future is aborted early, it's destroyed immediately with no remorse. This means it can't simply offer its own buffers to the kernel, because the kernel could write back after the Future has been freed.
An API contract that includes "just be careful not to do the stupid thing" is not good enough by Rust's standards, so the only way to guarantee safety would be to have Future's destructor wait synchronously until the I/O operation is cancelled on the kernel side, but that's inelegant in an async context.
Well, that sounds like more fun than a tax increase.
It does not require memory allocations, does not require a run-time, works on embedded targets, etc.
Also, the compiler generates all the boilerplate (the state machine) for you, which I find makes it very easy to use. And well, the compiler ensures memory safety, thread safety, etc. which is the cherry on top.
optional<T> poll(*waker_t)
When polled it either returns the final result or that it's pending. If it's pending, it keeps the reference to the waker arg, and uses it to notify when it's ready to be polled again.This design is very composable, because a Future can trivially forward the poll calls to other futures, and the wakers can be plugged into all kinds of existing callback-based APIs.
async/await is a syntax sugar on top of that that auto-generates the poll methods from async function's bodies (where each poll() call advances state to the next .await point).
There's no built-in runtime in the language that does the polling. You make your own event loop, and that gives you freedom to make it simple, or fancy multithreaded, or deterministic for test environments, etc.
To see how it can be used for actual asynchronous operations on a thread pool, take a look at asyncly, which I co-authored:
https://github.com/LogMeIn/asyncly/blob/master/Test/Unit/fut...
- create a new task (start over) and maybe delete the old task
- implement the task as a generator that can repeatedly yield values
I think this is as good as you can hope for without lots of other interfaces (and since you can implement all the promise types yourself, you can do that too).
Coroutines help solve IO-bound issues - a long networking API call doesn't block your program. You can continue doing other things. The side-effect of which may use all your cores efficiently but that's not the primary goal.
You still need threads.
does it, the former line, really count as a tutorial at the very beginning?
The end result is the fever that is containerization as a symptom of the sickness that is future shock from devs always using the latest.
I know classic c++, but don't really use it anymore.
I would say the experience is about what you would expect. There are some things that are great, that are cleaned up and more consistent. There are more conveniences. But then you run into weird edge cases / quirks, or a cascade of subtle compile errors, and just use a macro instead.
I'm writing a garbage collector and there are a bunch of things where it helps over C, but also a bunch of areas where it falls short. In summary, C++ is still great and frustrating at the same time. If anything that property seems to have increased over time: It's even more great and even more frustrating than it ever was :) Coroutines look like another example of that.
* ranges means that we're starting to get composable algorithms. Compared to doing `for_each` and then `accumulate` on a container, you can compose both operations into one reusable "pipeline" of operations. I really grew to like this from other languages, and am glad that C++ is starting to get it * modules will help a lot, but I doubt we'll get widespread use for another year at least (until cmake has it; build2 has it, but that's a bit esoteric even though it's very pleasant to work with) * concepts make working with template-laden code a lot easier. Most of the tricks for obscure SFINAE removal of templates are no longer necessary; one can just express it in a "positive" way as if to say "the thing matching this template should have these properties", instead of convoluted ways to turn off templates under certain circumstances. * coroutines are very half-baked, but when combined with executors (which will hopefully come in 23) it will really be useful I think. The stackless nature makes them hard to reason about, but trying to add stackful coroutines would likely be a non-starter; it would require a lot of runtime support and modifications that would either require a lot of performance overhead OR backward-compat difficulties.
That said, unless you want to actually use it to DO something specific (e.g. make a game, work at a specific company), Rust or similar languages are probably more pleasant experiences. Without a standardized build tool with sane defaults, it's hard to just play around with things in a fun way. Trying to figure out how CMake + Conan works will take beginners at least a day in my experience, and the lack of a solid template "good defaults, applicable most places but easily changeable" for the build system + dependency system makes things a bit tedious to just try things out.
Some would say C, but in domains like machine learning, you really don't want to write plain old C for what is running on the GPGPU.
Still, with all its issues, I do quite like the language, but unless you want to work on some industry that is C++-centric, I do not think it is worth learning just for the sake of it.
(Hopping onto this topic,) I wonder, what are the right resources for properly diving into the modern C++? Are there books that are up to date? Tutorials? What has worked for HNs the best?
In strict technological terms, there's for example the gamedev domain, which is C++ dominated, so a legitimate and non-obvious doubt, is if following up with such language is an appropriate choice or not.
Then there's the business side. It's a bit obscure how much Rust is used in BigCos; it's clearly catching on, but the extent is not obvious. Therefore, if one aims at, say (random pick) a Google job, up to date C++ knowledge may be more advantageous.
For the case when one is learning a new language (which is not the parent's case) _and_ they're not constrained by legacy/context, though, I agree that one should not even mention memory unsafe languages :)
Good article though.
Coroutines are doable if you're programming directly in assembly, but if you want to do it in C-like structured languages, it turns out that it's really tricky: the whole concept of those languages are about having a single stack and hierarchical lexical structure. You can do coroutines in languages like this, but unless you want to do nasty things with longjmp and manually manipulating the stack pointer, they simply aren't built for it. You can build structured languages with first-class support for constructs with coroutines, but it took a couple of decades of programming language development for that to happen. Even today, most of the languages that support coroutines (C++20 included), only has support for the stackless kind. Basically the only languages with full stackful coroutine support in wide use are Lua and (sort-of) Go.
C with Classes had them from the beginning, as Stroustrup liked them in Simula, but then (like many other things) had to take them out of the language after user feedback.
Re stackless coroutines and language support, while it is relatively straightforward to have stackfull coroutines as a library, the stackless kind in practice needs to be implemented as a language feature.
Aren't Javascript Generators also coroutines?
But C++ coroutines don't seem too incompatible with the level of OOP you already get in C++, no?
... no. coroutines are not supposed to be used in, like, more than an extremely small fraction of your codebase if you want to keep your code understandable
https://stackoverflow.com/questions/636829/difference-betwee...