173 comments

[ 3.7 ms ] story [ 240 ms ] thread
This is pretty big news. That is the biggest c++2a feature I want to use.
Me too. It's a lot of fun coming.
I am working on a red-green threading on Linux, what interests you?
But no concepts, no const expr, or use of auto at least according to cppreference
It's no wonder. C++ is deeply and irreparably broken, yet instead of fixing anything they just pile up more features in an effort to compete with Rust and Go. Modern C++ is like a 19th century horse carriage with a big subwoofer (but you have to bring a bunch of 9V batteries), huge hot-rod wheels (but no amortization so don't even think about going above 20 kmh) and an airplane engine from Rolls-Royce (which is there for the sake of heating only).
> yet instead of fixing anything they just pile up more features in an effort to compete with Rust and Go

I used to be a C++ diehard, but modern C++ is why I started learning Rust. If I'm going to have to learn a whole bunch of new idioms and rules, I might as well do it with a language that has the new features and safety built-in, rather than bolted on.

> I used to be a C++ diehard, but modern C++ is why I started learning Rust. If I'm going to have to learn a whole bunch of new idioms and rules

You don't. Just because you bump a version number it doesn't mean you are forced to use all the bells and whistles.

That stops working once you're no longer a solo developer, but part of a team, then you need to learn everything any of your fellow developers might use. You might as well focus on learning a new language and having access to new jobs where you don't have to deal with legacy languages.
> That stops working once you're no longer a solo developer

It doesn't. If you have any experience working on C++ projects you will be very aware that deciding which feature is and is not used is the kind of stuff which is defined in the project's coding guidelines docs, along with naming conventions and choice of build system.

With C++ you only have to use a feature introduced after C++11 if you decide you really want to use it. Some projects even in this very day still don't use smart pointers or exceptionsor std::array. Some people still do the old C with classes thing.

Moreover, the world does not come to an end if you arrive at a project and need to learn a feature provided by a programming language.

> If you have any experience working on C++ projects you will be very aware that deciding which feature is and is not used is the kind of stuff which is defined in the project's coding guidelines

And you will find that you have to learn these features once they are allowed in the guidelines. Some workplaces stick to the old features. Some regularly update with pushes toward use of modern features. When your coding guidelines prefer modern C++, you have to learn it.

The sentiment of dreading having to learn new language features (as in being forced to) does not compute to me. Being a software developer, you should always be learning new things.
The complexity that emerges out of C++ with most/all of its features is immense to the point where it's clearly not worth the investment when there are so many alternatives out there. Nobody should be expected to deal with a language so complex that it takes a decade or more of effort before one can say with confidence he truly understands it.

Which is why C++ is dying and those other alternatives are finding so much appeal with new generations of programmers.

That's a very optimistic way of looking at things. More than a decade ago (pre-C++11) I used to be a pretty proficient C++ developer. I had a bunch of books about C++ designs, I would use Boost left and right, I knew my design patters, I knew advanced templating tricks etc...

Then I kind of grew frustrated by the complexity and super long build times and for a few years I left C++ on the side, going back to C and other languages. Now I'm a big fan of Rust.

These days when I see a modern C++ codebase using "auto" all over the place, lambdas, the new constructor/initializer syntax, std::move and more, it can be very tricky for me to understand what some code is doing. I feel like I'm reading a new C-based language where I can understand most of it but I feel like some key components are eluding me. Generally I end up figuring it out but I really don't feel comfortable modifying the code because I don't understand all the implications. Very few languages, especially as old and huge as C++, change so drastically and so fast.

And again, I considered myself an advanced C++ user not so long ago.

>With C++ you only have to use a feature introduced after C++11 if you decide you really want to use it.

If you're the original developer who makes the decisions. If you end up working on a project using a different subset than the one you're used to then you're screwed. That's what the parent is talking about.

> Now I'm a big fan of Rust.

> These days when I see a modern C++ codebase using "auto" all over the place, lambdas, the new constructor/initializer syntax, std::move and more, it can be very tricky for me to understand what some code is doing.

But what if you had spent 1/10th of the time you spent learning Rust looking at these features (frankly looking at the wikipedia pages for each revision is enough most of the time I think :

https://en.wikipedia.org/wiki/C%2B%2B11

https://en.wikipedia.org/wiki/C%2B%2B14

https://en.wikipedia.org/wiki/C%2B%2B17

https://en.wikipedia.org/wiki/C%2B%2B20

)

> But what if you had spent 1/10th of the time you spent learning Rust looking at these features

You can write object-oriented code in C too (e.g. gtk, glib etc). It just involves much more boilerplate, and it doesn't offer the same amount of built-in safety guarantees.

So I know enough Modern C++ to use it, but why would I when there are far better alternatives?

those alternatives don't always offer what C++ has - for instance template<int I> is damn useful in many cases and not many languages have that
Because those alternatives, while better at syntax + semantics part, are still quite immature at IDE + libraries + eco-system part.

For example, try to use Rust to implement a UWP component callable from .NET, interacting with the Visual Layer, while using mixed mode language debugging inside Visual Studio.

> That's a very optimistic way of looking at things.

It's the way a professional software developer who actually works with C++ projects looks at C++.

> More than a decade ago (pre-C++11) I used to be a pretty proficient C++ developer. (...) Then I kind of grew frustrated by the complexity and super long build times

Honestly, that comes out as utter bullshit. It sounds like you tried to find out which were the cliche criticisms and mindlessly repeated them, without noticing none of it makes any sense.

The main "complexity" that C++11 added was smart pointers and threading. It is simply unbelievable how someone who parrots they switched to Rust claims that stuff such as smart pointers and threading is too complex to learn. It's the sort of stuff that any developer quickly picks up with a cursory glance through the docs.

Your whole statement is even less believable if we were to believe your claim that you were somekind of Boost wizard who "considered myself an advanced C++ user", because Boost is the staging ground for whatever was added to the C++ standard.

Quite honestly, I find it hard to believe any of your claims.

True to some extent but education is good and knowing the language you are using can only be a good thing.

The same could be said of English. Would you switch to another language because someone uses a word you have never used before?

Let's not pretend there are jobs in Rust or whatever you think replaces C++.
On the flip side, I learned low-level programming in rust, and am thankful that $bigcorp uses modern c++ so the initial gap was smaller. Rust is still better though.
Think about it the opposite way: the modern language gives you a much safer and more expressive way to express your algorithms and still gives you access to the large library of existing code.
> Just because you bump a version number it doesn't mean you are forced to use all the bells and whistles.

