Hacker News new | ask | show | jobs
by rramadass 1375 days ago
Well put!

Much of the C++ code out there is still C++98 and a lot of the "so called problems" had already been solved by using established code patterns (not GoF). "Modern C++" has added a lot of complexity but not for much benefit; its fanboys always use hand-wavy phrases like "much nicer", "makes programming simple"(!) etc. which mean nothing. After the introduction of STL and explosion in the usage of "Generic Programming" the only thing the language needed was Concurrency support for Synchronous/Asynchronous programming. Instead the ISO committee went off the rails into "modernizing" the language to try to make it like Python/whatever. The result is that experienced C++ developers are very cautious about adopting "Modern C++" while the larger "know little/nothing" crowd are raucously running around making all sorts of unwarranted claims.

IMO, today the greatest enemy of the language is the ISO committee itself.

3 comments

I am not sure you have used C++ that much.

Claiming that move semantics, structured bindings, lambdas, designated initializers, non-impliciy self, override for virtual functions marker, [[nodiscard]], coroutines, modules, smart pointers, safer bitcasting, non-ambiguous bytes (std::byte), scoped enums, delegated and inherited constructors, constexpr, consteval and much, much more are not improvements for day-to-day... well, shows me that you must have not used it that much.

Nice list ... but that doesn't mean you understand what you have listed. Your list reads like what a noob "Modern C++" programmer would focus on (keywords and features to get through an interview) rather than any long-term (i have been programming in C++ since early nineties) real work experience.

Lots of good C++ based systems have been written before any of the above existed. That proves that they are not "needs" but merely "wants". Only some in the above list are worth adding to the language while others are just noise (as an example keywords like "override"/"default"/"nodiscard" just add to syntactic noise rather than any actual benefit). The members of the ISO C++ committee simply pushed through their pet "wants" for brownie points.

When override was introduced it immediately solved a few hidden bugs here so it definitely has value. I'd say that when doing development with inheritance it catches at least a bug or two a month at compile-time. Same for nodiscard, I started using it recently and immediately found bugs. So for me their value is infinite
As a counter anecdote i have not found any uses for the above and yet my code is running perfectly fine. That just reinforces my point that they are "wants" and not "needs".
Well, that is ok. It depends a lot on your codebase.

The fact is that you will have to check by hand or inspection what the compiler can tell you, right? That saves time and effort. Especially when refactoring.

Go refactor the signature 2 or 3 virtual functions with let us say a couple parameters each in two classes and 4 or 5 derived classes, one two levels deep (this is real code what I am talking about) and do it with and without override.

Try to measure the time it takes you with or without override keyword. You could use some refactoring tool, fair. But you do not have that available in every context.

>Go refactor the signature 2 or 3 virtual functions with let us say a couple parameters each in two classes and 4 or 5 derived classes, one two levels deep (this is real code what I am talking about) and do it with and without override.

This is just trivial and does not really support your arguments.

The way it was done before was to use the "virtual" prefix keyword for virtual functions across the entire class hierarchy. Merely a discipline which was enforced religiously via coding guidelines; it gave the programmer the needed cue while the compiler doesn't care. You then did a Search and Replace as needed.

> Lots of good C++ based systems have been written before any of the above existed. That proves that they are not "needs" but merely "wants".

By the same argument, plenty of good code was written in C before C++ existed, so the entirety of C++ is "wants" rather than "needs".

Yes, In a very strict sense it is true for many experienced programmers and their domains of expertise. So when a expert embedded systems guy tells me that he will not touch C++ for his project, i understand and only ask him to look at C++ as a "Better C" and nothing more. All features are not equal and some are obviously more beneficial than others eg. simple user defined types i.e. class value types vs class hierarchies.

A good example is Linus Torvalds opposition to the use of C++ in the Kernel.

I do understand what I listed bc I basically have been doing C++ for a living for 13 years and started 20 years back at uni.

Of course good systems have been designed, but try to use the stl without lambdas. Or return big values by pointer bc u dnt have value semantics with all its associated usability problems. Try to write sfinae vs concepts or write a std::optional type without C++23 explicit self. Try to go and add boilerplate for free bc you did not have delegated constructors. Or try to build a table inside C++ at compile-time pre-constexpr. Do generic programming withou if constexpr. I have done myself all of that before C++11 and it was way more difficult to write a lot of code. Of course under your view anything is "nice to have". Then just grab assembly. But for the people like me that use it, I'd rather see it evolve with useful features.

