Live data from Hacker News

My tutorial and take on C++20 coroutines

scs.stanford.edu

101–110 of 144 posts

Re: My tutorial and take on C++20 coroutines

#101

As a developer, when you decide to use bleeding edge C++?? extensions keep in mind that when you do so you're making it much, much harder to run any of your code on a distro more than 4 years old. Is it worth it?

I can't find the article, but there was a rant a little while back that essentially panned C++ as veering into the "move fast and break things" mentality. Their recommendation was something to wait on the order of 7 years, where one should expect C++14 features to be generally solid and performant by now, but to treat everything else with caution. If you're supporting 4 year old distros, you might stick with C++11.

Re: My tutorial and take on C++20 coroutines

#102
post #97
post #93

Earlier 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…

Isn't Future lifetime must be tied to I/O operation, so Future will not outlive I/O operation? Can you post an example, please?

Re: My tutorial and take on C++20 coroutines

#103
post #97
post #93

Earlier 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…

[deleted]

Re: My tutorial and take on C++20 coroutines

#104
post #19

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…

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…

[deleted]

Re: My tutorial and take on C++20 coroutines

#105

What I find interesting about coroutines is that they are relatively trivially achieved in hand-coded assembly using `jmp` and being careful about registers in a way that makes sense. `jmp`ing around is a pretty normal way to code in assembly. In a sophisticated environment they become a beast. It surprises me that we actually do lose something meaningful when abstracting to higher level languages.

[deleted]

Re: My tutorial and take on C++20 coroutines

#106
post #84

Earlier 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.

The problem is coroutines need compiler support before you can play with them. Boost coroutines were rejected because doing coroutines only as a library just isn't pretty (it works, but nobody liked it), so looking at other languages the current direction was decided. Only now that you can do something can libraries spend the decades to figure out how it all fits into C++.

Re: My tutorial and take on C++20 coroutines

#107

Earlier quoted context omitted.

You are right that you are effectively stuck in a single coroutine (non-stack) frame. But you can chain multiple such coroutine frames, because one coroutine can co_await another.

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

#108
post #18

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…

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. 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
    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 skip_iter(int skip, int stop) {
      for(int i=0;i

Re: My tutorial and take on C++20 coroutines

#110

Earlier 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?

All those languages have also stackless coroutines. Notably Lua and Go have stackful coroutines.

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.

Post reply on HN