Some notes: - 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 inv…
My tutorial and take on C++20 coroutines
111–120 of 144 posts
Re: My tutorial and take on C++20 coroutines
#112Re: My tutorial and take on C++20 coroutines
#113Its insane how complex C++20 coroutines are when compared with Rust coroutines.
Can you say more on this? What is Rust doing differently here that simplifies things?
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.
Re: My tutorial and take on C++20 coroutines
#114Earlier quoted context omitted.
Also, if it’s not clear, within a coroutine, you can call any function (or coroutine) you want. It’s just that to co_yield, you have to be in the coroutine not deep in the stack.
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?
Re: My tutorial and take on C++20 coroutines
#115Part 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…
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…
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?Re: My tutorial and take on C++20 coroutines
#116Earlier 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?
class Stooge {...};
CoroutineIterator three_stooges() {
co_yield Stooge("Larry");
co_yield Stooge("Curly");
co_yield Stooge("Moe");
}Re: My tutorial and take on C++20 coroutines
#117Some notes: - 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 inv…
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).
Re: My tutorial and take on C++20 coroutines
#118Seems like once again we are stuck with an overly complex and un-ergonomic design that is going to burden every single C++ programmer for a decade, until someone gets fed up and fixes it. Just like chrono, just like random, just like std::string, all of std::algorithm, etc. God damn it.
Re: My tutorial and take on C++20 coroutines
#119To 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...
Re: My tutorial and take on C++20 coroutines
#120Earlier quoted context omitted.
Can you say more on this? What is Rust doing differently here that simplifies things?
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…