Those keywords do NOT add noise. They add safety, since defaults cannot be changed. C++ is as good as it can get with its constraints. You can think it is bad, but C++ is an industrial language, real-world and with compatibility as a feature.

I can buy you could disagree with some of the decisions, but mostlyit fullfills well its purpose. Herb is just trying to create a C++ 2.0 that is simpler and 100% compatible. It is good, very good to not start from scratch if you are in industrial envs. You just do not throw away 40 years of code that has tested the pass of time pretty well, no matter it was written in C or C++.

I am not sure that you have understood my comment, hence let me explain;

I am a longtime programmer firmly in the C++ camp (as an example, see one of my earlier comments here: https://news.ycombinator.com/item?id=27854560). I am also not against the evolution of the language. But what I (and many other C++ programmers) are against is the messianic zeal of the "Modern C++" proponents hellbent on changing the language to be more "user-friendly" like Python/whatever in a mistaken belief that "C++ programming is now made simpler". By adding more and more features the interaction between them is now only more complicated then ever (i.e. When and How do you use them correctly and efficiently? How do you combine them into a clean design?) making the programmer's job that much more difficult (as an aside, this is also the reason beginning/new-to-C++ programmers give up on the language).

The above problem is compounded because C++ is already a multi-paradigm and multi-usage language i.e. situated in a co-ordinate plane with the above axes.

The paradigm axis:

- a) Procedural Programming i.e. "better C". - b) Object-Oriented Programming. - c) Generic Programming. - d) Compile-time Programming.

The Usage axis:

- a) Low-level Interface Programming eg. MCU programming. - b) Library Programming. - c) Application Programming.

Every feature ideally sits at an intersection of the above two axes. Thus for example, the "auto" keyword from C++11 is best suited for "Generic Programming paradigm" and "Library implementation usage" (eg. in the implementation/use of STL). But what is happening is that the "Modern C++" proponents are using it willy-nilly everywhere (because "hey i never declared any stinking types in <scripting language>") making the code that much harder to comprehend. Similar arguments can be raised against a lot of other features. This is the reason many of us are cautious w.r.t. the new features; we know the existing potholes, have worked around them and have our system under control and in hand. The ramifications of introduction of new features just for its own sake is unknown and makes us very nervous.

I do understand it, but you put yourself in a false dichotomy at the same time. You say you have to master every corner just because features are added. This is not true most of the time.

Of course you layer features on top of existing things. It is the only way to "simplify" the language and keep it compatible. For example, now you still have raw pointers: noone recommends managing raw memory anymore, the recommended thing is to return a smart pointer or do RAII inside your class (this last one pre-C++11).

How about virtual functions? Oh, now you have to know more (and this actually does not really hold true in every context, sometimes it is easier): of course you might need to know more! Recommendation is to mark with override! How about template metaprogramming with SFINAE! No! Hell, NO if you can avoid it! That is why constexpr, if constexpr and concepts exist!

It is not more difficult to program in the last standards, it is way easier to program. What is more difficult is that you need to know more. But you do not need to know absolutely every aspect of the language. They layered features that are more general so that you stop using the legacy ones, and that is an improvement inside the constraints of what C++ is allowed to do right now to keep it useful (compatibility).

If what you want is to learn to program in permutations of all styles, including the old ones, in C++ and in all kind of devices... I do not know people that are experts at all domains and levels of the stack, no matter the language you use. So again, you can use your subset.

> But what is happening is that the "Modern C++" proponents are using it willy-nilly everywhere (because "hey i never declared any stinking types in <scripting language>"). Similar arguments can be raised against a lot of other features.

You basically said nothing concrete above.

BTW, noone prevents you from using many of the inferior styles. And by inferior I do not mean non-modern, I do not buy modern for the sake of buying it, I use the features that make my life easier, and the same feature or library thing that makes my life easier in one context is the one I discard in another (for example dynamic memory in embedded or fancy ranges in videogames I might need to debug and understand to the vectorization loop level if I do not have the parallel algos).

You can still, as I told you in the comment above, ignore override, for example, and try the refactoring exercise I told you, making your life more difficult on your way.

Or ignore structured bindings in for loops and declare the variables yourself for pairs. Or you can do some fancy metaprogramming with SFINAE and spend 3 hours figuring out what is going on or why you cannot use it in the parameter type of an overloaded operator and have to use it in its return type or guess which overload you are selecting, because if there are two difficult things in C++ those are initialization and overloading.

