This kind of claim is extremely common HN but utterly foreign as someone who actually writes C++ for a living.
[1] https://www.theregister.com/2003/06/09/sun_preps_500m_java_b...
Adding JDBC and later nio is probably what got it there the most.
Congratulations, this is the stupidest thing I have read this week.
Of course the 20 years of enormous popularity and huge success in multiple industries must be due to marketing. I mean, what else.
It was the initial push (with gobs of money) given by Sun that gave Java the momentum. No language was ever pushed so hard in the marketplace by any other organization. Without that push, the language would never have the popularity that it enjoys today. There is nothing inherently "superior" to the Java language. Far better languages have fallen into obscurity because they did not get the publicity (eg. Eiffel) that Java did.
I think everybody does this and it's totally ok. Nobody needs all of C++.
I guess that's why I like simpler languages more, where I don't have to think about how the language or compiler is going to treat my code.
Go is OK. It's a little faster to get a first version working, but the result tends to be slower and more likely to crash on memory/concurrency issues than a rust version would be.
I use both; go is fine for spaghetti ~architecting~ devops-ing piles of microservices together. Rust is much better for the data path though.
I spent the last two months coding in Go pretty much not enjoying it. Nil is not always equal to nil in third party libraries through an interface -- and the compiler wouldn't warn about it.
serde is also world class for serializing/deserializing, and can generate implementations for you based on struct definitions independent of data format (json/bson/yaml/toml/etc).
The only sharp edge you may bump into for a web service is in your choice to go either sync or async, and if you go async you must then choose a runtime (usually tokio) as these are libraries and not integrated into the std lib beyond just the `async` keyword and a few traits.
But yeah, serialization in Rust is a breeze compared to Go. Probably one of the things I hated the most, along with the errnil boilerplate.
What were your issues with the C FFI? That usually gets praise from people.
Not type safety so much as program correctness. Type safety is just one aspect of it. Golang is "type safe" but still panics on null pointer accesses.
There's certainly a large learning gap; both for "classic" C++ as well as the popular modern features. But once you get over that I haven't found other peoples C++ any harder to read than other peoples C or JavaScript or Python.
No one's reaching to esoteric features like "..." or atomics unless they're some low level library wizard (in which case all their friends are also low level library wizards) or the situation actually calls for it.
Absolutely! C# introduced "var" back in 2008, and to this day there are people arguing that you shouldn't use it except when it's outright impossible to spell out the type.
From what I hear, it's the same story with "var" in Java 10.
I also don't think they're arguing cxx98 is a good language, especially by today's standards.
The argument they're making is that the tact C++ has taken in more modern revisions is actually _worse_ than writing code the shitty 1998 way. At least for axes games care about.
You mentioned move semantics, which is a great example. For a lot of game/engine code, move semantics are just adding a pile of complexity for negligible gain. Ie. Why not just use a pointer?
They didn’t have any argument. Just old man yelling at cloud.
> You mentioned move semantics, which is a great example. For a lot of game/engine code, move semantics are just adding a pile of complexity for negligible gain. Ie. Why not just use a pointer?
Because it is error prone.
OP said:
>> I've been engineering videogames in C++ for almost 20years working on AAA games
It seems like you're implying that every single business domain uses C++ the same way that your business domain does. Instead of insinuating OP is a common HN commenter that doesn't write C++ for a living, which seems to be an incorrect assumption, maybe you should recognize that not every business domain uses the same subset of C++ as you?
Your comment just reads as an arrogant opinion hiding behind a false sense of authority. It sounds like you're saying, "anyone that disagrees with my opinion must be some hobbyist programmer that doesn't know what a real programmer that does this for a living actually codes like". As if coding in a language for your job automatically implies that the code will be of a higher quality than hobbyist code. Its gatekeepy and gross imo.
I've worked professionally with disgusting C++11 code that was an amalgamation of code from some people that clearly knew what they were doing, and a lot of code that seemed to be from people that had no clue what was really happening. They had no clue because of all the hidden complexities in modern C++ that Herb Stutter is trying to reduce.
This should be a huge klaxon for the C++ community. Whether or not it's true, it's a massive problem for the C++ one way or the other.
>This kind of claim is extremely common HN but utterly foreign as someone who actually writes C++ for a living.
The above comment is equally applicable to their comment.
I have never worked on games -- maybe that domain has simpler internals -- but there are important categories of C++ software outside of games that unambiguously benefit immensely from the modern C++ features. In databases it is common to ride as close to the bleeding edge of modern C++ as is feasible because the utility of the new features is so high.
I'm sorry but this assertion does not pass the smell test.
Sticking with C++11 means you do not have std::make_unique, and any claim that these "best software engineers" not only fail to use smart pointers but also refuse to even consider instantiating a std::unique_ptr in a safe, standard way and for no reason at all is something that lacks any credibility.
> Modern C++ is for experienced C++ developers
It really isn't. "Modern" C++ is just the same old C++ with useful features that improve the developer experience (see aggregate initialization, for starters,nested namespace definitions, utf8 character literals, structured binding, etc) and don't require developers to resort to in-house trickery passed around through tribal knowledge to implement basic features (move semantics, constexpr, etc).
> It's just disappointing...
Speak for yourself. It's fantastic that people continue to improve the language and make everyone's life easier, instead of being stuck in the C++98 mud. Each and every single major standard release since C++98 brought huge productivity and safety improvements that everyone stands to benefit. Even standardizing stuff from Boost and the like is a major step forward.
It's totally fine that you personally prefer to not benefit from any of the improvements that sprung in the past two decades, but don't presume for a minute that you represent anyone beyond yourself when making Luddite-like claims.
Pretty much every non-trivial C++ engine i've seen has its own equivalents for memory management. Even a game engine development book i bought in ~2001 (meaning it was written before then) had a chapter dedicated to implementing smart pointers.
They are there because it makes sense to have them. Now you don't have build and maintain your own ship.
That standard implementation is a thin wrapper over malloc(). That standard malloc() is not necessarily good enough. The performance is not great, but worst of all when you have many small objects, they are scattered all over address space. Chasing many random pointers is expensive.
While it’s technically possible to use custom memory management with std::unique_ptr with that that second template argument with custom deleter, it complicates things. The code often becomes both simpler and faster when using custom smart pointers instead.
That’s not specific to videogames, applies to all performance-critical C++. These standard smart pointers are good for use cases with a small count of large long-lived objects. For large count of small things, the overhead is too large, people usually do something else instead.
The operative word is "it enough".
Let's not fool ourselves by claiming that all memory allocations take place in hot paths, and that all conceivable applications require being prematurely optimized to the extreme because they absolutely need to shave off that cycle from an allocation.
Meanwhile people allocate memory in preparation of a HTTP request, or just before the application is stuck in idle waiting for the user to click on the button that closes the dialog box.
It makes absolutely zero sense to proselytize about premature optimization when no real world measurements are on the table.
People mostly stopped using C++ to develop web servers which handle web requests, because they moved to Java, C#, PHP, Ruby, Python, etc. People mostly stopped using C++ to develop GUI apps which handle these buttons, because they moved to Java, C#, and now JavaScript/TypeScript.
What’s left for C++ is software (or sometimes individual DLLs consumed from other languages) which actually need it to achieve the required performance. Even despite the language is unsafe, low-level, and relatively hard to use which directly affects software development costs.
However, for Java or modern C#, in my experience the performance is often fairly close. When using either of them, very often one doesn’t need C++ to be good enough.
Here’s an example, a video player library for Raspberry Pi4: https://github.com/Const-me/Vrmac/tree/master/VrmacVideo As written on that page, just a few things are in C++ (GLES integration, audio decoders, and couple SIMD utility functions), the majority of things are in C#. Still, compared to VLC player running on the same hardware, the code uses same CPU time, and less memory.
Aren't you contradicting yourself? You started off complaining malloc of not being good enough, but now it's suddenly ok to tolerate Java and C#'s performance drop when compared to C++?
Which one is it?
This is a discussion on C++.
> People mostly stopped using C++ to develop web servers which handle web requests, because they moved to Java, C#, PHP, Ruby, Python, etc.
I'm not sure you understood what I said, or thought things through.
By the way, the top performing web framework in the Tech Empower benchmark is a C++ framework which uses C++'s standard smart pointers.
https://github.com/drogonframework/drogon
Also, one of the most popular web frameworks for Python started off as an April Fools joke. I'm not sure what's your point.
Lastly, the main reason why C++ ceased to be the most popular choice in some domains was because it was during a very long time the most popular choice in some domains, and still remains one of the most popular choices. Some of the reasons why C++ dropped in popularity is the fact that some vendors decided to roll their own alternatives while removing support for C++. Take for instance Microsoft, which was once responsible for making C++ the only tool in town for professional software development. Since it started pushing C# for all sorts of web applications, multi-platform applications, and even desktop applications, and also pushing the adoption of those technologies as a basic requirement to distribute apps in its app store, developers can only use technologies that exist. But does that say anything about the merits of C++?
Over time, it became less important for C++ to be a good general-purpose language. When performance of idiomatic C++ is good enough, using C++ is often a bad idea: it delivers comparable performance to C# or Java, but it’s more expensive to use. While technically C++ has desktop GUI frameworks, web frameworks and others, they aren’t hugely popular: due to development costs, people typically prefer higher level memory safe languages for that stuff.
For use cases like videogames, HPC and similar, C++ has very little competition, because that level of performance is borderline impossible to achieve in other languages. It’s for these use cases people care about costs of malloc, cache-friendly RAM access patterns, and other things which are less than ideal in idiomatic C++.
On the Apple and Google side that ship has long sailed, with C++ used only on the lower OS levels, and as basis for MSL.
Naturally there are still the GUIs for game consoles left (although XBox dashboard uses React Native, with the previous generation being UWP, PS 4 famously used WebGL), and special purpose embedded devices.
All of this code built just fine on pre c++11 compilers, ran reliably, was performant and easy to maintain.
Rolling your own smart pointers is not something I'd discourage.
If we're still on the topic of game engines, someone who cannot easily figure out a smart pointer implementation has no place working with the game engine's code in the first place.
Unreal Engine has its own implementation for that stuff and it took me literally minutes to get to grips with it - same with the custom engines in companies i worked at before. This has absolutely never been a real problem in practice.
One could see why it's more important for a game dev to have a language that forces your mental models to more closely match what the hardware is actually doing, rather than using abstractions that make it less likely that you'll introduce bugs.
You are voicing something that was true very long time ago. In world where you must be cross play cross platform to cover enough players there is no room for "what the hardware is actually doing". There only few games made by platform holders with exclusivity in mind are still there.
If I want to write fairly performant code quickly I'll reach for C++ and use more of the toolbox.
Whenever I move from the old-style C++98 code units to newer modern C++ ones I breath a sigh of relief. It's beautiful, safe and modern; it reads like poetry.
If I get a code review where someone manually calls new/delete I will fail that code review.
That said, I agree this proposed "new syntax" is ugly and unreadable and unnecessary.
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.
I can continue.
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.
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 longno it's not. it's fine to have non-POD unions - but you have to be careful to call constructors and destructs of non-POD's explicitly. thus, variant which automates that.
Also, I didn't talk specifically about unions, but about sum types. There is a need for saying that an object X can be of type A OR type B, no matter the properties of these types.
Only from C++11 (or is it later?). So a problem was created by relaxing the existing requirements for a "union" to which a solution was proposed by adding a "variant"? Something which had no runtime overhead (but UB) now has runtime overheads.
Regarding your point about "sum types", agreed.
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.
>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.
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?
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.
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!
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.
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.
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.
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.
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.
Seems suspicious to me that you claim to have used C++ for long. You do not understand even how virtual functions worked in the language.
I said;
>to use the "virtual" prefix keyword for virtual functions across the entire class hierarchy.
What is meant is that the entire signature of the virtual function including the "virtual" keyword is reproduced in all derived classes thus ensuring that no mistakes are made.
I also said;
>it gave the programmer the needed cue while the compiler doesn't care
It was just good programming discipline rather than depending on compiler crutches.
Just to drive it home; here is an example: https://stackoverflow.com/questions/4895294/c-virtual-keywor...
No, that did not enforce safety or solved the refactoring problem I told you. It seems you do not want to listen that override fullfills the case where you assert that you are overriding (and it will be a compile error). You have a misunderstanding and a wrong mental model for how virtual worked. It did not enforce anything, just declared a virtual function, no matter it was new or an override. virtual can introduce a new virtual function by accident.
Example:
class MyClass {
public:
virtual void f(int) {}
};
class MyDerived : public MyClass {
public:
virtual void f(int) {}
};
Refactor: class MyClass {
public:
// NOTE: signature changed to double
virtual void f(double) = 0;
};
class MyDerived : public MyClass {
public:
// FORGOT TO REFACTOR!!! STILL COMPILES!!!
virtual void f(int) {}
//void f(int) override {} // COMPILE TIME ERROR!
};
> You are making inflammatory statements without understanding what has been written; it is not appreciatedNo, I was not. I was just showing you do not know how the mechanism works with facts.
Above you have an example of why what you say does not work. I would recommend to talk more concretely and not to make too broad statements about tools you seem to not know in detail but it is up to you, I would not stop you. Just a friendly recommendation ;)
btw - Anybody who has been following this chain of responses can see who is the one using inflammatory language to hide their lack of comprehension and knowledge.
For the last time;
1) We did not depend on compiler crutches to help us.
2) We enforced coding discipline religiously so that all virtual functions are unambiguously identified with full signatures across the entire class hierarchy.
3) When you need to refactor you did simple search/replace on the full signature using grep/ctags/cscope/whatever across the entire codebase.
That is all there is to it.
This might be the most valueless discussion i have had on HN and that too over a utter triviality...sigh.
> 3) When you need to refactor you did simple search/replace on the full signature using grep/ctags/cscope/whatever across the entire codebase.
That can break in a ton of ways. Still. For example, if the pattern is not correct for grep. ctags/cscope does not fully understand C++ also AFAIK. Then you are shifting from debug your code to debug your greps, etc.
Not sure how you did it exactly, but I see it as a fragile practice. Because you rely on absolute human discipline.
> This might be the most valueless discussion i have had on HN and that too over a utter triviality...sigh.
Sorry for that. Feel free to not reply. But do not take it to the emotional terrain so fast. I just argued since the start that C++ improvements are not an accumulation of "pet features" for the sake of it. It is you who presented that as facts without any kind of evidence in the first place.
To be clear, I do not care about C++ that much or anything particularly. But I have used it enough to identify one of your top posts as emotional and non-factual.
Greetings.
I should have probably said something like "override is just a compiler crutch for lazy programmers who can't be bothered to be careful in their job" but i thought it might get me banned from HN :-)
>It is you who presented that as facts without any kind of evidence in the first place.
My comments just in this whole thread refute your above statement.
>But I have used it enough to identify one of your top posts as emotional and non-factual.
I don't think you have understood the motivations behind the features you claim to have used, how they were solved earlier without those features and properly judging whether the change was worth it.
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 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.
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.
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".
A good example is Linus Torvalds opposition to the use of C++ in the Kernel.
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?
It even states: C++ added tuples to the standard library in C++11. The proposal even mentions Python as an inspiration.
How would you implement something like unique_ptr without move semantics?
Can you give an example of angles being used to avoid needing move semantics?
I can't imagine writing a long running, memory conscious, and fast C++ program that uses whatever 'macro' management strategy you envision.
We also happen to do 0 other steady-state syscalls (not just 0 mmap, munmap, mremap, etc.), so we can just run the program under valgrind and any time valgrind prints something is a bug.
We didn't use C++ but some other software I've heard uses a similar approach is written in C++.
So I also don't see how these are related
The typical pattern is to create an arena whose lifetime is the lifetime of some important operation in your program (say, serving a single request or processing a single frame), do all of the logic of that operation via simple allocation in the arena (ideally bump-pointer allocation), don't free/delete anything at all, then when the operation is finished, just free the arena itself (or don't, and reuse it for the next operation by just resetting the pointer).
This implies, or at least allows, a very different style of writing C++ than usual - no need for tracking ownership, so no RAII, so no constructors or destructors, so no smart pointers.
Of course, you can also write this kind of code with RAII and smart pointers (with nop destructors for non-resource objects), using placement new to construct objects inside the arena and so on. But it's not necessary, and some find it easier to avoid it.
A std::string is an example we can use, it acts fairly similar to unique_ptr. Sometimes you just need to heap allocate something, work with it and/or store it as a member, maybe pass it to a function or whatever, and then have it cleaned up at the end of the scope or when the object that it's a member of is destroyed. I don't think an arena can replace this use case.
If we need something with multiple handles, that doesn't have a trivial and predictable lifetime (such as objects / variables in a programming language interpreter, or entities in a video game), you can reach for shared_ptr or an arena. In the specific example I gave I would certainly prefer the arena, and would implement a garbage collector to deallocate stuff when it became unreachable.
The arena pattern is fairly common in Rust, because in Rust even a reference counted smart pointer doesn't let you bypass the "shared ^ mutable" enforcement from the borrow checker! Having everything owned by the arena greatly simplifies issues with the borrowck, and you can just allow callers to take temporary references and drop them when they are finished with them. There is a crate called "SlotMap" that provides a data structure to assist with the pattern (there is a C++ SlotMap library as well somewhere).
Anyways I have rambled a bit, but I think unique_ptr solves a different problem than what you describe, which instead is more of an alternative to reference counted pointers (shared_ptr).
If you use a bare pointer instead, not only do you get the same guarantees, but now if you need to modify the code to actually share that object, there's no need to modify any references from unique_ptr to shared_ptr.
With an arena ideally you can just pass out actual references (T&) to callers instead of pointers!
Boost has a full implementation of boost::unique_ptr that is identical to std::unique_ptr but works with C++98 [1].
https://www.boost.org/doc/libs/1_80_0/doc/html/boost/movelib...
I guess this means that you don't have to have it at the language level to support unique_ptr, but you do need some form of move semantics at least. I think having it built into the language as a proper feature is preferable personally.
https://www.boost.org/doc/libs/1_80_0/doc/html/move/what_is_...
>Rvalue references are a major C++0x feature, enabling move semantics for C++ values. However, we don't need C++0x compilers to take advantage of move semanatics. Boost.Move emulates C++0x move semantics in C++03 compilers and allows writing portable code that works optimally in C++03 and C++0x compilers.
There are lately some improvements to that, like making move/forward an "intrinsic" from the point of view of the debugger. Also the SIMD interactions seemed to be a problem in certain loop vectorization.
As for "you are clever bc you do not use move constructors"... I can be clever and use assembly instead of C. But I am not sure it would be the right choice in all scenarios. If you have a tool and can take advantage of it, just make use of it.
An enormous amount of "smart" old school c++ is at best equivalent to a well written modern equivalent, and at worst more dangerous, error prone and slower. Anything that uses references for writable out parameters combined with a bool return value (or no return value) for success is a great example. On my last project, I rewrote a bunch of fundamental Unreal Engine path handling methods, replacing the old school bool GetXXX(TCHAR* In, TCHAR* Out); with FStringView GetXXX(const FString& In);
In the process of doing so I found multiple engine and game bugs that in the best case were logic bugs and in the worst case were memory safety issues. I also left the original function call in place and made it wrap the new version with no perf overhead for the callsites I didn't change, and a 30% increase for the cases that I did fix. Almost all of that is because I stopped requiring null checks, I was able to lean on RVO and move semantics, and I was able to avoid unnecessary initialisations in many places in the code. Most of the sites I replaced were calling FString ThingStr; TCHAR* Thing =<...> if (GetXXX(FooStr.c_str(), Thing)) ThingStr = FString(Thing);
And most were replaced with FStringView Thing = GetXXX(Foo);
> write very simple C++98 code, maybe a bit of C++11 but that's it. They keep it simple.
Assuming of course there's already a smart pointer library that you're using, modern c++ allows for simpler, more correct and faster code in many many places. Some examples are auto:
for( map<string, int>::iterator it = map.begin(); it != map.end(); ++it)
becomes
for (auto& it: map)nullptr is now a thing over NULL, move semantics make it feasible to write logical code without needing to jump between logic and type manipulation for basic operations, static assertions exist, enum class, attributes (nodiscard, noreturn, deprecated),constexpr and all the evolutions that came with it, structured bindings, concepts, if/for initializes...
All of the above are just off the top of my head features that I interact with daily, even if I don't write them daily, all of which make my code and project safer faster and easier to understand.
> This new C++ syntax is just a gimmick that we don't need.
I disagree. It has many useful properties. It gave us the spaceship operator which massively simplifies the code needed to write correct objects. A single well written spaceship operator can mean numerous algorithms just work on containers of your type, rather than custom filter/search operations. The syntax of left to right definitions for functions _seems- superfluous but it drastically reduces the amount of work needed to be done by the compiler which should translate into real world compilation speedups. The import/include swaps could allow for new code to interact with well behaved old code in a safer manner. As someone else said on this thread, it's not always about one feature in isolation, it's about combining multiple features and the result being worthwhile.