But I want to use those features. I want memory safety and a way to say this variable owns the memory, vs that variable which is only borrowing it. I want move semantics in some cases and copy semantics in others. I want lambdas so I don't need to define new functions (or classes) to handle simple visitor patterns (or other cases where passing a 'function' makes sense).

These are good, useful features that I'd like to use to make my code more safe, more performant and more readable, and while C++ can do these things to some degree, there's boiler plate and new rules (rule of 5 vs rule of 3) and gotchas, compared to a language like Rust that has these things built in, with more safety and without compromising performance, and for me it made sense to start using it instead.

I still have projects in C++ that I'll maintain with modernish C++ rather than re-writing them, but for new things where I have a say in the matter, I'll choose Rust.

I see that you have not kept up. Since C++17 it's "rule of zero": you don't need to write any of them, because the compiler does it. As the language gets more capable, the code you write gets simpler.

I don't think that is happening in Rust.

> I see that you have not kept up.

I have not (C++14 onwards was roughly where I started paying less attention), and that was partly my point. C++ has gone through dramatic change, and if I'm going to go through that dramatic change, I might as well do it with a language that has modern features and safety built in, and doesn't require extra boilerplate to do it.

Looking up the rule of zero, it seems there's still some debate about whether it's a good thing or not [0] (don't know if it's been resolved yet), and it still doesn't preclude you from knowing about the rule of 5 and the side-effects of declaring or not declaring certain operators/constructors/destructors.

> As the language gets more capable, the code you write gets simpler.

> I don't think that is happening in Rust.

This happens quite often in Rust, see for example error handling and the evolution of the ? operator, and the recent addition of async/await, and unlike c++, as the language gets more capable the changes required in terms of coding style and updating your mental model of how things work is usually not so dramatic.

0: http://scottmeyers.blogspot.com/2014/03/a-concern-about-rule...

2014 was a long time ago.

Scott has retired. The better alternative to his suggestion was noted right there in the comments. Rules of 3 or 5 concern only designers of a library, and if you are designing a library, you may ignore them now.

Do not feed the trolls.

The net number of people adopting C++ in any unit time dwarfs by orders of magnitude the number adopting Rust; and the number already using C++ exceeds that of Rust by many more. That will remain true for a long time. As a consequence, any improvement to C++, either core language or library, has overwhelmingly larger lasting real-world benefit than any corresponding feature in Rust.

Rust pursues benefits by making a class of low-level errors hard to express, at some cost to expressiveness, and at quite substantial cost to adopters who must learn to work around the limitation (which they do). Against such cost it provides some convenient new control flow features, and leaves behind many C and C++ misfeatures that C++ cannot.

C++, instead, pursues benefits by making the language increasingly able to capture semantics in libraries, and thereby deliver thoroughly optimized and tested semantics to users without compromise. The result is that C++ users, by using such libraries, are able to code at a level high above that where the bugs prevented by Rust would manifest, and write less code that could harbor bugs of any kind, overall.

It is not clear whether adoption of high-level coding practices by coders using or switching to C++ will produce more benefit than those adopting Rust, and coding without access to such libraries. I have placed my own bet, and we will see.

But we can say with certainty that every coder who switches to modern, safe C++ from C or C-like C++, or to Rust, is a net win for humanity. And, if Rust in the end fails to grow out of its boutique niche (which is still absolutely possible), everyone later leaving Rust for C++ will bring to it high expectations that will incrementally raise the quality level of new C++ code.

Bad-mouthing of C++ by Rust fans may feel good, but it causes a net harm to humanity by the degree to which it reduces the number of coders who switch to modern C++ from C. The number of C coders who switch to Rust, instead, is far, far too small to compensate for such harm.

So the only defensible, responsible behavior is for C++ and Rust users to promote C++ and Rust (not necessarily in that order) and concentrate on getting coders off C.

You have some strange ideas about why people use C instead of C++ these days. People continue using C not because C++ lacks coroutines or something else (a strange argument for C which has almost no features to speak of), and not because they hate safety. No, people use C dor the sake of portability, for the sake of embedded systems, and for the sake of utter simplicity where that is required. Weaning them off C to C++ is impossible because C++ already doesn't fit their requirements. In fact, Rust is better suited to embedded devices than C++, so there's no real need to proselytize C++ to C programmers. Or to anyone, I would argue, because C++ is a terrible language nearly impossible to write safe software in, as well as an ergonomical, developmental and compilational pain in the ass.

In fact, I believe that in terms of developer time wasted as well user frustration, C++ is the most harmful language in the history of humanity. Someday I'll put together a page listing the dozens of C++'s design daults. Here's just a single article demonstrating how incompatible with simplicity and correctness C++ is: http://www.icu-project.org/docs/papers/cpp_report/the_anatom...

In my experience, the majority of C that is used is actually by people who think they're working in C++, because they were taught to use manual memory management, raw pointers and other such C-like constructs. Some keywords about classes were thrown in towards the end of "C++ 101" or whatever they learned from, and away they go, writing C with classes like the last 30 something years never happened.
That's still C++, just not modern C++.
Technically, in that the C++ spec allows it, yes, it's C++. However, I find such definitions to be of little practical use, although frequently in the workplace I sit and wait while someone satisfies their need to carefully divide the world up into precise, clear, exacting and useless definitions before we can get on with doing something useful - the industry just seems to attract that; often they don't care about trashing all those lines immediately afterwards, so long as everyone acknowledged the lines. That said, the discussion about how many layers there are in a model of networking and what they're named rendered some people unable to do anything for the rest of the day.

Colloquially and practically, I might suggest that it demonstrates that the programmer may be dangerously thinking in C with little lumps of data as their classes. The programmer is thinking and coding in C, with some classes, that they then feed to a C++ compiler.

People were doing it like that back in the nineties, when what we think of as "modern C++" didn't exist.

AFAIK this very problem is addressed with introduction of move semantic and the goodness, which comes with it, such as unique_ptr instead of raw pointers. With that default move assignment operator does the right thing by default. So C++ langauge development actually improves simplicity and correcness as least for this particular example.
"Here's just a single article demonstrating how incompatible with simplicity and correctness C++ is"

I don't see where is the problem. Perhaps, you could give an example of the equivalent code in your favorite language?

The article is from 1997. Every C++ developer will agree that the area surrounding owning raw pointers is chock-full of footguns, but that area is avoided in modern C++, both in theory and in practice.
(comment deleted)
It is a fact that people still do graduate from C to C++, for a wide variety of reasons. Some discover that their embedded system can be coded with C++ as easily as C. Some learn that C++ is faster than C. Some just gain more confidence and adopt a new feature that is only in C++.