In the meantime, some of us will be using some concepts to constraint the overload set, or, even better, present as interface the weakest of the concept fullfilling a function and use if constexpr inside to choose the optimized paths for each type as needed.

Those are improvements, BIG improvements on how I write everyday C++ code. The language is big, bc there is not a choice. But a good training and taste for what to use and what not to use in your area of work is necessary.

My other response is also applicable here: https://news.ycombinator.com/item?id=32894154
> "modernizing" the language to try to make it like Python/whatever.

I've been writing C++ code professionally for almost 20 years now, and Python for 10 years. I tried to think of any C++ features post C++98 that would make it "like Python", but I can't think of any. Can you give some specific examples?

We will not go down this path since everything is highly debatable but here is an article: https://preshing.com/20141202/cpp-has-become-more-pythonic/

It even states: C++ added tuples to the standard library in C++11. The proposal even mentions Python as an inspiration.

You say a lot of words, but have substance. Which “so called problems”? Which “established code patterns?”. Why “it means nothing” and to whom?

I can continue.

This is the old trope of asking for proof from one side in a forum where things are discussed freely. I could turn it around and ask you to justify all the new "features" added to say C++11..20. I am here for the rest of time.

But to give a few concrete examples;

>Which “so called problems”?

Apparently "variants" were introduced to solve problems with POD "unions". Mere complexity for not much benefit. Nobody i have worked with ever said "unions" were difficult to use.

>Which “established code patterns?”

RAII as a solution to People harping about memory problems. I have written entire protocol message libraries just using RAII to avoid memory leaks.

>Why “it means nothing” and to whom?

When "Modern C++" proponents say everything should be written with the new features using hand-wavy phrases, "it means nothing" to experienced programmers. If we find a feature useful to model a concept and/or express something we will use it; but always weighed against how complex it is to read and maintain. A good example is template meta-programming taken too far.

The key reason C++ took off was because it added minimal "Zero-Overhead abstraction" constructs over the baseline "low-level and simple" C core. Suddenly programmers could have their cake and eat it too. The evolution of C++ should have continued in the same spirit but instead in a misguided effort to compete with later and differently designed languages a lot of complexity has been added for not much apparent benefit.

Variants are meant to introduce a tagged union like data structure into C++.

You can do this without variants by manually defining your own unions and managing the tag yourself, but this is very not type safe and requires extra boilerplate code to manage!

Maybe you have never used and don't care about this feature but it's actually pretty useful! Tagged unions make representing certain types of data very elegant, for example nodes of an AST or objects in a dynamic programming language. You can use OOP patterns and dynamic dispatch to represent these things as well, but I think tagged unions are a better fit, and you get to eliminate a virtual method call by using std::visit or switching on the tag.

I suspect that maybe you have never been introduced to sum types in general which is not uncommon! I am curious if you have experience with using them or not?

https://en.wikipedia.org/wiki/Tagged_union

variants/sum types is not some earth-shattering concept but has been implemented using tagged unions in C from the beginning of time. At one point in my career i had an occasion to write a "spreadsheet-like data structure" (basically a linked list of linked lists) in three different ways one of which was using a tagged union for the data nodes. The point is that people trivially rolled their own when needed and did not clamour for language/library additions.

I have already pointed out in some of my other replies why it is wrong to consider a variant as a replacement for POD union.

I do realize people have been using tagged unions for a long time. Having some library helpers goes a long way in making them more usable and more expressive. Having them built into the language as a first class feature is even better yet, but std::variant is a nice middle ground.

Technically you can implement it manually, but the same thing could be said about all language features, even in C. We don't need actually need structs built into the language, we can just allocate blocks of data and handle the offsets by hand. We don't need functions, we can just push stuff to the stack and use the call opcode. The same goes for various loop constructs, goto can do everything "for" and "while" can do!

I don't think "we used to roll our own X back then" is a strong argument for something being bad or unneeded. Abstractions are all about allowing the computer to do work for us, and making things less error prone and more expressive. This is why we have programming languages to begin with and don't write everything in assembly!

What you are expressing is a Sentiment and not an Argument. First see my other relevant comment here: https://news.ycombinator.com/item?id=32893171

