Part of this is that I’m tired, but it blows my mind how difficult C++ coroutines are as someone who considers themselves decent at C++ (although maybe I’m not) and uses coroutines in other languages. The amount of code needed to do almost nothing is extraordinary, and putting it all together doesn’t seem like you would often get on your first try. I get that new keywords basically can’t be added, but man, that’s pai…
A part of the tis that the standards committee decided to do a multi phase rollout. They created the core language parts and will hopefully in a future version add the library support making it easier to approach. (And then hopefully find that the design works with the library design ...)
My tutorial and take on C++20 coroutines
131–140 of 144 posts
Re: My tutorial and take on C++20 coroutines
#132Earlier quoted context omitted.
I would say, yes. C++11 is, in some ways, a totally new and more powerful language. I was sold on it as soon as I started learning. C++14 and C++17 add some useful features. I know basically nothing about C++20.
As a non-C++ professional programmer question, what's the occurrence of the codebases you work on, where the standard is significantly/fundamentally C++ 14/17?
Re: My tutorial and take on C++20 coroutines
#133Earlier quoted context omitted.
Isn't it like that is most languages? I'm thinking about Python, C#, JS. If you call a blocking function from an async function, you cannot yield from deep inside the blocking function. 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's a big deal because, while it has some downsides, being stalkless means they can have next to no overhead, meaning it can be performant to use coroutines to write asynchronous code for even very fast operations. The example given https://www.youtube.com/watch?v=j9tlJAqMV7U&t=13m30s is that you can launch multiple coroutines to issue prefetch instructions and process the fetched data, so you can have clean code th…
Re: My tutorial and take on C++20 coroutines
#134Earlier quoted context omitted.
I find the Rust design very simple: a coroutine is just a state machine, i.e. just a C struct. I find this very easy to reason about. 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. w…
I'm not sure that answers my question; C++ also uses a state machine. Most of the post is concerned with the compiler library interface - where Rust uses Generator, GeneratorState, Pin, etc. Is there something fundamentally different about the design here?
`Generator` and `GeneratorState` are not exposed or usable. There are no knobs to turn like C++'s `await_ready` or `early_suspend`/`final_suspend`. There is no implicit heap allocation or corresponding elision optimization, and thus no need to map between `coroutine_handle`s and promise objects.
To be fair, C++'s design is a bit more flexible in that it supports passing data in and out of a coroutine. But even if you look at Rust's (unstable work-in-progress) approach to supporting this, the compiler/library interface is still way simpler. The difference is really not related to how much functionality is stabilized, but how scattered and ad-hoc the C++ interface is.
Re: My tutorial and take on C++20 coroutines
#135Earlier quoted context omitted.
> And from what I've read they are better than Rusts coroutines for this use case Reference please? In what sense are they better, and what makes them better?
Rust's Futures don't have asynchronous destructors (I don't know if coroutines do). 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…
However that is not a general property of c++ coroutines. The generator style coroutines also seem randomly cancellable
Re: My tutorial and take on C++20 coroutines
#136Earlier quoted context omitted.
I find the Rust design very simple: a coroutine is just a state machine, i.e. just a C struct. I find this very easy to reason about. 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. w…
I'm not sure that answers my question; C++ also uses a state machine. Most of the post is concerned with the compiler library interface - where Rust uses Generator, GeneratorState, Pin, etc. Is there something fundamentally different about the design here?
For a very long time, async/await were just normal Rust macros; there was no compilerlibrary interface.
For a year or so, async/await are proper keywords, which provides nicer syntax, and some optimizations that were hard to do with macros (e.g. better layout optimizations for the state machines).
But that's essentially the whole thing.
Looking at safety, flexibility, performance and simplicity, Rust design picks maximum safety, performance, and simplicity, trading off some flexibility in places where it really isn't necessary:
- you can't move a coroutine while its being polled, which is something you probably shouldn't be doing anyways
- you can't control the layout of the coroutine state for auto-generated corotuines; but you can lay them out manually if you need to, for perf (the compiler just won't help you here)
- you need to manually lay out a coroutine and commit to the layout for using them in ABIs
C++ just picks a different design. 100% safety isn't attainable, maximum flexibility is very important, performance is important, but if you need this you have alternatives (callbacks, etc.). I personally just find the API surface of C++ coroutines (futures, promises, tasks, handles, ...) to just be really big.
Re: My tutorial and take on C++20 coroutines
#137Earlier quoted context omitted.
What do you mean nested call? As a first approximation, you need an heap allocation for each coroutine function instance for its activation frame. Every time a coroutine instance is suspended, the previously allocated frame is reused. If you instantiate a coroutine from another coroutine, then yes you need to heap allocate again, unless the compiler can somehow merge the activation frames or you have a dedicated allo…
Is that a given, though? Rust's generators are decent prior art - they generate a state machine that would only require heap allocation if the size of the state machine becomes unbounded (for example, a recursive generator). Otherwise the generator is perfectly capable of being stack allocated in its entirety. This turns out to be sufficient for a large amount of programs, with a sufficient workaround for the ones wh…
There are already proposals to improve on the design, but we will have to see if they work out.
Re: My tutorial and take on C++20 coroutines
#138Earlier quoted context omitted.
I'm trying to wrap my head around what the implication of stackless coroutines is. 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…
> but can I await a function, which awaits another, and so on? 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.
Re: My tutorial and take on C++20 coroutines
#139Earlier quoted context omitted.
Doing things right takes time. This is one step in making co-routines useful. Library authors now have something to work with to figure out what the next part of the C++ standard is. Expect standard library support in C++ in either c++ 23 or 26.
I agree with GP, I feel a similar disdain when I hear that features that are merged into the standard are for the "library implementors". Boost.Coroutine and Boost.Asio have been around for how long? At least a decade? I think there has been more than enough experience with coroutines and async programming to get coroutines in standard C++ done so that at least the "average" C++ programmer can grok them.
Re: My tutorial and take on C++20 coroutines
#140Earlier quoted context omitted.
This is how I view iterators, but with an extra notch in difficulty. You really need to understand the coroutine model to get anywhere -- and the same is true of iterators. Are these leaky abstractions? I generally think of leaks as "pushing", but this is "pulling" -- I'd call 'em sucky abstractions. But I digress. I just sat down with the tutorial and banged out what I consider to be a holy grail for quality of life…
So, given sibling comments on stack and function calls, this: skip_iter(int skip, int stop) for(int i=0;i Wouldn't work with complex types, boxed integers and so on? Because calling postfix ++ would be a function/method call?