The large number of remaining C coders, and the ease of switching incrementallty, means that the number switching to C++ is always overwhelmingly larger than the number adopting Rust from all starting points.

We may lament that they do not start writing modern, high-level C++ immediately, but the number of CppCon (and similar) talks hosted on Youtube, and rocketing attendance at, and number of, such events, shows that there is great interest in learning good modern C++.

> Someday I'll put together a page listing the dozens of C++'s design daults.

It's pretty old and somewhat outdated by now, but you might enjoy the C++ FQA (Frequently Questioned Answers):

https://yosefk.com/c++fqa/

Almost no one is using Rust in embedded. C++ has a much higher uptake among that crowd.
I'm not sure modern C++ has proven to be safer than C (old C++ most certainly has not). Also, I'm not as sure as you that there is an overwhelming number of people adopting modern C++, given the slow speed at which I've seen at least my company being able to move to C++11 and then C++14.

The reality is that C++ is still an extremely unsafe language, where any slight deviation from strict discipline (discipline that itself has only really been ergonomical since C++14 or so) will lead to memory corruption errors. Even with modern C++ features you can get memory corruption: null references, references to moved objects, no way to signal errors from destructors, and there is still no good way to check whether an operation on ints will overflow. Moreover, the insistence on optimizing around undefined behavior, specific to C and C++ I believe, coupled with large reliance on undefined behavior in the standard, leaves C++ in a very poor position as far as guarantees of safety are concerned.