Language design is a fine balance; the addition of Abstraction Features has to be balanced against the cognitive load it imposes on the Programmer. These abstractions also need to be tailored to a computation model supported by the language. For example, the Niklaus Wirth school of language design was famous for insisting on minimalist languages; You only added features if they were "needed" to support the model of computation and all extraneous/trivial features were omitted. C++ took the opposite route from the beginning which was ok to a certain extent since it increased the expressive power of the language (eg. multi-paradigm). But over time the balance is being lost and the cost of cognitive load is outstripping the benefit of an added abstraction. This is bad and not welcome. So looked at in this light how many of the features in Modern C++ are essential and how many are extraneous (eg. Concurrency features = Essential, variants/syntactic annotations/etc. = Extraneous)? That is the fundamental issue.

> Nobody i have worked with ever said "unions" were difficult to use.

I wouldn't trust anyone to write the correct boilerplate for

    union { std::string s; int v; }; 
unless they'd do just this all day long
std::string is not a POD.
... yes? One still needs to put it in sum types sometimes. This is what std::variant solves.
But that is the point; "union" is supposed to be a POD, if not all language guarantees are off.
You mistakenly assume I am pro new "features".

I'm just pointing out that you didn't have anything concrete in your messages.

> Apparently "variants" were introduced to solve problems with POD "unions". Mere complexity for not much benefit. Nobody i have worked with ever said "unions" were difficult to use.

https://stackoverflow.com/a/42082456

First link in Google, how is moving from undefined behavior and not needing manually declare type of union (sic!) is a "mere complexity for not much benefit"?

> Nobody i have worked with ever said "unions" were difficult to use.

Sure, enough people say that about for and while loops while avoiding functional patterns like a plague.

> RAII as a solution to People harping about memory problems. I have written entire protocol message libraries just using RAII to avoid memory leaks.

Which "modern, overhead features" are solved by RAII?

> When "Modern C++" proponents say everything should be written with the new features using hand-wavy phrases, "it means nothing" to experienced programmers. If we find a feature useful to model a concept and/or express something we will use it; but always weighed against how complex it is to read and maintain. A good example is template meta-programming taken too far.

Who are these experienced programmers and how do you define those? Your circle of people?

> If we find a feature useful to model a concept and/or express something we will use it; but always weighed against how complex it is to read and maintain.

Modern features are literally easier and less complex than old way. Like in the variant example.

> A good example is template meta-programming taken too far.

And how often "modern c++ proponents" suggest meta-programming to solve ordinary problems? Because everywhere I've encountered C++ discussions, those were resorted either for internal usage or suggested to avoid at all.

> The key reason C++ took off was because it added minimal "Zero-Overhead abstraction" constructs over the baseline "low-level and simple" C core.

There's no key reason C++ took off. It took off, because it took off.

> The evolution of C++ should have continued in the same spirit but instead in a misguided effort to compete with later and differently designed languages a lot of complexity has been added for not much apparent benefit.

There's a reason why those differently designed languages were defined differently. Maybe instead of blindly hating evolution, try understanding the reasons behind it.

And you have mistakenly assumed that i am "blindly hating evolution". The distinction i make is between "needs" and "wants"; much of what has been added in Modern C++ is "wants".

>First link in Google, how is moving from undefined behavior and not needing manually declare type of union (sic!) is a "mere complexity for not much benefit"?

Because you have not understood the definition and guarantees of a "union"; and UB is not always a bad thing. "union" is explicitly defined to be a POD with all it entails. The stackoverflow answer does not provide anything new. If you want something more, you code it explicitly when needed. No need to burden the language; in fact it creates more problems because "variant" does not guarantee layout compatibility.

>Sure, enough people say that about for and while loops while avoiding functional patterns like a plague.

Of course familiarity and clarity always trumps "new patterns".

>Which "modern, overhead features" are solved by RAII?

The harping on "never use naked pointers" in your code.

>Who are these experienced programmers and how do you define those? Your circle of people?

Of course; It should be the same for you and everybody else too!

>Modern features are literally easier and less complex than old way. Like in the variant example.

This is what we are debating; it is not a fact that you seem to assume.

>There's no key reason C++ took off. It took off, because it took off.

There is always a tipping point. In C++'s case it was compatibility with C and new language constructs for higher level abstractions.

>There's a reason why those differently designed languages were defined differently.

Exactly; Each starts with a Computation Model and evolves a syntax to that model. The evolution should not be willy-nilly dumping everything and the kitchen sink into a language. C++98 was complicated enough but still manageable but what has happened from C++11 onwards is just too much complexity requiring even more effort from experienced programmers. You cannot hand-wave it away by saying "Modern C++ is a whole new language so forget baseline C/C++ cores" which is quite silly.