[HN Gopher] My tutorial and take on C++20 coroutines
___________________________________________________________________
My tutorial and take on C++20 coroutines
Author : erwan
Score : 167 points
Date : 2021-02-22 07:14 UTC (15 hours ago)
(HTM) web link (www.scs.stanford.edu)
(TXT) w3m dump (www.scs.stanford.edu)
| oivey wrote:
| 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 painful.
| johannes1234321 wrote:
| 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 ...)
| Davidbrcz wrote:
| C++ coroutines as they are in the standard are not really
| intended to the everyday programmer (1), but rather for library
| implementers who will use and abstract them into higher level
| features.
|
| 1: like most of C++ one would way
| oivey wrote:
| Ultimately, I think the simultaneous complexity and lack of
| built in functionality will limit how often people end up
| using this new part of the standard. I can use coroutines to
| write high performance, parallel code in other languages
| without being a mythical ~library implementor~. I usually
| even write libraries in those languages.
| bluGill wrote:
| 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.
| swagonomixxx wrote:
| 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.
| bluGill wrote:
| 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++.
| mazieres wrote:
| Agreed. I think the killer use is for event-driven programs
| that already do a lot of "stack ripping". If you are
| already manually putting all your "local" variables into an
| object and slicing your functions into many methods, then
| the grossness of C++20 coroutines will be easy to digest
| given how much more readable they can make your code.
| pjmlp wrote:
| Currently WinRT has the best support for C++ co-routines,
| Microsoft was after all the main driver of the proposal,
| and they map to the Windows concurrency runtime.
|
| So on WinRT it is hardly any different from what C++/CX
| allowed for in concepts.
| gpderetta wrote:
| Not really, coroutines are supposed to be user level
| constructs. I.e most users should be able to write functions
| that yield or await. Unfortunately the machinery surrounding
| coroutines is very complex, but that can be hidden behind
| library functions.
| jillesvangurp wrote:
| This is a similar approach to what Kotlin did with its co-
| routine implementation. Aside from the suspend keyword, there
| are no language features that you work with directly. Instead
| everything is done via the kotlinx-coroutines library.
| Kotlinx means it is a multiplatform library with
| implementations for native, js, and jvm that do the
| appropriate things on each platform.
|
| 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.
| varispeed wrote:
| What exactly do you find complex? I see that it is just a blob
| of C++ that is difficult to read at a first glance, but if you
| get past that, it is actually super simple.
| babooska wrote:
| I hope C++ at some point gets another frontend in terms of
| syntax. Languages should probably be specified in terms of
| abstract syntax instead of being stuck with a bad frontend
| like C++.
| pjc50 wrote:
| This is all down to not having a "managed runtime", so it has
| to be shoehorned into the environment available where you have
| a stack, a heap, and that's basically it.
| lights0123 wrote:
| For me coming from Rust, this just seems way overly
| complicated to me. Rust's async doesn't even need a heap to
| work: https://lights0123.com/blog/2020/07/25/async-await-for-
| avr-w...
| gpderetta wrote:
| Requiring heap allocation for coroutines was extremely
| contentious to say the least. But because C++ does not have
| a borrow checker, all the other allocation-less designs
| proved to be very error prone.
|
| C++ coroutines do support allocators [1], so it is not a
| huge issue, but it does complicate the design further.
|
| [1] the compiler is also allowed to elide the allocation,
| but a) it is not clear how this is different from the
| normal allocation elision allowance that compilers already
| had, and b) it is a fragile optimization.
| tijsvd wrote:
| Does that mean that every nested coroutine (async call)
| needs another heap allocation, or just the top level one?
| gpderetta wrote:
| 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 allocator.
| hctaw wrote:
| 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 where you can't (box
| the generator, making the allocation explicit).
| Cyph0n wrote:
| Great article, thanks for sharing!
| gpderetta wrote:
| They are very complex, and they kept being patched up till the
| last minute as more corner cases were discovered.
|
| 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.
| klyrs wrote:
| 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<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; } }
| e12e wrote:
| So, given sibling comments on stack and function calls, this:
| 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?
| klyrs wrote:
| That's just a simple example. I think the following should
| work, possibly with modification for forwarding:
| class Stooge {...}; CoroutineIterator<Stooge>
| three_stooges() { co_yield Stooge("Larry");
| co_yield Stooge("Curly"); co_yield
| Stooge("Moe"); }
| perennus wrote:
| Typical HN comment I know, but how is this blog generated? It
| looks fantastic. Is this only Markdown + Pandoc?
| nly wrote:
| 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 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
| _huayra_ wrote:
| To clarify what stackless means when you're writing code:
|
| * 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.
| mazieres wrote:
| 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.
| _huayra_ wrote:
| Thanks for this great article! I've been circling my way
| around understanding coroutines and this really put it in
| place.
|
| 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".
| BenFrantzDale wrote:
| 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.
| captainmuon wrote:
| 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?
| BenFrantzDale wrote:
| 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 that issues multiple prefetches and
| process the results. Whereas in Python (don't get me
| wrong, I love Python) you might use a generator to
| "asynchronize" slow operations like requesting and
| processing data from remote servers, C++ coroutines can
| be fast enough to asynchronously process "slow"
| operations like requesting data from main memory.
| gpderetta wrote:
| 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.
| gpderetta wrote:
| > These yield contexts are not stack frames though. Every
| coroutine you invoke from a coroutine has its own context that
| can outlive the caller.
|
| 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?
| nly wrote:
| Yes
| hctaw wrote:
| Are C++20 coroutines allowed to be recursive? Or does recursing
| require boxing?
|
| 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).
| c-cube wrote:
| I'm curious. Intuitively, a non-recursive coroutine would
| yield a state machine that only goes "forward". If you add
| tail recursion into the mix, you could have cycles in the
| state machines (going back to the begin state), correct? Of
| course non-tail recursion would not work within a single
| frame.
| hctaw wrote:
| Yes, a tail recursive coroutine could reuse its previous
| frame context across yields.
|
| 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."
| dieters wrote:
| There's none in the standard library, but e.g. Seastar already
| has coroutine support implemented for its future<>s and it
| works really well - the code looks clearer and in many cases it
| reduces the number of allocations to 1 (for the whole coroutine
| frame).
| captainmuon wrote:
| 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 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...
| gpderetta wrote:
| C++, C#, Python coroutines are all stackless and pretty much
| equivalent. Lua, Go have stackful coroutines (i.e. one-shot
| continuations). There are libraries for stackful coroutines
| in C++ of course.
|
| 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).
| nly wrote:
| Stackless means when you resume a coroutine you're still
| using the same OS thread stack. Coroutine contexts/activation
| records are conceptually heap allocated (although in some
| cases that can be optimized away).
|
| 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.
| mazieres wrote:
| > Stackless means when you resume a coroutine you're still
| using the same OS thread stack
|
| 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.
| hctaw wrote:
| > 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.
| jcelerier wrote:
| > I'm really looking forward to rewrite some Qt callback hell
| code into async...
|
| last time I tried it was pretty easy to make C++ coroutines
| fit into Qt's event loop.
| [deleted]
| snarfy wrote:
| It seems really dumb that they are stackless. If you are
| saving/restoring the stack pointer anyway in your yield routine
| it's trivial to set it to a block of memory you allocated in
| the initial coroutine creation.
|
| 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?
| gpderetta wrote:
| The compilation model is implementation defined, but yes, the
| expectation is that they compile down to a switch-like
| explicit state machine.
|
| 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.
| account4mypc wrote:
| > It seems really dumb that they are stackless
|
| 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.
| hctaw wrote:
| Modern stackful coroutines don't (or shouldn't) use context
| switching (at least not anything like ucontext_t, just use
| threads if you're going to spend the cycles to preserve the
| signal mask) or setjmp/longjmp. Those tricks are slow and
| hazardous, especially when code needs to cross language
| boundaries or remain exception safe.
| dilawar wrote:
| I see they are excellent for concurrent programming but is it
| possible to utilise all cores of processor effectively with
| coroutines?
| jcelerier wrote:
| it'd be very trivial with something such as ASIO's io_context
| as your coroutine event loop:
| https://dens.website/tutorials/cpp-asio/multithreading
| b0sk wrote:
| Not necessarily. Concurrency is generally not parallelism.
|
| 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.
| rightbyte wrote:
| No. Think of the coroutines as goto:s with a dynamic label that
| does some magic to replace locals too.
|
| You still need threads.
| [deleted]
| panpanna wrote:
| Is learning modern C++ worth the effort in 2021?
|
| I know classic c++, but don't really use it anymore.
| chubot wrote:
| I also know classic C++ and used it professionally, although it
| wasn't my main language. I've started using more C++ 11, 14,
| 17, lately. Particularly with constexpr, and some more stuff
| with templates and smart pointers.
|
| 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.
| bregma wrote:
| By 'classic' do you mean C++17, or do you mean 'C with Classes'
| from a quarter century ago?
| brobdingnagians wrote:
| Depends on what you want to use it for. Game programming with
| UE4 is much nicer with modern c++ syntax, or anything where you
| want to push the boundaries; but I use kotlin for most things
| because the syntax is much more concise and the performance
| isn't an issue.
| Cthulhu_ wrote:
| How much C++ coding does a modern game still require? I
| would've thought most of the (coding) effort would be in a
| scripting language inside the game engine's editors.
| jcelerier wrote:
| all my friends who do gamedev for their day job are 100%
| C++ except that one who generally works on the menus,
| settings, etc. (in semi-large / large french video game
| companies)
| goodcanadian wrote:
| 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.
| pizza234 wrote:
| 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?
| goodcanadian wrote:
| I don't know how to answer that. For me, it is 100%, but it
| is a small sample size and one where I, in part, set the
| standard.
|
| If you have a C++11 code base, you don't really lose
| anything by adopting C++17, and you do gain a few things.
| pizza234 wrote:
| Interesting; I've always mistakenly assumed that C++
| codebases working with different standards would
| necessarily end up being a patchwork.
| johannes1234321 wrote:
| If you combine larger libraries from pre-eleven with
| post-eleven libraries you will see the different worlds
| (Qt as a prime example), small libraries can easily be
| enhanced with smart pointers etc. so do you don't have
| much of a clash. After eleven most features are nice
| things, which make things a bit nicer here or there, but
| don't change the code as much as reliance on moves and
| smart pointers do with eleven. Also upgrade from C++11 to
| C++17 is as painless as a compiler update can be.
| (Meaning: in some corner case you will probably hit some
| compiler optimization caused issue, but for most code
| it's a non-issue)
| goodcanadian wrote:
| I'm sure someone will come up with a counterexample, but
| my experience is that pretty much everything is backwards
| compatible.
| bregma wrote:
| C++17 was backwards compatible with the venerable C++11
| in almost all ways except for the removal of some library
| constructs few people actually use and have long been
| replaced by superior models, like `std::auto_ptr` and the
| old binder functors.
| pjmlp wrote:
| As much as I like some replacement candidates, C++ is
| everywhere, so if you want to do anything related with
| graphics, machine learning, compiler development, OS drivers
| ,IoT toolchains, and not having the burden to sort out
| infrastructure problems by yourself, then C++ is the less
| painful way to go.
|
| 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.
| gpderetta wrote:
| Yes, you do not program in C++ because you want to program in
| C++. You do because a lot of cool, interesting (and yes, well
| paid) stuff is done in C++.
|
| 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.
| pjmlp wrote:
| Same here, I keep referring how C++ was the upgrade path
| from Turbo Pascal because I never came clear with bare
| bones C.
|
| Since 2002, it has been a mix of .NET, C++ and Java, with
| my C++'s usage decreasing since those days, yet it is never
| zero, there is always a library or OS API to create
| bindings for.
|
| I also like the language, but I definitely do not fit the
| culture of every ms and byte counts that they inherited
| from C.
|
| On the C++ code where I have full control, RTTI, exceptions
| and STL bounds checking are always enabled, and I am yet to
| find a project where it mattered to have done otherwise.
| adamnemecek wrote:
| Just learn Rust.
| pizza234 wrote:
| I'm a big Rust supporter/advocate (and I do use it for
| personal, non-trivial, programs), but I still suggest it only
| for a minority of the cases, as real-world is complicated.
|
| 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 :)
| _huayra_ wrote:
| "worth the effort" is very difficult to quantify. If you
| haven't touched C++ in a while, I'd say the things in C++20 are
| intriguing, even if half-baked:
|
| * 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.
| GreenWinters wrote:
| > Is learning modern C++ worth the effort in 2021?
|
| (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?
| [deleted]
| mazieres wrote:
| I really liked Josuttis's book on C++17
| (https://www.cppstd17.com/). The only C++20 book I know of is
| the Grimm book linked in the referenced blog post
| (https://leanpub.com/c20). I'm glad I read it, but honestly
| it's a bit rough still. To be fair, though, it's still only a
| draft so will probably improve. And I'm assuming you already
| have C++11. There are a lot of resources for that one. I
| happened to use Stroustroup 4th edition, but I'm willing to
| believe there are better books, particularly for people
| already familiar with C++03.
| volta83 wrote:
| Its insane how complex C++20 coroutines are when compared with
| Rust coroutines.
| dnautics wrote:
| screw rust (which needs async-std), look at how zig does
| coroutines.
| [deleted]
| hctaw wrote:
| You don't need async-std for coroutines (generators) in rust
| (or async at all, they are two different, albeit related
| features). Async is implemented using generators.
| millstone wrote:
| Can you say more on this? What is Rust doing differently here
| that simplifies things?
| volta83 wrote:
| 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.
| which is the cherry on top.
| dralley wrote:
| Technically it's an enum, or tagged enum.
| creedor wrote:
| I'm a bit confused by all of this. The idea of coroutines existed
| since the 60's. Then we came up with OOP that tries to combine
| function + data. And now we abolished this, are back at only
| functions and are now solving the 'state capture' problem for
| functions again with: Coroutines? How does the history of
| programming paradigms/patterns make any sense? :)
|
| Good article though.
| jcelerier wrote:
| > . And now we abolished this,
|
| ... 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
| oivey wrote:
| Eh, I think coroutines are a convenient way to achieve
| concurrency and parallelism. If you limit mutation and try to
| reduce the long reaching accessibility of variables then I
| think they're generally very understandable, especially
| compared to other concurrency/parallelism paradigms.
| jcelerier wrote:
| > Eh, I think coroutines are a convenient way to achieve
| concurrency and parallelism.
|
| but how often do you need concurrency and parallelism
| outside of handling network requests and performing some
| complicated math algorithm (which may be in a lib that you
| don't touch anyways, e.g. FFT>) ?
|
| e.g. most UI code has to run on the main thread due to
| macOS / Windows limitations, and in most UI software it is
| extremely rare to have the kind of chains of callbacks
| whose readability gets improved by coroutines.
| gpderetta wrote:
| Coroutines are not really for parallelism. I doubt you'll
| see them much around math heavy code. Possibly if someone
| implements some Cilk style work stealing executor...
| pjmlp wrote:
| Ideally, but they taint everything they touch, that is why in
| C# I always start them from some point with Task.Run(), or
| leave it for the event and request handlers.
| sheenobu wrote:
| The glib answer is that these kinds of things are cyclical.
|
| But C++ coroutines don't seem too incompatible with the level
| of OOP you already get in C++, no?
| Mikhail_Edoshin wrote:
| Objects and coroutines are very closely related.
| jussij wrote:
| These are features built into the language designed to make
| multi-threaded code safer and easier to write.
| OskarS wrote:
| Your analysis of history there is a bit lacking. Coroutines
| didn't go out of fashion because of OOP, it went out of fashion
| because of structured programming and higher level languages.
|
| 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.
| spc476 wrote:
| Or take advantage of the ABI of the runtime, and use
| assembly. [1] Yeah, not portable. But using
| setjmp()/longjmp() has issues as well (way too easy to mess
| it up because it doesn't do what you think it's doing).
|
| [1] https://github.com/spc476/C-Coroutines
| gpderetta wrote:
| There is no particular difficulty in having one-shot
| continuations in C (or C++) and in fact over the last few
| decades there have been plenty of libraries implementing
| them. They just never caught on with the general C and C++
| programming population, although they were popular in some
| niches.
|
| 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.
| diskmuncher wrote:
| > Basically the only languages with full stackful coroutine
| support in wide use are Lua and (sort-of) Go.
|
| Aren't Javascript Generators also coroutines?
| gpderetta wrote:
| they are, as far as I know, stackless. I.e. you can only
| yield from the generator top level frame.
| TargetedVictim wrote:
| Dutch police and mainstream media is trying to kill me over
| bitcoin and ethereum, for over 3 years now.
| https://pastebin.com/btAfNf3T
| wapxmas wrote:
| "std::coroutine_handle<> *hp_;"
|
| does it, the former line, really count as a tutorial at the very
| beginning?
| tobias3 wrote:
| I've used C++ coroutines (with io_uring). They are really useful
| in this case and allows one to write code like one would with the
| simple blocking API. And from what I've read they are better than
| Rusts coroutines for this use case (and for the yield use case
| they aren't good).
|
| 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...
| tijsvd wrote:
| > 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?
| pornel wrote:
| 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 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.
| drran wrote:
| 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?
| [deleted]
| cozzyd wrote:
| Note that the author (in addition to being a great professor) is
| responsible for one of my favorite papers:
| https://news.ycombinator.com/item?id=11098655
| sempron64 wrote:
| 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]
| gnulinux wrote:
| Of course we lose something when we abstract to higher level
| languages, that's exactly what it means to abstract out. It'd
| be more surprising other way around. For example, when you're
| programming in a language like Haskell, Python or Java you
| cannot micromanage your pointers etc. This is because these
| languages abstract away the memory layout of objects. If you do
| hacky things and override the memory intentionally you can
| cause e.g. garbage collection to misbehave etc. On the other
| hand in C++ or C you can manage exactly how objects are encoded
| in the memory, when and how you allocate memory for your
| system. All these things are not things you're meant to be able
| to "customize" in higher level languages.
| jupp0r wrote:
| Keep in mind that these is a really basic building block where
| you can bring your own runtime and hook coroutines into it, not
| something that is at all usable out of the box. This is
| exacerbated by the fact that the C++ standard library is still
| lacking support for non-blocking futures/promises and queued
| thread pools to run them.
|
| 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...
| signa11 wrote:
| i used this : https://blog.panicsoftware.com/coroutines-
| introduction/ a while back. maybe someone else might find it
| useful too...
| zarkov99 wrote:
| Seems 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.
| dralley wrote:
| You have hope that someone is going to fix it? You're an
| optimistic soul :)
| smallstepforman wrote:
| From the existing explanations, I've deduced that coroutines are
| a form of continuation based coopetative multitasking. Is it
| possible to "restart" the sequence, and how would this be done?
| gpderetta wrote:
| As far as I understand, they are one-shot continuations, so no,
| they are not restartable unfortunately. I believe at some point
| one proposal had copyable (and hence multi-shot) continuations,
| but the proposal had other issues.
| smallstepforman wrote:
| Thanks for the quality response. Without restarts, it limits
| the use cases.
| sesuximo wrote:
| There are two ways you can restart with the standard interface:
|
| - 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).
| zabzonk wrote:
| void main() does not exactly inspire confidence.
|
| https://stackoverflow.com/questions/636829/difference-betwee...
| mazieres wrote:
| Umm... if you download the code you will see that main returns
| int, but the main1...main6 functions invoked by main return
| void because they don't need to return a value.
| zabzonk wrote:
| I copy and pasted `void main()` from the article. If you read
| the SO question I linked, you would see that `void` is not a
| valid return type for `main`.
| mazieres wrote:
| Oh, that must be a typo in the document, sorry. I just
| fixed it. The actual corodemo.cc file was correct, though:
| http://www.scs.stanford.edu/~dm/blog/corodemo.cc
|
| Also, here's a more authoritative source than stack
| overflow for the return type of main: https://timsong-
| cpp.github.io/cppwp/n4861/basic.start.main#2
| superkuh wrote:
| 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?
| klyrs wrote:
| 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.
| nickysielicki wrote:
| Similarly, as a user, keep in mind that when you use a 4 year
| old distro, it becomes much, much harder to run any software
| that has adopted new C++ features.
| superkuh wrote:
| At least now. Back during the golden age of desktop from 2000
| to about 2010 things were stable. But then dev funding for
| linux and it's primary libs went back to be about creating
| the most bleeding edge, fastest, possible environment for
| running server farms for mega-corps and desktop stability was
| abandoned.
|
| 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.
| smitty1e wrote:
| > C++20 coroutines are implemented as a nice little nugget buried
| underneath heaps of garbage that you have to wade through to
| access the nice part. Frankly, I was disappointed by the design,
| because other recent language changes were more tastefully done,
| but alas not coroutines. Further obfuscating coroutines is the
| fact that the C++ standard library doesn't actually supply the
| heap of garbage you need to access coroutines, so you actually
| have to roll your own garbage and then wade through it.
|
| Well, that sounds like more fun than a tax increase.
___________________________________________________________________
(page generated 2021-02-22 23:02 UTC)