In general, the responsible thing if we want a more secure world would be to discourage the use of manual memory management as much as possible, whether it be in C, C++ or Fortran. That does include trying to get people to use smart pointers if they absolutely have to use C++, but it should also include getting people to use other languages if at all possible (Java, C# or Go would be the more mature ones for large business).

You're saying that switching to "safe" modern C++ is just something you could do at a whim. Learning how to write safe C++ is a hell of a task to undergo, and still you are at the mercy of your program crashing even though you have taken careful approach to memory management etc.

Modern C++ is just not a safe language to program with. It is very easy to produce errors that result in segmentation faults, and it takes great care to produce code that works properly even with shared_ptrs and unique_ptrs and so on.

But you are correct that adoption of C++ is higher than Rust, and will continue to be so until some mission critical framework or for example game engine starts to support Rust in as a first citizen language.

Until then, we have modern C++, with all it's quirks and difficulties and lost developer time in figuring out where the hell was that dangling pointer or why the heck was an object not freed correctly, or why did the array access or vector access go off by some value.

Not saying C++ isn't good, it is, but a lot of time is wasted figuring with C++ currently when a lot safer alternative could be used. Unfortunately that is not very realistic in many scenarios yet, although I carefully evalulated Rust also for my current project, the requirement of supporting a C++ game engine just outweighs easily the hassle of having to figure out bridges and bindings and such.

Writing safe modern C++ is much easier, and much less work than the old ways. So it is easy to switch, and easy to tell if you are Doing It Right: if it's not clean, safe, and easy, there is a better way.
I am completely the other way. Coming from Java, I am finding modern C++ pretty amazing tool for design. There are some amazing compile-time API's I can design which is impossible in other languages. Maybe the shiny will wear off after some time but so far I am loving it. I am truly looking forward to C++ 20.
C++ is pretty great in single player mode, you have so much power at your fingertips. It's the epitome of "and the kitchen sink too".

It's when you switch to online multiplayer and end up having to maintain and modify code written by other people who have different opinions on how kitchen sinks should be installed and used that things tend to sour a bit.

This is true for all languages and projects you inherit though. Eg I am trying to use Magento in PHP land and it is truly grotesque.
Magento was grotesque from day one.
(comment deleted)
> in an effort to compete with Rust and Go

I think that you overestimate how much Go and Rust are used in the wild, and how much of a threat they are to the C++ ecosystem. IMHO C++ is mainly competing with its own past.

I genuinely believe statements like GP’s are a result of living in a cutting-edge tech bubble, not necessarily SV, but online hype and discussion based reality. Sure several large impactful projects use Go and a few companies are dabbling in using Rust. However, the number of these usages is dwarfed into near statistical irrelevance by the amount of C++ code in active production and usage (it is even more irrelevant if you count programs written in C).

That is not to say there is nothing good or beneficial about these languages, devs using them are free to evaluate the features provided and find that they suit their use case and go get to work. But some dev’s pet language, no matter how loudly they scream on Reddit and HN, is not yet threatening C++.

I can't imagine a real world robotic assembly line in 2020 not using c/c++ indefinitely. Doesn't matter how cool the new language is, what people really want is to not have to rewrite that dependency they're still using from 1998.
You can't fix C++ because the only thing there is to fix is to get rid of all the legacy baggage but doing that would break backwards compatibility (including probably compatibility with C) which means that it's not C++ anymore.

"Fixed" C++, per your description, already exists. It's called D. It's called Rust. It's called C#. It's called Go.

Meanwhile there appear to be many people who (I must admit much to my puzzlement) seem to still enjoy writing C++ and want to add even more features and paradigms to it. I wish them good luck and I'll be sure to look at the results from afar and see if I can recognize some bits of the C++ I used to write 15 years ago. Oh! I think I saw an "if"!

"It's called D. It's called Rust. It's called C#. It's called Go"

All of these languages with the exception of D are completely different. It's like saying you fixed a cupboard by replacing it with a sofa.

D's the most similar, but it's sadly not exactly a hot language or one where there's jobs available.

Was the cupboard being used as a sofa?
* rid of all the legacy baggage * It keeps being repeated over and over but where can I find such a list? There's almost no misfeature in c++. It's issues are just the fact it's too low level / manual.
How is it too manual compared to eg C# where I need to implement IDisposable and wrap use of variables in "using" to really ensure something is cleaned up when I want it cleaned up (without risky memory ballooning relying on the GC)? C++ destructors to clean up your resources are really neat. And deterministic - you know when stuff is going to get cleaned up.

Or was it some other "manual" feature that's too much of a burden? Honestly curious.

I don't have a list, but initialisation is quite a mess and the syntax inconsistent. That would be one of the first thing I would fix in C++.
Here's a non inclusive list

1. Initialization

    Type foo = Type(FieldType())
    Type foo = Type{FieldType()}
    Type foo = Type { .field = FieldType() } 
2. std::unique_ptr can be null

3. std::shared_ptr does not guarantee atomic reference counting

4. std::variant and std::optional require exception handling.

5. std::map should be the std::unordered_map data structure, while std::ordered_map should be the special case. The unordered_map should also stop being a terrible hashmap implementation on most platforms.

6. std::vector<bool> is not what you think it is under the hood

7. Explicit should mean the opposite when it come to single argument constructors (meaning implicit casting needs to be explicitly enabled in the constructor definition)

8. Although on that point, one could argue that implicit casting should be a different operator overload than constructor definition.

9. There needs to be a mechanism to check the validity of a template argument before the template is expanded (concepts will fix this, iirc)

10. https://codegolf.stackexchange.com/questions/1956/generate-t...

11. It should be trivial to implement an std compliant iterator. It is not: https://stackoverflow.com/questions/8054273/how-to-implement...

> std::unique_ptr can be null

what would be the difference between a never-null unique_ptr and a reference ?

> std::shared_ptr does not guarantee atomic reference counting

is that a problem in practice ? all implementations do use atomic reference counting. Just because it's not written in the standard does not mean that common sense does not apply.

> std::variant and std::optional require exception handling

exception is the standard way to report errors in C++

> std::map should be the std::unordered_map data structure, while std::ordered_map should be the special case. The unordered_map should also stop being a terrible hashmap implementation on most platforms.

Anyone who has a problem that calls for caring between unordered_map and map will know the difference, and for everyone else, does it matter ?

My opinion on that is that standard library types do not really matter - I use 3 different non-standard map implementations for specific use cases in one of my apps and there is zero friction as maps are almost always an implementation detail, it is nigh uncommon to e.g. have an API that takes / returns maps.

> std::vector<bool> is not what you think it is under the hood

that depends on who you ask :p from past teaching experience, if you ask students to tell you what is vector<bool> they will tell you that it is packed because it feels like the logical thing to do.

> https://codegolf.stackexchange.com/questions/1956/generate-t....

turing-complete language can generate arbitrary things and is not immune to halting problem, news at 11 :p

>what would be the difference between a never-null unique_ptr and a reference ?

Ownership.

> all implementations do use atomic reference counting

They do not.

> exception is the standard way to report errors in C++

Not really, at least in practice. Many C++ developers do not use exceptions at all, with good reason.

>turing-complete language can generate arbitrary things and is not immune to halting problem, news at 11 :p

The point is that a relatively simple syntax error on behalf of the programmer generates an incomprehensible error message. This is just a comical example of that, and a lot of real world code using templates is full of opaque errors at the start. That said, concepts should help eventually.

> Meanwhile there appear to be many people who (I must admit much to my puzzlement) seem to still enjoy writing C++ and want to add even more features and paradigms to it.

I enjoy C++. My general approach to programming is that you should make a domain-specific language to express your problem, and then solve it in that DSL. C++ is a good tool for that when you also need deterministic performance guarantees. So any "new feature" that allows me to write more expressive DSLs is welcome.

In what way is C++ "deeply" broken? I seem to be using it daily with no problem, as does the rest of the world. I manage to use applications and operating systems where vast portions of it are written in C++ without problems - what problems are you having? Honestly, it's dead easy to criticise but you must be using the product of another language if you feel so strongly about this...?

Tell me, which browser and operating system are you using to write this comment? It must be one written in something other than C++, right? Your browser isn't using WebKit or Blink by any chance is it??

Let's hope the web server or network appliances used to transfer the data to you are not using software written using this "deeply broken" language!

I mostly agree with you, but operating systems and web servers are generally written in C and not C++. For example Linux, (the vast majority of) GNU, Apache, and Nginx are all C.

(Though I expect your parent has similarly negative views on C?)

You are correct, thanks. My comment comes across as very irritated, but it's the same argument that will continue to the end of time I guess - "this tried, tested and widely used language is broken because it is not language $x and because it lacks this edge-case invented usage scenario even though it works adequately for 99.9% of all other scenarios as demonstrated by decades of use". It's wearying. Sure, C++ is complicated but so is English!

I thought nginx was C++?

I also thought large portions of Windows are C++? Everybody loves COM!

Nginx is C, and the authors really don't like C++.

(I used to work on ngx_pagespeed, which was a C++ server module that plugged into Nginx to rewrite pages so they would load faster.)

Thanks for the info!

Did they approve of your module?

They liked that we were building something cool for their server, but our approach was so different from theirs (in ways that went way beyond the language) that our module wasn't ever going to become officially part of Nginx or anything.
To balance with what you say:

Windows is mainly C for the kernel, and C++ for the rest https://en.wikipedia.org/wiki/Windows_NT#Programming_languag.... That's around 90% of the desktop/laptop market.

IIS, Microsoft's web server, is C++ https://en.wikipedia.org/wiki/Internet_Information_Services. I think it is still the 3rd most popular web server in the worldwide.

A significant portion of the C++ code in Windows uses very few C++ features or idioms and is basically C with classes or one or two useful features.
Which is still C++.
Absolutely, but that kind of code doesn’t really speak to complaints about modern C++ and all it’s become. I just wanted to chime in with that extra information. I don’t really have a dog in the fight, other than having a lot of love for C and good C code.
It is, but I do wonder if a large part of the problem with regard to using C++ features on Windows is the poor support for it in the ancient COFF linker and binary format they support. Lots of stuff like template export is possible with ELF which is impossible or dangerous on Windows.
I can also attest that lots of “modern C++” code exists in Windows.

There is code in Windows that was written yesterday and code in Windows that was written over three decades ago.

COM/UWP is the proper way to use C++ features on Windows in a inter-language way.

While not ideal exporting templates has been possible since Windows 3.1 days, as long as one sticks to a specific compiler.

That's impossible. According to half of the comments in every c++ thread on HN, any usage of C++ will immediately converge to using all features!!

/s obviously.

No my terrible grasp of English even as a native speaker is the cause of this confusion. What I meant was: “No I meant that from cppreference using coroutines means you can’t use the features I mentioned not that those features don’t exist in c++20“
There's no reason that any of those couldn't be written in c++ by good coders. It's just a historical thing.
My parent was arguing "C++ can't be as bad as you say, look at all these things written that use it". Things written in C are not evidence for this, anymore than things written in assembly.
I also use C++ everyday, and find it quite powerful, but I also feel that it is ill designed. The other day I had to work with code that involved a public member function that returns a private type. You can only bind to the return value of this function with auto (because the type is private), but can otherwise do anything with the object. Why this is allowed is beyond me.

You're right that much of the software we use is written in C++, for good reasons, but asking people to "use a product of another language" just because they are critical (in a not so nice way, admitted) of it is going a bit too far. It's like asking someone to move to a different country because they think their system is "broken beyond repair".

Your design above with the private type being returned could possibly be useful. Perhaps outsiders to the class will be prevented from instantiating the nested-type without using the class itself to generate the nested type. This could be contextually useful and certain (interface) functionality could be grouped in a manner that is more convenient to use than by calling methods on the original class.

I like that C++ allows us to express this situation, even if it's a bit of an unusual design pattern.

You can prevent instantiation of the nested type already by using private ctors and friending the wrapper class from this type, while keeping the class itself public. That is a much cleaner expression of intent. I believe that if a class is usable via its interface, it is public any way (in that you will need to go and change all uses of this object should you decide to change the nested type's API, whether it is declared private or not).

And also note that binding to the object at all has been possible only after C++11, while this "feature" works with earlier standards. That rules out the possibility of this being designed into the language with the express goal of achieving what you say (it doesn't matter, but just pointing out).

Visibiliy has always been sabout naming thing. You were always able to bind private types to template arguments (otherwise you would never been able to create an std::vector<PrivateType> or pass MyPrivateContainer to an std algorithm. What's new in C++11 is the ability to also bind locals, but that no more powerful than what you could do before (via a CPS transform).
I agree this is weird, but isn't it pretty similar to the old c pattern of returning an opaque handle? I'm assuming you can't do anything with the object, since you don't know its layout and its member functions would be inaccessible. it sounds like you just get to hold onto it and pass it to any accessible functions that take it as an argument. am I misunderstanding how this works? I can't find any resources that directly explain this situation.
You can do member access into the object! That breaks encapsulation or whatever was the reason to keep the type private. Passing it around usefully isn't possible except to friend or member functions of the same class (since you can't write a function taking a type T that is private to your scope). In fact the only reason I could think of for doing something like this is to prevent people from including this type in their APIs, while still allowing to work with the object. But there are better ways to do that, including just exposing what the little object would expose via direct method calls on the enclosing class.

This isn't similar to C handles. There is usually a public API around the kind of the handle (e.g., the file oriented syscalls that works with file descriptor integers). In such cases, C handles are more like "this" pointers with an associated API.

Also, it cannot be the intent of allowing this pattern to implement opaque handles, as you can bind to them only after C++11 (auto), while such malformed code still compiles (including member access into the returned temporary) with -std=c++98.

ah okay, looks like I was mistaken. it's pretty hard to see how this could possibly be useful.
With bricks you can build a useless house with no doors, but bricks are pretty great.
I use this case quite commonly for implementation encapsulation. You could think of it as a capability.

The type of a lambda is inexpressible so they can only be handled with auto or decltype. I think that’s reasonable.

I am curious how you achieve encapsulation this way, please elaborate (I tried to find uses for this pattern. Even the author of the code I'm talking about, and other much better C++ programmers than myself seemed to call this an oversight rather than intent).
Here's a simple example: I have a table of stageful actions (perhaps part of a UI) that is dynamically constructed and maintained. I could register token which looks up the necessary information in an internal hash table. Of course said internal table would have to stay in sync with the UI's table. Instead I can just return an opaque object (perhaps a virtual object if life is fancy) with all the necessary state; if the UI window is deleted then all the objects will be deleted too at just the right time, with no explicit coordination with the part of code that created them (their destructors could do some bookkeeping if appropriate).

Traditional callbacks can also be used in this case, but what is a callback lambda but private state? And a stateful object rather than a lambda function is easier to manipulate, say by inspecting in the debugger or perhaps by providing a generic function for things like printing the entry.

Another case is where you have some hairball third party or legacy library that you want to be able to use in your modern code; you can often simply make it an opaque private object rather than making a complex visitor for it.

I kind of understand what you describe, but I still don't understand why the type needs to be private. You can return opaque objects of a public type just as well for this job, can't you?

    class Token {
      virtual ~Token();
      protected:
        friend class MyClass;
        ...
        /* all your state */
    };
> what is a callback lambda but private state?

You can (and do) program to the callback's interface -- say you ask for an std::function<void()>, and the user gives you that. What is behind the function (private state or not) is not your concern, you simply perform an public operation (calling it) on a public interface (i.e., via std::function).

What is the difference to you if I pass a private object or a public object all of whose instance variables and methods are private? Either way the point is to say “this is not your object to manipulate”. But making it a type you cannot even express makes that far more explicit.
I see, I guess then that's where we disagree. It is very confusing for people from my limited experience, for little gain. Plus you make it harder to do things like put these tokens in e.g., a vector without resorting to decltype.

Going back to the original point i was making with this example, I still think this was probably not a capability consciously designed in the language.

it's obviously possible to make useful things with c++. I too do this every (week)day.

imo, the biggest issue with c++ is the lack of orthogonality. there is usually a "best" way to do something, but you have to read a lot to choose between the many other ways of doing it. the usual "best" way may not be applicable in your case because it is subtly incompatible with some older feature that's already being used.

as an aside, I always get a kick out of standard library functions like std::addressof. you have to use stuff like this in highly generic templated code because you can't be sure that some clown hasn't overridden the `&` operator for some type that will eventually be passed to your function.

> an airplane engine from Rolls-Royce

RR makes excellent aircraft engines, for both Boeing and Airbus https://en.wikipedia.org/wiki/Rolls-Royce_Trent

In addition, Rolls-Royce cars are made by BMW now, who also make quite good engines.
Correct. RR doesn't make cars anymore, it's only just a badge. Also, RR is dominating the marine propulsion sector, these engines are some of the largest in the world!
This I did not know, interesting!
"yet instead of fixing anything they just pile up more features in an effort to compete with Rust and Go"

Alternately they're iteratively evolving the language as each generation of features are fleshed out? They've been doing this for several decades now. It's their job. And for those people using C++ tooling, or working on C++ projects: Cool, new stuff to ease some edge conditions.

As much as I like Rust, progress usually comes in progressive changes, not with big bangs.

Modern C++ is already a large improvement over C, which was already a 19th century carriage, when everyone was moving into steam cars.

Unfortunately, modern C++ still includes (the vast majority of) C, both in theory and in practice in many code bases around the world.
Indeed, and it is its Achilles tendon, once its Trojan horse for adoption, nowadays the source of security headaches.

However, I don't see many companies throwing away their IDEs, game engines, build systems just like that.

It is easier to migrate those teams to static analysis tooling, than throwing it all away and start from scratch in another ecosystem.

Hence why both Microsoft and Google are still two major contributors to ISO C++, in spite of being in the process of adopting Rust.

Can you point out where Google and Microsoft are moving to Rust because I've seen none of that.
“Moving to rust” is too strong; they are both adopting Rust for real, production use cases. I’ll give you one example of each: Google is using Rust in ChromeOS, at the virtualization later, and Microsoft is using ripgrep to power the search for VS: Code. There’s more than just that but those are the two easiest things to point to.
The concepts part I'm not sure about, but the other parts don't look so bad. According to cppreference.com[0]:

> Coroutines cannot use variadic arguments, plain return statements, or placeholder return types (auto or Concept).

> variadic arguments

For the purposes of creating variadic functions, variadic templates are the preferred solution, since variadic arguments have a number of drawbacks (e.g. unavoidable type promotions).

> plain return statements

This is the definition of C++ coroutines, more or less.

> placeholder return types (auto or Concept)

Note that this isn't the typical use of auto (i.e. inferring the type from the right hand side of an assignment), but about return type deduction. From a quick read, it looks like there are multiple, equally valid kind of coroutines (e.g. lazy<int> vs generator<int>) which cannot be deduced from the body of the coroutine. It could be argued that lazy<auto> should work, but I would guess it clashes with all the already existing complexity of template deduction. I agree that this looks like a wart but not a horrible one.

I'm not sure about the concept part, would have to learn more about concepts to comment.

> Constexpr functions, constructors, destructors, and the main function cannot be coroutines.

> Constexpr functions

Constexpr functions are functions whose value can be decided at compile time. Coroutines can call constexpr functions, they just can't be one. I guess it could be useful to determine the full output sequence of a coroutine at compile time. Maybe it could be added in a future language version, if it's worth the added complexity.

> constructors, destructors, and the main function cannot be coroutines

I guess this is just formalism, having those things as coroutines doesn't make logical sense.

0: https://en.cppreference.com/w/cpp/language/coroutines

Thanks for the itemized reply. Your points make sense
C++ is getting more fun to use, every cycle.

C++11 was a huge step up with lambdas, move semantics, atomics, threads, constexpr functions, loops over containers, and loads more. C++14 added generic lambdas and lifted lots of arbitrary restrictions, making everything smoother. C++17 added destructuring, template argument deduction from constructor arguments, and constexpr if. C++20 brings concepts, ranges, modules, coroutines, and automatic comparison opetators.

Each time, more kinds of programming are made natural and easy to write, and more old workarounds become unnecessary. We hardly ever need to write out constructors, copy assignments, or destructors anymore; or even, with '20, comparison operators. Template metaprogramming is mostly unneeded now.

There have been library implementations of this for years. And they are a far cry from true lwt aka golang.
Goroutines are not copyable so they're not really a substitute for byuu/co
I think my mind is about to be blown. What does copyable mean when it comes to coroutines?
A description of one thing you can do with this is at https://github.com/moonjit/moonjit/issues/84

It's not a common feature. Usually it's present in a language that has "continuations" and the api is something dreadful like call/cc. But you can use it in Lua with the above mentioned patch, and you can use it in byuu/co with memcpy.

I object to the use of "dreadful" for call/cc, it's just a different paradigm that requires a different way of thinking but it's not a bad thing per-se. It's not like goto or setjmp which are fundamentally unsound constructs that completely break the flow of the program.
When designing these primitives, I think there are a few considerations: When creating a new execution context, which one should begin executing, and which one should be bound to a local variable in the executing one? Should there be any primitive that switches to an already-existing execution context without creating a copy of it? How many primitives should there be?

call/cc answers: "The new execution context should begin executing and the old one should be bound to a local variable, no, one"

As a person who occasionally explains these things to people who have never been exposed to them, I strongly prefer: "The old execution context should continue executing and the new one should be bound to a local variable, yes, as many as we need to produce the preceding answers"

I think without trying too hard you can get the number of primitives in the latter case down to three, "create", "copy", and "switch." If you wanted to bug your users you could get it down to two.

Call/cc is not very composeable.
Well, call/cc usually makes it impossible to add a construct like 'finally' to a language, so it does at least block some options.
Depending on what you want from this, you can maybe use dynamic-wind. But yeah.
Lol continuations will blow your mind. "Returning" to somewhere else than you came? Humbug!
Unless I'm mistaken you can already do that with go's channels
Hint: I don't mean returning data ;-)
And you don't need to return data with channels, even though you typically do. They are primarily a tool for controlling flow with concurrent goroutines.

Do you have an example of what you have in mind so we can see more clearly what it is about?

byuu's comment in this comment tree gives a good example.
Serialization [1].

Say you want to take a snapshot of where your program is, run the code some more, and then later on restore execution back to that snapshot.

In the parlance of video games and emulators, this would mean save states. You could use these save states to implement speculative execution to eg remove input latency from games using a method called run-ahead [2].

With state machines, this is trivial: just record all the state machine variables. With coroutines / cooperative threading, it's not: the state is all stored on the stack frame.

My stackful coroutine library can do this by essentially memcpy'ing the stack frames themselves, but it comes with some seriously negative caveats: the biggest is you can't save this to disk, because on the next program run, ASLR, etc would invalidate the pointers in the stack frame. In fact, you can't even safely release and allocate new memory between a serialize and unserialize point, or the stack frames may have dangling pointers in them. In practice, this is acceptable and workable for features like real-time rewind and run-ahead.

Library support for C++ coroutine serialization would be trivial since they are stackless. Getting around the limitations I mentioned for stackful coroutines would be immensely more challenging.

[1] https://byuu.net/design/cooperative-serialization

[2] https://byuu.net/input/run-ahead

> Getting around the limitations I mentioned for stackful coroutines would be immensely more challenging.

Challenging but it's definitely been done before. The system below [1] (disclosure: I worked on it years ago), stores every memory write in a separate buffer on the assembly level. You need to modify the compiler for this, and it cuts performance roughly in half, but it allows you to step backwards in code and perform sophisticated performance analysis.

[1] https://www.ghs.com/products/timemachine.html

Oh certainly, it's just beyond what I can do as a C library. I've also seen another approach that implemented a customized libc replacement to allow this to work. But that and modifying the compiler itself are really extreme steps.

It's still doable, and it's definitely worth doing, but I understand the uses I've mentioned are rather niche, so it's unlikely to occur. For one, we still don't have built-in stackful coroutine support even in C++20.

> modifying the compiler itself are really extreme steps

Yes, it's not for the average developer. Yet it should be incumbent on the tools builders. I'm actually surprised that most compilers/interpreters don't have a "step backwards" flag that you can turn on during compile/run-time. It would be a helpful feature to expert and novice developers alike, but hasn't really caught on outside of niche areas.

Can't you just disable ASLR?
In c++20 lambdas are now copyable and there’s no reason the routines calling co_awai, co_yield, and co_return can’t be lambdas perhaps called by lookup.
Is that so? Please elaborate
https://en.m.wikipedia.org/wiki/GNU_Portable_Threads

N:M threading from 1999!

And there are many more recent examples too.

It's not N:M with arbitrary M, but N:1 (mentioned in the link you posted).

There is no parallelism whatsoever in GNU Pth.

You only get a single kernel thread and "green" userland threads cooperatively scheduled on top of that single kernel thread.

That's it.

It looks like Facebook's `folly` library had an implementation of coroutines available in their experimental directory since mid-2018. There was still some evolution going on after it was pushed though (and it seems like there still is today).

Not sure if that helps the argument that this has been around for "years" (since it's still in the experimental directory), but I guess there were some libraries trying to accomplish this.

https://github.com/facebook/folly/tree/master/folly/experime...

The only implementation that I would consider around of years, were the C++/CX extensions, and even those aren't 100% like ISO C++ ones ended up looking like, although Microsoft was a big contributor to its design.
Go routines are a tiny subset of what coroutines allow in other languages, because Go doesn't have the concept of generators/continuations nor exposes the low level runtime management APIs.
Technically goroutines are equivalent to one shot continuations, while generators, lacking the ability to capture a call stack are not (they capture the return continuation, but not any arbitrary one).

So no, go coroutines (or any stack full coroutines) are actually more powerful than thise exposed in other languages.

In other news, Iain doesn't have zstd yet :-).
Don't let its name fool you. C++ coroutines is nothing about the coroutines you imagine from other languages.

C++ coroutines, defined by C++20 standard is nothing but a syntax sugar. It consists of three keywards, co_await, co_yeild and co_return. The functions which use these keywords are said to be a coroutine and it's code will be transformed such that some specific member functions of the user-defined type will be called.

And that's everything. It's just a syntax sugar like range-based for. Actually, it's less than the range-based for because there is no standard library support for it.

If you wrote a class that does, say, lightweight user-mode threading according to the C++ coroutine's syntax sugar expect, it become coroutine in the sense of most other programming languages. It can also support generators and other idioms if YOU or SOMEBODY wrote a library for it. Good luck with that. Because even the C++ SC's experts couldn't design and agree upon a good such standard library until the deadline of C++20.

In my opinion, these features should better be implemented by powerful generic static reflections rather than a dedicated special syntax just for this.

> In my opinion, these features should better be implemented by powerful generic static reflections rather than a dedicated special syntax just for this.

Boost has had a coroutines library for some years now. What do you make of their approach?

C++ coroutines are pretty much the same as stackless generators/async from other languages (python, JS, C#). They have nothing to do with range for.

They are not stackful coroutines, like thise from lua for example, but multiple implementations of thise are available as a library. There still a chance they might make it to the standard library.

It's not just syntax sugar, since the compiler still does the hard work of capturing local variables in to a (stackless) activation record
At the end of the day, everything is syntactic sugar over machine code. Doesn't means it is a bad thing though.
Ah, I loved coroutines. Combining this with Boost.ASIO and Boost.Beast, you can make a high-performance HTTP server/client in C++ quickly while defeating the competitors several margins far away.

Sadly, coroutine TS in Boost.ASIO is still a preview feature, and I've met a lot of quirks that stopped me forwarding from using it, for example, sockets have to be moved even inside a lambda, that can clearly optimized out by referencing, and I had some frustration of this.

Do you have any benchmark to back this assertion by any chance ?

The only benchmark I know of is https://www.techempower.com/benchmarks/ which is really poorly done but it's better than nothing. Rust seems to sometimes come first and C++ framework are close (and not by several margins).

The benchmarks leader "actix-web" was not really regular/typical rust, and conflict with the "community" caused it to implode. see https://github.com/actix/actix-web/ and https://news.ycombinator.com/item?id=22075076
Still, the fastest C++ framework there is slower than than both the fastest Go-based fwk and the fastest Java-based one.
Yes this is why I said the benchmark is poorly written. It encourages frameworks to do such thing.

Even if you look at the code of hyper for the benchmark, it does some ugly things not recommended in production. I'm not blaming actix/hyper's authors, I'm blaming the benchmark author encouraging (not voluntarily) such practice.

However the benchmark is enough to see that C++ is not faster by several margins, hence my curiosity about benchmarks proving otherwise.

With a coroutine you may not necessarily have to do as many gymnastics as with callbacks. You can turn a push operation ("call me back using this function with a newly connected socket"), in to a pull operation ("get me a newly connected socket")

For example, here's an awaitable that just wraps ASIO callback for accepting new connections:

    auto async_accept (tcp::acceptor& acceptor, tcp::socket& socket) {
        struct Awaitable {
            tcp::acceptor& acceptor;
            tcp::socket& socket;
            boost::system::error_code ec;

            bool await_ready() {
                return false;
            }

            void await_suspend (std::experimental::coroutine_handle<> coro) {
                return acceptor.async_accept (
                    socket, [this, coro](boost::system::error_code ec) mutable {
                        this->ec = ec;
                        coro.resume();
                    }
                );
            }

            auto await_resume() {
                return ec;
            }
        };
        return Awaitable{acceptor, socket};
    };
Honestly even with RAII memory management in C++ can be hard, especially with all lambda/closures. I can totally see this becoming a even bigger mess if combined with corotines.

Most languages which have anything like corotines/async/await avoid this problem by using a GC. Rust kinda is will equipted for this with it's borrow checker (but it's hard for the compiler team to get completely right). But I don't see C++ to be suitable for many coming/common programming patterns with them. It is just way to easy to mess up memory management in the edge cases (like e.g. early cancelation etc.)

A typical solution is to use shared pointers, and just try to take care to avoid ownership cycles. It's certainly common when using ASIO callbacks, which are a bit like coroutines, especially if they're methods of a class that represents the overall "function". I don't know much about C++2a coroutines but maybe a similar strategy could be used for them too.

In a sense, reference counting isn't far off being a basic form of GC. In fact CPython uses reference counting and only uses anything else for detecting cycles.

Reference counting is a form of garbage collection. I don’t like the trade offs for many applications (problems with cycles and runtime overhead) but when you can’t use a transporting collector it’s often better than mark/sweep
Asio enforces exactly once semantics for callbacks, so if you are careful you can thread your state through your callbacks [1] in a linear way and avoid reference counting. It is not always worth the effort though, but coroutines should make it trivial.

[1] last time I checked ASIO didn't support move only callbacks so it was ackward to implement. Things might have changed since.

I rather like c++ closures in that they are pretty explicit about how capture works. Given the other concepts in the language it is absolutely necessary to consider how closure capture works with the copy constructor, for example. I would contrast it with objc blocks, where capture is pretty implicit and sometimes feel ambiguous.

But I think that copy constructor is the root of a lot of evil performance penalty in c++. In the old days it was especially easy to over-allocate with vector or string. Move semantics fixed some of this. But passing around closures without a lot of care is also a consequence. An "easy" solution, to capture a shared_ptr, is not without its costs.

Yes, I often delete the copy constructor so that accidental copy cases become compile-time errors, though tttt that doesn’t seem to come up that often.

I was bitten by a use-after-move last week but it was my own fault in some startup-time-order-of-initialization undefined behavior that the code was depending on that only worked on one compiler. It’s pretty uncommon.

Shared_ptr, the solution and cause of most performance problems :)
As a counterpoint, I've been ubiquitously using coroutines in C++ as the primary concurrent task scheduling mechanism in database engines for around a decade. It has always been dead simple and mostly straightforward, resource leaks were never an issue, though details vary with coroutine implementation.

Since C++11, memory management has been remarkably easy to get right the first time in complex systems. It isn't just RAII, there are many other mechanics that enable resources to be automagically managed optimally from the perspective of the developer. In my experience across millions of lines of code, resources don't leak ever at this level of abstraction (pre-C++11 is a different story).

I am perennially baffled by what other people are doing with modern C++ that resource leaks are chronic problem. I get the impression there are a large number of C++ programmers that don't grok basic resource management in C++11 (it really isn't that hard) and blame the language when resource management issues occur.

Well, just write a simple C++11 lambda that uses a reference capture, and store that lambda somewhere and watch your app crash when it that lambda runs after the referenced object is gone. Even if you stick with writing no "new" or "delete" in your code, just as good modern C++ encourages, this simple problem will always be there, and it doesn't just affect lambdas, though that's the sneakiest place it often happens.

Of course you can avoid these problems very easily, but you have to be bitten by them first to know how easy they are to write.

(comment deleted)
Don't capture by reference, as you're misusing the language.
Excuse this ignorance from C# land, but how does one get by in C++ without ever writing "new"?
Use the Standard Library, which safely encapsulates new and delete.
Smart pointers and so on? As I said in another sibling reply, I have experience in almost everything from C# down to C, functional, etc., with the (almost) sole exception of C++, so I'm always curious in understanding that language's idioms.

The idea of needing to employ the standard library to avoid using a core feature of the language seems... interesting? I guess.

You couldn't implement the standard library or libraries providing tools like the standard library without having "new" etc available in the language. And of course C++ hasn't had all of this from the start, some use cases still like or need to avoid using too much of the standard library, ...
Most such cases are delusory.

That said, anyone who needs more than a couple of std::shared_ptr in a program is probably Doing It Wrong. I rarely find a use for them; prevalence of std::shared_ptr is often called Java Disease.

Similarly, for std::unique_ptr in public interfaces.

Finally: implementers of the Standard Library are not confined to using only Standard facilities, so in fact they do not actually need op new. In practice, they use it, but always correctly so you don't typically need it at all.

Mostly using collection classes such as std::string, std::vector, std::map and so on, but also using smart pointers such as std::unique_ptr for lower-level stuff. And of course, the Standard Library is built on those "core features" - it just makes them easier and safer to use.
'new' allocates an object on the heap, where it is prone to being leaked.

Direct allocation on the stack (and maybe moving to the caller function if needed) keeps everything local and ensures destructors are always called when the stack unwinds.

Right, I guess the thought of never having any heap-based allocation just seems... curious to me, I guess. I have lots of C# (and Java, and Python, and...) experience, and a fair amount of C experience, but I've managed to skip past C++, so many of the idioms I see in modern C++ are fascinating for me.
This isn't really the right concept. Lots of objects must be on the heap, as the stack cannot contain them. The question isn't to avoid putting objects on the heap, it's to avoid the use of "new" to put them on the heap.

Modern C++ encourages avoiding manually using "new" and relying on smart pointers for that, as they will automatically call "delete" and avoid a large class of memory leaks from heap usage.

Note that member objects are often on the heap without using any smart pointers or use of "new" simply because they inherit the memory area from their enclosing object, and I've see many inexperienced C++ devs manually allocate memory for these objects unnecessarily.

Thanks. Your answer captures the nuances much better than mine. I was concentrating simplistically on the difference between heap and stack allocation, but that misses a lot of the common usage & idioms.
Use smart pointers, put things in containers or inside other objects, ...
One way is to stack allocate it `foo temp_foo { 1, “bar” };`.

Another is to ‘emplace’ straight into a container. When the container is deleted so are the objects.

A third is to use `std::make_shared<foo>(1, “bar”)` or std::make_unique.

If you `new` something, you must `delete` it exactly once.

The constructor and destructor of a class are ideal for this because they are paired like this. But if your class does (memory) resource management like this, it should only do resource management, else it would violate the single responsibility principle.

This naturally leads to classes that are solely about resource management, where you only express the intended ownership (e.g. unique or shared) and this dedicated class ensures every `new` is matched with a `delete`. Those of course exist in the standard library (since C++11): `std::unique_ptr` and `std::shared_ptr`.

Similar story for arrays: Use `std::vector` if you need a dynamically sized array. It will do all the bookkeeping and memory management you'd have to do with `new T[N]` but it does it right.

It's not about avoiding heap allocation, despite the association that several people are bringing up here. It's about "express and abstract the resource management strategy instead of reimplementing it everywhere and obscuring it to readers".

I think it's mostly lack of planning and design, and then a layer gets built on that, and then another layer, because no one is brave enough to risk their job on rewriting a mostly working but brittle piece of code.
Sorry, I just got back from a long vacation. For users still on C-front, what benefits are to be had by moving to a compiler supporting std lib coroutines?