I don't want to outright dismiss this piece as "argument bait", but it is essentially just a bunch of assertions on what other people should do with virtually no rationale and a bunch of shade thrown around deliberately. Sure, we all like to have a good argument, and C++ is an easy target, but this seems wholly unproductive from the get-go. If there was any intent of convincing others then this isn't how to do it.
I would also like to know who or what the "Orthodox C++ committee" is. A search only gets you this exact gist. Is the author himself the committee? That really doesn't put this piece into a better light.
Russian here. Guys at my company still use Far (https://en.wikipedia.org/wiki/Far_Manager) as their primary file manager, and I'm not aware of any people other than us Russians who are still using Norton-like file managers in their day-to-day work.
Interesting idea, pity about the name. There's a vaguely similar effort with a similarly poor choice of name, Clean C, which is guaranteed to compile as both C and C++ with the same behaviour (provided there's no undefined behaviour or platform-specific behaviour). [0][1]
> Exception handling is the only C++ language feature which requires significant support from a complex runtime system, and it's the only C++ feature that has a runtime cost even if you don't use it
I don't think that's true. I believe that if you enable RTTI you pay for it even if you don't use it, which is why it's banned in the LLVM codebase. [2]
> Don't use C++ runtime wrapper for C runtime includes (<cstdio>, <cmath>, etc.), use C runtime instead (<stdio.h>, <math.h>, etc.)
I believe this is still supported, but is officially deprecated, for what that's worth. [3]
This is somewhat exaggerated, especially because while there are several poorly designed areas in C++ and the STL, it's not all that bad and it can lead to safer, less fragile code than the C equivalent with less effort (but arguably, more skill is required in order to learn and proficiently use C++). It can even be faster than C in the hands of a skilled developer that knows where and how use the right features.
It doesn't hurt that the STL is extremely optimized, and often provides you tools that accomplish what you cared about without lots of the gotchas and pitfalls you're almost certainly doomed to fall for if you're not aware of their existence. Almost every single C project I've seen (including some of mine, I've to admit) end up involuntarily reimplementing some basic algorithm or container, and most often than not it ends up being less efficient, or unnecessarily using the heap.
One thing I think most people largely misunderstood is exceptions; they're actually totally fine if used in _exceptional_ circumstances, as if they were some kind of softer assertion, when you expect some kind of contract to be always true, but calling abort is just too unreasonable. This is basically the same approach Rust and Go use with panic(), and in both languages this facility by default causes the runtime to unwind the stack, as with C++ exceptions.
I think lots of people have been turned off by certain bad designs in older C++ revisions, where exceptions where indiscriminately used for error handing. They were a nightmare to use right, and the lack of helpers like unique_ptr made exception safety very hard.
Modern practices, an arguably better language after C++11 and guidelines/libraries such as the GSL vastly help in mitigating most of these issues, as long as people actually stick to them (i.e., no C style code without RAII).
I may also argue that the whole "C++ exceptions are expensive" argument is 100% moot in 2020, I've been running code on microcontrollers with half a megabyte of ram with them turned on and they add an almost insignificant amount of overhead; they only cause a small amount of binary bloat, but that's nothing compared to the size of certain modern libraries (and storage is cheap).
> and it's the only C++ feature that has a runtime cost even if you don't use it – sometimes as additional hidden code at every object construction, destruction, and try block entry/exit, and always by limiting what the compiler's optimizer can do, often quite significantly.
C++ doesn't do "hidden code" - "if you don't use a feature you don't pay for it" is one of the bedrock principles of the language.
"you don't pay for it" doesn't mean that no code gets generated for exceptions in this case, but that no code gets executed unless an exception gets thrown, and your example does not disprove that.
Generated code has a cost in terms of size, of course, and in terms of performance via the addition of branching, branch mis-prediction penalty and decreased cache locality. This function invocation will also of course flush the pipeline and harm inlining. On a hot path this could be non-trivial.
That not the point, though. The point is that the cost is invisible with exceptions. For example, there could perhaps be an alternate version of C++ where every function is required to declare whether it could throw an exception (and a very smart compiler proves where it's possible), or that -fno-except flag is inverted so you have to opt in to exceptions.
I don't agree with the article but this is actually true, exceptions and RTTI are the two exceptions to the zero overhead principle. if you don't use -fno-exceptions (or mark the function as noexcept), every function will generate additional instructions to deal with potential exceptions happening
This is false. Most code emitted by modern compilers incurs no overhead for exceptions except for when they are actually thrown. This is a historical rule that is no longer true.
I don't think it really contradicts what I said, I didn't say extra instructions were executed on each function call, just generated. I guess it depends on how you define overhead, but if you compile a program that does not use exceptions with exceptions on, it will run slower if you benchmark it. That being said, I do use exceptions and think they're obviously worth it, but there is an overhead to them, even if it's not as simple as "x additional instructions are executed"
That would be one strange benchmark then. It would have to be benchmarking something other than the code in question.
Simple analogy: let's say you have a program that has 10 functions in it. You write a benchmark that exercises one of these and never (no, not once, never) calls the other functions. The other 9 are truly dead code. Would you expect the benchmark result to be different between that and a program that only included the one function being benchmarked?
There are instances where I can get current GCC to generate less instructions as part of the normal control flow when adding noexcept declarations. This is quite surprising given the common claim that exceptions shouldn't affect that. Unfortunately, I think I lost the example where I could reproduce it in the Compiler Explorer.
The issue there is interaction between exceptions and deterministic destructors. In the end it means that there has to be some hidden code for every stack allocated object whose destruction could be skipped over by exception handling mechanism. There are various way to implement that, but all of them involves something that is "hidden code" and also the mythical "overhead for feature you don't use".
Another giant issue with C++'s "if you don't use a feature you don't pay for it" and interactions of various features with each other is that features you do use tend to be horribly expensive. It seems to me that authors of C++ tend to use some antiquated model of what "runtime cost" means which totally ignores effects of cache locality and code size.
I'm not a C++ developer, but AFAIK the fact that C++ is not 100% "you don't pay for what you don't use" is commonly acknowledged. Exceptions are an example of that.
The concept isn't terrible necessarily, but the execution and condescension sure is. And some of the rules are just pointlessly argumentative. Such as refusing to use <cstdio> and instead using <stdio.h> - those are officially documented to be different things with different behaviors. Blanket banning one of them doesn't improve simplicity, especially if you're blanket banning the "wrong" set of headers. Unless the goal is to also ban namespaces, but that's not called out as such (and namespaces are one of the least contentions C++ features - everyone seems to like them well enough).
Or similarly:
> Don't use anything from STL that allocates memory, unless you don't care about memory management.
So don't use std::vector? Or std::unordered_map? Or std::string? These are all perfectly fine classes, banning them makes no sense at all. Maybe the goal was to ban "hidden cost" classes like std::function, but rolling your own 'C-style' is a hell of a lot more complex & error prone (raise your hand if you've seen a C pointer callback that forgot to have a void* context or a mismanagement of said void* context...)
I think any attempt at defining a strict subset of a language is going to face some pushback like yours, but I do agree that while I agree with most choices this one seems strange. Rolling your own containers is boring and error-prone and won't have all the features of the STL.
Also, while iostream is a bit of a mess, having it be strongly typed means that it's, in my opinion, well worth using over cstdio.
One thing I was surprised not to see mentioned here is multiple inheritance. In my experience it gets really messy really fast.
Also I really recommend reading the linked archived Boost discussion about a geometry library, it's quite funny. It starts with:
double distance(mypoint const& a, mypoint const& b)
{
double dx = a.x - b.x;
double dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
And after a couple of pages of refinements ends up with:
> One thing I was surprised not to see mentioned here is multiple inheritance.
There's nuance to banning that since implementating multiple interfaces is technically multiple inheritance in C++. So I think I'd agree with you but with an exception for pure-virtual classes.
> Also, while iostream is a bit of a mess, having it be strongly typed means that it's, in my opinion, well worth using over cstdio.
> So don't use std::vector? Or std::unordered_map? Or std::string? These are all perfectly fine classes, banning them makes no sense at all.
Orthodox C++ bans exceptions, so if an allocation failure occurs within one of those classes, the only thing your program can do is abort.
That’s fine for some programs - Google’s C++ style guide takes this approach, as does LLVM. But for other programs, where aborting on allocation failure is not acceptable, there’s no way to use those classes without using exceptions.
> Google’s C++ style guide takes this [ no exceptions; libraries abort ] approach
Note the style guide says they’d rather use exceptions but had too much legacy code (and now must have a couple of orders of magnitude of it by now): “On their face, the benefits of using exceptions outweigh the costs, especially in new projects. However, for existing code, the introduction of exceptions has implications on all dependent code. … Because most existing C++ code at Google is not prepared to deal with exceptions [don’t use them]”
Google’s non use of exceptions is often used as justification for not using them either, by people who haven’t actually read Google’s style guide.
The only "real" issue with exceptions is binary size, and binary size does seem to be a rather specific goal of Orthodox C++ even though it's not explicit about that. But on any desktop or server deployment binary size is also so incredibly irrelevant it's not a good justification. On mobile maybe, but it's questionable. For embedded definitely, though.
While interesting I guess that sounds like a straightforward compiler bug not a problem with the feature overall. Unless there's some missing nuance why 'noexcept' was apparently being ignored which would need further investigation.
Exceptions have hidden costs that are hard to spot. For example, generated assembly for C++ code with or without noexcept can be quite different. Part of the reason is that DFA based optimizations get overwhelmed by the number of implied execution paths when exceptions may be thrown. So the optimizer essentially is forced give up more often, resulting in slower code for the normal case. This is orthogonal to any binary size issues from linking the runtime support for exceptions.
Of course, but that's comparing error handling code with non-error handling code.
The comparison you need to make is instead exceptions vs. return values. Unless you're going to argue all errors should abort by default or similar.
I do wish noexcept had better support, or was even the default (C++ and wrong defaults, a tale as old as time). But it's disingenuous to compare exceptions against nothingness.
No, this holds in a fair comparison with manual error checking. In the presence of possible exceptions, the compiler has to make the assumption that any nontrivial expression can throw, especially if its implementation is opaque at the callsite. With explicit error handling, this is reduced to those checkpoints that are inserted manually, which are generally fewer.
If you call a function within the condition of an if statement, the compiler needs to conservatively assume that the execution flow from there is either to the rest of the conditionsl or to either one of the catch blocks or the functions' stack unwinding. If there cannot be an exception, the execution flow has none of these additional branches that bog down the DFA.
> But for other programs, where aborting on allocation failure is not acceptable
Sure but that seems like the edge case not the general case? Nearly any desktop or mobile app or game won't have any such constraints, for example. And you've also got overcommit to deal with on those platforms, so it's not like your allocation failure will actually happen at malloc time either.
So outside of embedded, which tends to have a variety of constraints, what even pretends to handle allocation failures across the entire program?
Not really. You can supply alloctors to contai ers, but it becomes iffy for strings. Custom allocator support for std::function was removed in C++17. And there is no way that I am aware of to use custom allocators for the std::shared_ptr refcounters.
It is quite common in certain contexts to allow the user of a library to provide their own custom allocator. And this would be kind of pointless if some allocations circumvent that, wouldn't it?
The deeper rationale is that some systems like to assign fixed memory pools to subsystems. These may be reset or destroyed at certain points in the applications life cycle and the general expectation is that this frees all memory used by said subsystem. If you still want to use the STL in that context, you need to provide custom allocators for everything that potentially allocates.
But by doing that you'd be breaking the safety (and functionality) of shared_ptr as you'd end up with potentially dangling references (similarly weak pointers are broken). Which then raises the question of why are you using shared_ptr in the first place? You could use a custom allocator just for auditing that all outstanding references were released, I suppose, but actually releasing the memory anyway is just adding bugs. Which can be a fair risk to take, it just seems like shared_ptr is then not the thing you want to indicate appropriate "running with scissors" behavior.
As long as you can guarantee that no shared_ptr lives longer than its backing memory pool, it's perfectly fine. The same goes for every other kind of pointer wrapper you could dream up.
At least you can write a replacement for shared_ptr. The same is not true for std::function. This is the only named type a capturing lambda converts to according to the language standard and it's an STL type with complex behind the scenes behavior.
You can write your own std::function, too, nor is it the only STL type that can take a capturing lambda (std::packaged_task for example).
A capturing lambda is just a class with an operator(). It's complicated to do what std::function does, but fully possible.
In fact, custom std::function replacements have better lambda support than std::function itself. Such as unique_function in https://github.com/Naios/function2 which can handle non-copyable lambdas.
Try not to read condescension into things so easily. Eg I'm pretty sure that the line you quote,
> Don't use anything from STL that allocates memory, unless you don't care about memory management.
is not meant condescending but literally. Ergo, if you don't care about memory management (which is fine), then feel free to use lots of STL stuff.
Case in point: for lots of embedded software, memory matters a lot and it's important that it's obvious to the reader of the code what memory gets allocated where. The STL classes you quote make this harder to see. But if you have lots of RAM anyway (ie "you don't care about memory management") then this does not matter much.
A key goal of this list appears to be, I quote, "Projects written in Orthodox C++ subset will be more acceptable by other C++ projects". This particular guideline makes it more likely for your code to be deemed acceptable by embedded programmers so it seems to fit. I don't think it was intended to be condescending in any way. I think the same holds for the other points.
It could've definitely been written a bit clearer though.
> Try not to read condescension into things so easily.
If they didn't intend condescension, they should not have put a comic displaying exactly that (and with essentially no further relevance) into the middle of the article.
> is not meant condescending but literally. Ergo, if you don't care about memory management (which is fine), then feel free to use lots of STL stuff.
I don't see it that way. Plenty of people care about memory management and also see the STL as a fine tool to use. Little is gained from a memory management perspective by avoiding e.g. std::vector. Whether condescension is intended or not, this definitely comes across as talking down to me, and worse, it is incorrect.
I don't think "more acceptable by other C++ projects" works out well. If I don't appreciate how RAII and exceptions clarify normal control flow, I won't write code that avoids leaking when its dependencies throw. This poisons any project it spreads to. Google famously had to abandon any hope of using exceptions because of a critical mass of flawed legacy code.
While I can't make a general comment, for some applications such as videogame engine development, std::unordered_map for example is not a perfectly fine class. Its linked-list-based memory layout means it is extremely CPU cache-unfriendly, making it incur an inordinately high amount of cache misses. [0] As you have a budget of 6ms per frame for 165 FPS, and you often need to iterate over many thousands of entities in each frame, std::unordered_map for one is a non-starter.
In general, when discussing which language features to use, I would keep in mind that something can work well for an application that is fairly straightforward and has modest performance requirements, while being a complete no-go for a different use case. I'm not saying we should throw C++ in the bin, but I also think many people have their reasons for saying some C++ features are more trouble than they're worth.
Still, it's not ok to just blanket ban them. If they're not efficient enough for certain high performance areas, just don't use them there. I don't see why, for instance, they can't be used along with more efficient implementations, for instance in areas where parallelism and memory locality are not that important (for instance, for a menu or to hold settings).
I feel that banning them altogether may lead people to implement stuff from scratch even when they don't need it, creating another possible source of bugs and vulnerabilities.
Sure but the argument against STL wasn't performance but rather that it can allocate at all.
So optimal unordered map implementations, like Google's absl::flat_hash_map or Facebook's F14 would be similarly banned as they internally allocate.
I'd definitely recommend those libraries over std::unordered_map for sure, but it's not like std::unordered_map is unusably slow or broken, either. It's fairly comparably to Java's HashMap that everyone uses without thinking about it.
> As you have a budget of 6ms per frame for 165 FPS, and you often need to iterate over many thousands of entities in each frame, std::unordered_map for one is a non-starter.
that's when you use one of the two hundred alternative implementations which keeps the same API but offer different performance compromises & tradeoffs :
If you prototyped with std::unordered_map you'll likely just have to change a couple types and add the relevant includes here and there, rerun your benchmarks, and tada.
I was also disappointed by that post. I'm definitely not a fan of modern C++ but I wish they would have explained their reasoning in more detail. How is this different from the other C++ subsets that they list in the references?
> Don't use anything from STL that allocates memory, unless you don't care about memory management.
The important part is "if you don't care about memory management".
In language benchmarks, C++ is often portrayed as less efficient/slower than C. C++ is also commonly shunned when it comes to embedded software, especially on low performance chips. It doesn't have to be! C++ is (almost) a superset of C, and generally, they use the same compiler backend, so your C code should compile on a C++ compiler and generate the same binary.
Now that you know you can write C++ running as efficiently as C, you can start to carefully add features that will keep the spirit and efficiently of C while making your life easier, or even improve performance. For example you may want to replace macros with templates, use proper objects instead of doing like stdio does with FILE*, use namespaces, etc...
That's exactly what Orthodox C++ is about. It is C++ for those who want to write C. There is absolutely nothing wrong with STL containers and smart pointers and all the fancy stuff that make up modern C++, there is also nothing wrong with using languages that have heavy runtimes and garbage collectors, it is just not the use case Orthodox C++ is addressing.
I think that's the good thing about the mess that is C++11 and beyond. You can pick what you want. You can stay low level and know exactly the memory layout of your program. Or you can choose not to have a single raw pointer and let it manage the memory for you.
Note: There are still a good reasons to use C over C++. A big one is that linkage is a lot simpler and more compatible in C. C++ compilers do name mangling to support things like namespaces and polymorphism and require the linker to understand their particular conventions, and you may need the right libstdc++ for your target. You also need to be aware of static initialization.
That entire writeup is a scathing indictment of C++'s template system. It just gets progressively more horrifying and less understandable the further you read, until you're left with a mess of angle brackets, scope resolution operators and a pounding headache.
I think it goes to show that the combination of C++ giving the developer a lot of flexibility in how to approach a problem, as well as being unopinionated, leaves room for stuff like this to be built.
There are a lot of decent ways to use templates; that link is definitely not one of them.
I think that about sums up my experience with boost. I've found boost to be hit and miss in terms of elegance. One of the things I've liked about the standards track is how they've cleaned up many of the same boost concepts that ultimately ended up in std.
All of that is so that most user code doesn’t have to worry about corner cases. Libraries (of all languages) are full of weird stuff so that users can blithely use them without thinking about it.
I should say, after a quick glance most of the stuff is only adding greater typefullness and type saftey, which IMHO is good thing, considering the total type anarchy found in most large c projects.
When some projects/domains even now prefer C to C++, aren't they trading off safety for vague expectation of greater execution speed?
I find the Arduino C++ "dialect" being quite orthodox. Maybe a bit too rigorous, since it doesn't use the STL (which is mostly quite fine). I personally don't like C++ code which exploit all Turing completeness of the templating language. Code using such libraries can get a horror to debug on cryptic compiler error messages.
GCC isn't very clever about generating small binaries for embedded targets. Combining this with the code bloat from templates and heap allocator that must then also be linked into the program will make useful programs a tight squeeze in limited microcontroller program memory at the best of times. Dynamic memory management in long running programs with a small heap also invites fragmentation and spurious allocation failures.
I'm sorry, but the STL, especially the Stepanov designed parts are brilliant pieces of software. Written in a style unlike anything else. Generic programming is often misunderstood and that's a damn shame.
In that case, the impetus was to dress up having failed to implement templates, exceptions, and other "new" language features, typically as a result of budget cuts.
In this case, it appears to be, rather, to avoid learning anything new.
Make no mistake: code written to this, or any old-school subset, is Bad Code. Features are not added to C++ on a whim. They are added because they make programming in new C++ a better experience than old C++. New C++ code is faster, safer, smaller, less bug-prone, and more fun than old C++. C++20 is much more fun than C++17, which is better than C++14, which filled out lots of features from C++11.
Do not trust anyone suggesting any virtue in more C-like C++ code, or in actual C code. We left those behind for reasons.
I completely agree. The fact that the first example is using printf just shows that the best it can offer is some things we already know (template metaprogramming sucks etc.).
C++20 has std::format which is insanely more powerful, safe and performant than printf. To say that you should only be using the non-allocating functions is also bad advice. The whole thing reeks of someone who didn't bother to measure where he was actually spending time.
I didn't read the whole thing but I'm expecting that hes bashing exceptions too, even though they don't cost anything other than code size unless you actually throw. And it's perfectly fine to turn them off, if you're fine with abort.
Really, the only thing I can think of that I really dislike about both C and C++ is that backtraces should have been a first class citizens since the early 2000s. I think that's a huge reason why people pick up other languages more quickly too.
Go can be understood as an improved C that keeps much of C's simplicity but adds small, powerful features like interfaces and channels and garbage collection
Go puts essential C idioms directly into the language (pointer/length is formalized as slices, packages are part of the language instead of just being naming convention, etc.)
The longer I used C++, the more I despised it. I used C++ for 11 years and I literally hate the language. But C has always remained a pleasure, and Go is a continuation/modernization/enhancement of that
Go is mature, stable, widely-used, well-supported, well-understood, etc., so it's fully mainstream
Do you have any thoughts on Zig? It's nowhere near stable yet, but it seems like the a promising C replacement, in terms of maintaining simplicity, strict memory control, etc. (speaking as someone who's only worked a few months in C, as a hobby)
I’ve heard that Go code can make use of libraries written in C++, but that you can’t write libraries in Go and use them from C++. Is that true?
If so, it’s a significant impediment to introducing Go into a team that’s currently using C++ - any Go code would be isolated from the rest of the codebase.
Like the OP approved subset of C++, the design of Go is anachronistic on purpose, and makes a point to ignore most of what we have learned about programming language design since the 1990s.
Go goes further and puts the blame on its users, who are “not capable of understanding a brilliant language […] the language that we give them has to be easy for them to understand and easy to adopt”[0].
This stands in a stark contrast to one of C++'s design principles, namely that the language shouldn't “force people to use a specific programming style”[1]. C++ not being opinionated is not aesthetically pleasing, and at times confusing. But that wide acceptance to new ideas is exactly what allowed those ideas to graduate from niche languages to the mainstream.
Go's austerity is an acknowledgement that C's austerity is a net win (as long as you fix some of C's well-understood deficiencies; e.g., you're better off with slices and maps built directly into the language)
When a language ignores a problem in the name of simplicity, every application now needs to choose some idiosyncratic way to solve the same problem without help. Mastering a larger toolbox takes work but it pays off.
> every application now needs to solve the same problem
Some applications do. Other applications, for which the problem is irrelevant, can now ignore it rather than having the complexity of a solution baked into their foundations.
I see you've revised your comment from 3 days ago, where you made the more sweeping statement that "the longer you use C++, the more you despise it".
My experience is that C++ is complex, but it gives you power. I think whether or not you like the complexity of C++ depends very much upon whether or not you _need_ the power that comes with it. If you do not need the power, I can understand your argument that a simpler language is preferable.
I don't want to outright dismiss this piece as "argument bait", but it is essentially just a bunch of assertions on what other people should do with virtually no rationale and a bunch of shade thrown around deliberately. Sure, we all like to have a good argument, and C++ is an easy target, but this seems wholly unproductive from the get-go. If there was any intent of convincing others then this isn't how to do it.
I would also like to know who or what the "Orthodox C++ committee" is. A search only gets you this exact gist. Is the author himself the committee? That really doesn't put this piece into a better light.
I wonder if it is satire, but then again it doesn't seem particularly too out-there, and "orthodox" reminds me of Russian culture, largely due to this: https://en.wikipedia.org/wiki/File_manager#Orthodox_file_man...
What is Russian about that?
Russian here. Guys at my company still use Far (https://en.wikipedia.org/wiki/Far_Manager) as their primary file manager, and I'm not aware of any people other than us Russians who are still using Norton-like file managers in their day-to-day work.
I know lots of people here in Sweden that do
Interesting idea, pity about the name. There's a vaguely similar effort with a similarly poor choice of name, Clean C, which is guaranteed to compile as both C and C++ with the same behaviour (provided there's no undefined behaviour or platform-specific behaviour). [0][1]
> Exception handling is the only C++ language feature which requires significant support from a complex runtime system, and it's the only C++ feature that has a runtime cost even if you don't use it
I don't think that's true. I believe that if you enable RTTI you pay for it even if you don't use it, which is why it's banned in the LLVM codebase. [2]
> Don't use C++ runtime wrapper for C runtime includes (<cstdio>, <cmath>, etc.), use C runtime instead (<stdio.h>, <math.h>, etc.)
I believe this is still supported, but is officially deprecated, for what that's worth. [3]
[0] http://www.qnx.com/developers/docs/qnxcar2/index.jsp?topic=%...
[1] https://stackoverflow.com/a/9695020/
[2] https://llvm.org/docs/CodingStandards.html#do-not-use-rtti-o...
[3] https://stackoverflow.com/a/13643019/
edit Correction: It's called Clean C not Clean C++
This is somewhat exaggerated, especially because while there are several poorly designed areas in C++ and the STL, it's not all that bad and it can lead to safer, less fragile code than the C equivalent with less effort (but arguably, more skill is required in order to learn and proficiently use C++). It can even be faster than C in the hands of a skilled developer that knows where and how use the right features.
It doesn't hurt that the STL is extremely optimized, and often provides you tools that accomplish what you cared about without lots of the gotchas and pitfalls you're almost certainly doomed to fall for if you're not aware of their existence. Almost every single C project I've seen (including some of mine, I've to admit) end up involuntarily reimplementing some basic algorithm or container, and most often than not it ends up being less efficient, or unnecessarily using the heap.
One thing I think most people largely misunderstood is exceptions; they're actually totally fine if used in _exceptional_ circumstances, as if they were some kind of softer assertion, when you expect some kind of contract to be always true, but calling abort is just too unreasonable. This is basically the same approach Rust and Go use with panic(), and in both languages this facility by default causes the runtime to unwind the stack, as with C++ exceptions.
I think lots of people have been turned off by certain bad designs in older C++ revisions, where exceptions where indiscriminately used for error handing. They were a nightmare to use right, and the lack of helpers like unique_ptr made exception safety very hard.
Modern practices, an arguably better language after C++11 and guidelines/libraries such as the GSL vastly help in mitigating most of these issues, as long as people actually stick to them (i.e., no C style code without RAII).
I may also argue that the whole "C++ exceptions are expensive" argument is 100% moot in 2020, I've been running code on microcontrollers with half a megabyte of ram with them turned on and they add an almost insignificant amount of overhead; they only cause a small amount of binary bloat, but that's nothing compared to the size of certain modern libraries (and storage is cheap).
Very dubious stuff here. For example:
> and it's the only C++ feature that has a runtime cost even if you don't use it – sometimes as additional hidden code at every object construction, destruction, and try block entry/exit, and always by limiting what the compiler's optimizer can do, often quite significantly.
C++ doesn't do "hidden code" - "if you don't use a feature you don't pay for it" is one of the bedrock principles of the language.
That’s just marketing. See: https://godbolt.org/z/q9zEj4
With exceptions turned on there’s a hidden call to _Unwind_Resume. Additionally, the destructor has to be duplicated.
"you don't pay for it" doesn't mean that no code gets generated for exceptions in this case, but that no code gets executed unless an exception gets thrown, and your example does not disprove that.
Generated code has a cost in terms of size, of course, and in terms of performance via the addition of branching, branch mis-prediction penalty and decreased cache locality. This function invocation will also of course flush the pipeline and harm inlining. On a hot path this could be non-trivial.
While true it's also fewer branches than manual error handling via return values.
So the only "fast" option is to just not handle errors at all. Which is a terrible recommendation, of course.
That not the point, though. The point is that the cost is invisible with exceptions. For example, there could perhaps be an alternate version of C++ where every function is required to declare whether it could throw an exception (and a very smart compiler proves where it's possible), or that -fno-except flag is inverted so you have to opt in to exceptions.
you still have branches with noexcept, the difference is that they call std::terminate in case of stack unwinding.
I don't agree with the article but this is actually true, exceptions and RTTI are the two exceptions to the zero overhead principle. if you don't use -fno-exceptions (or mark the function as noexcept), every function will generate additional instructions to deal with potential exceptions happening
https://en.cppreference.com/w/cpp/language/Zero-overhead_pri...
This is false. Most code emitted by modern compilers incurs no overhead for exceptions except for when they are actually thrown. This is a historical rule that is no longer true.
https://stackoverflow.com/a/13836329/510036
I don't think it really contradicts what I said, I didn't say extra instructions were executed on each function call, just generated. I guess it depends on how you define overhead, but if you compile a program that does not use exceptions with exceptions on, it will run slower if you benchmark it. That being said, I do use exceptions and think they're obviously worth it, but there is an overhead to them, even if it's not as simple as "x additional instructions are executed"
That would be one strange benchmark then. It would have to be benchmarking something other than the code in question.
Simple analogy: let's say you have a program that has 10 functions in it. You write a benchmark that exercises one of these and never (no, not once, never) calls the other functions. The other 9 are truly dead code. Would you expect the benchmark result to be different between that and a program that only included the one function being benchmarked?
yes if the generated code causes e.g. more cache misses and branch mispredictions, which additional instructions inbetween the executed ones will
Did you benchmark with layout randomization?
There are instances where I can get current GCC to generate less instructions as part of the normal control flow when adding noexcept declarations. This is quite surprising given the common claim that exceptions shouldn't affect that. Unfortunately, I think I lost the example where I could reproduce it in the Compiler Explorer.
How convenient
I reproduced it:
https://godbolt.org/z/nzof4s
test() with exceptions enabled is two instructions longer in the normal code path.
The issue there is interaction between exceptions and deterministic destructors. In the end it means that there has to be some hidden code for every stack allocated object whose destruction could be skipped over by exception handling mechanism. There are various way to implement that, but all of them involves something that is "hidden code" and also the mythical "overhead for feature you don't use".
Another giant issue with C++'s "if you don't use a feature you don't pay for it" and interactions of various features with each other is that features you do use tend to be horribly expensive. It seems to me that authors of C++ tend to use some antiquated model of what "runtime cost" means which totally ignores effects of cache locality and code size.
I'm not a C++ developer, but AFAIK the fact that C++ is not 100% "you don't pay for what you don't use" is commonly acknowledged. Exceptions are an example of that.
There are ideas/proposals how to fix that: https://www.youtube.com/watch?v=ARYP83yNAWk
This is rather awful.
The concept isn't terrible necessarily, but the execution and condescension sure is. And some of the rules are just pointlessly argumentative. Such as refusing to use <cstdio> and instead using <stdio.h> - those are officially documented to be different things with different behaviors. Blanket banning one of them doesn't improve simplicity, especially if you're blanket banning the "wrong" set of headers. Unless the goal is to also ban namespaces, but that's not called out as such (and namespaces are one of the least contentions C++ features - everyone seems to like them well enough).
Or similarly:
> Don't use anything from STL that allocates memory, unless you don't care about memory management.
So don't use std::vector? Or std::unordered_map? Or std::string? These are all perfectly fine classes, banning them makes no sense at all. Maybe the goal was to ban "hidden cost" classes like std::function, but rolling your own 'C-style' is a hell of a lot more complex & error prone (raise your hand if you've seen a C pointer callback that forgot to have a void* context or a mismanagement of said void* context...)
I think any attempt at defining a strict subset of a language is going to face some pushback like yours, but I do agree that while I agree with most choices this one seems strange. Rolling your own containers is boring and error-prone and won't have all the features of the STL.
Also, while iostream is a bit of a mess, having it be strongly typed means that it's, in my opinion, well worth using over cstdio.
One thing I was surprised not to see mentioned here is multiple inheritance. In my experience it gets really messy really fast.
Also I really recommend reading the linked archived Boost discussion about a geometry library, it's quite funny. It starts with:
And after a couple of pages of refinements ends up with:
But hey, it can compute distances in non-cartesian hyperspaces so that's pretty cool.
And it doesn't depend on whatever the heck "mypoint" is.
> One thing I was surprised not to see mentioned here is multiple inheritance.
There's nuance to banning that since implementating multiple interfaces is technically multiple inheritance in C++. So I think I'd agree with you but with an exception for pure-virtual classes.
> Also, while iostream is a bit of a mess, having it be strongly typed means that it's, in my opinion, well worth using over cstdio.
Orthodox C++ would ban this but you can have both printf-style with type safety with https://github.com/fmtlib/fmt
Which inspired C++20's std::format: https://en.cppreference.com/w/cpp/utility/format
Which is also banned by Orthodox C++
> So don't use std::vector? Or std::unordered_map? Or std::string? These are all perfectly fine classes, banning them makes no sense at all.
Orthodox C++ bans exceptions, so if an allocation failure occurs within one of those classes, the only thing your program can do is abort.
That’s fine for some programs - Google’s C++ style guide takes this approach, as does LLVM. But for other programs, where aborting on allocation failure is not acceptable, there’s no way to use those classes without using exceptions.
> Google’s C++ style guide takes this [ no exceptions; libraries abort ] approach
Note the style guide says they’d rather use exceptions but had too much legacy code (and now must have a couple of orders of magnitude of it by now): “On their face, the benefits of using exceptions outweigh the costs, especially in new projects. However, for existing code, the introduction of exceptions has implications on all dependent code. … Because most existing C++ code at Google is not prepared to deal with exceptions [don’t use them]”
Google’s non use of exceptions is often used as justification for not using them either, by people who haven’t actually read Google’s style guide.
Also: few of us have Google sized problems.
The only "real" issue with exceptions is binary size, and binary size does seem to be a rather specific goal of Orthodox C++ even though it's not explicit about that. But on any desktop or server deployment binary size is also so incredibly irrelevant it's not a good justification. On mobile maybe, but it's questionable. For embedded definitely, though.
https://twitter.com/timsweeneyepic/status/122307740466037145...
While interesting I guess that sounds like a straightforward compiler bug not a problem with the feature overall. Unless there's some missing nuance why 'noexcept' was apparently being ignored which would need further investigation.
Exceptions have hidden costs that are hard to spot. For example, generated assembly for C++ code with or without noexcept can be quite different. Part of the reason is that DFA based optimizations get overwhelmed by the number of implied execution paths when exceptions may be thrown. So the optimizer essentially is forced give up more often, resulting in slower code for the normal case. This is orthogonal to any binary size issues from linking the runtime support for exceptions.
Of course, but that's comparing error handling code with non-error handling code.
The comparison you need to make is instead exceptions vs. return values. Unless you're going to argue all errors should abort by default or similar.
I do wish noexcept had better support, or was even the default (C++ and wrong defaults, a tale as old as time). But it's disingenuous to compare exceptions against nothingness.
No, this holds in a fair comparison with manual error checking. In the presence of possible exceptions, the compiler has to make the assumption that any nontrivial expression can throw, especially if its implementation is opaque at the callsite. With explicit error handling, this is reduced to those checkpoints that are inserted manually, which are generally fewer.
If you call a function within the condition of an if statement, the compiler needs to conservatively assume that the execution flow from there is either to the rest of the conditionsl or to either one of the catch blocks or the functions' stack unwinding. If there cannot be an exception, the execution flow has none of these additional branches that bog down the DFA.
> But for other programs, where aborting on allocation failure is not acceptable
Sure but that seems like the edge case not the general case? Nearly any desktop or mobile app or game won't have any such constraints, for example. And you've also got overcommit to deal with on those platforms, so it's not like your allocation failure will actually happen at malloc time either.
So outside of embedded, which tends to have a variety of constraints, what even pretends to handle allocation failures across the entire program?
You can supply your own allocator for the STL
Not really. You can supply alloctors to contai ers, but it becomes iffy for strings. Custom allocator support for std::function was removed in C++17. And there is no way that I am aware of to use custom allocators for the std::shared_ptr refcounters.
There is, allocate_shared takes an allocator: https://en.cppreference.com/w/cpp/memory/shared_ptr/allocate...
I'm not entirely sure why you'd want a custom allocator for shared_ptr, though.
It is quite common in certain contexts to allow the user of a library to provide their own custom allocator. And this would be kind of pointless if some allocations circumvent that, wouldn't it?
The deeper rationale is that some systems like to assign fixed memory pools to subsystems. These may be reset or destroyed at certain points in the applications life cycle and the general expectation is that this frees all memory used by said subsystem. If you still want to use the STL in that context, you need to provide custom allocators for everything that potentially allocates.
But by doing that you'd be breaking the safety (and functionality) of shared_ptr as you'd end up with potentially dangling references (similarly weak pointers are broken). Which then raises the question of why are you using shared_ptr in the first place? You could use a custom allocator just for auditing that all outstanding references were released, I suppose, but actually releasing the memory anyway is just adding bugs. Which can be a fair risk to take, it just seems like shared_ptr is then not the thing you want to indicate appropriate "running with scissors" behavior.
As long as you can guarantee that no shared_ptr lives longer than its backing memory pool, it's perfectly fine. The same goes for every other kind of pointer wrapper you could dream up.
At least you can write a replacement for shared_ptr. The same is not true for std::function. This is the only named type a capturing lambda converts to according to the language standard and it's an STL type with complex behind the scenes behavior.
You can write your own std::function, too, nor is it the only STL type that can take a capturing lambda (std::packaged_task for example).
A capturing lambda is just a class with an operator(). It's complicated to do what std::function does, but fully possible.
In fact, custom std::function replacements have better lambda support than std::function itself. Such as unique_function in https://github.com/Naios/function2 which can handle non-copyable lambdas.
> So outside of embedded, which tends to have a variety of constraints, what even pretends to handle allocation failures across the entire program?
Don't embedded platforms mostly use something other than STL anyway?
What do you call "embedded platforms" ? Car dashboards in 2020 run Qt on QNX for instance.
Try not to read condescension into things so easily. Eg I'm pretty sure that the line you quote,
> Don't use anything from STL that allocates memory, unless you don't care about memory management.
is not meant condescending but literally. Ergo, if you don't care about memory management (which is fine), then feel free to use lots of STL stuff.
Case in point: for lots of embedded software, memory matters a lot and it's important that it's obvious to the reader of the code what memory gets allocated where. The STL classes you quote make this harder to see. But if you have lots of RAM anyway (ie "you don't care about memory management") then this does not matter much.
A key goal of this list appears to be, I quote, "Projects written in Orthodox C++ subset will be more acceptable by other C++ projects". This particular guideline makes it more likely for your code to be deemed acceptable by embedded programmers so it seems to fit. I don't think it was intended to be condescending in any way. I think the same holds for the other points.
It could've definitely been written a bit clearer though.
> Try not to read condescension into things so easily.
If they didn't intend condescension, they should not have put a comic displaying exactly that (and with essentially no further relevance) into the middle of the article.
>Ergo, if you don't care about memory management (which is fine), then feel free to use lots of STL stuff.
This isn't really true. All of these containers take custom allocators, which can be tailored to your use case (eg: a stack based allocator).
> is not meant condescending but literally. Ergo, if you don't care about memory management (which is fine), then feel free to use lots of STL stuff.
I don't see it that way. Plenty of people care about memory management and also see the STL as a fine tool to use. Little is gained from a memory management perspective by avoiding e.g. std::vector. Whether condescension is intended or not, this definitely comes across as talking down to me, and worse, it is incorrect.
I don't think "more acceptable by other C++ projects" works out well. If I don't appreciate how RAII and exceptions clarify normal control flow, I won't write code that avoids leaking when its dependencies throw. This poisons any project it spreads to. Google famously had to abandon any hope of using exceptions because of a critical mass of flawed legacy code.
While I can't make a general comment, for some applications such as videogame engine development, std::unordered_map for example is not a perfectly fine class. Its linked-list-based memory layout means it is extremely CPU cache-unfriendly, making it incur an inordinately high amount of cache misses. [0] As you have a budget of 6ms per frame for 165 FPS, and you often need to iterate over many thousands of entities in each frame, std::unordered_map for one is a non-starter.
In general, when discussing which language features to use, I would keep in mind that something can work well for an application that is fairly straightforward and has modest performance requirements, while being a complete no-go for a different use case. I'm not saying we should throw C++ in the bin, but I also think many people have their reasons for saying some C++ features are more trouble than they're worth.
[0]: https://stackoverflow.com/questions/42588264/why-is-stdunord...
(Edited to remove the bit about std::vector, as this argument does not apply to it.)
You’re right about std::unordered_map but std::vector storage is guaranteed to be continuous[1].
[1] https://en.cppreference.com/w/cpp/container/vector
You're right! I realised my mistake and edited my post in the meantime.
unordered_map is usually fine if you keep the loading below 0.5. It is also fine if you don't care, as for example during program start-up.
BTW, vector is contiguous, not continuous.
Still, it's not ok to just blanket ban them. If they're not efficient enough for certain high performance areas, just don't use them there. I don't see why, for instance, they can't be used along with more efficient implementations, for instance in areas where parallelism and memory locality are not that important (for instance, for a menu or to hold settings).
I feel that banning them altogether may lead people to implement stuff from scratch even when they don't need it, creating another possible source of bugs and vulnerabilities.
Sure but the argument against STL wasn't performance but rather that it can allocate at all.
So optimal unordered map implementations, like Google's absl::flat_hash_map or Facebook's F14 would be similarly banned as they internally allocate.
I'd definitely recommend those libraries over std::unordered_map for sure, but it's not like std::unordered_map is unusably slow or broken, either. It's fairly comparably to Java's HashMap that everyone uses without thinking about it.
You can use pmr and then do no allocation in std containers.
> As you have a budget of 6ms per frame for 165 FPS, and you often need to iterate over many thousands of entities in each frame, std::unordered_map for one is a non-starter.
that's when you use one of the two hundred alternative implementations which keeps the same API but offer different performance compromises & tradeoffs :
https://martin.ankerl.com/2019/04/01/hashmap-benchmarks-02-0...
If you prototyped with std::unordered_map you'll likely just have to change a couple types and add the relevant includes here and there, rerun your benchmarks, and tada.
I was also disappointed by that post. I'm definitely not a fan of modern C++ but I wish they would have explained their reasoning in more detail. How is this different from the other C++ subsets that they list in the references?
> Don't use anything from STL that allocates memory, unless you don't care about memory management.
The important part is "if you don't care about memory management".
In language benchmarks, C++ is often portrayed as less efficient/slower than C. C++ is also commonly shunned when it comes to embedded software, especially on low performance chips. It doesn't have to be! C++ is (almost) a superset of C, and generally, they use the same compiler backend, so your C code should compile on a C++ compiler and generate the same binary.
Now that you know you can write C++ running as efficiently as C, you can start to carefully add features that will keep the spirit and efficiently of C while making your life easier, or even improve performance. For example you may want to replace macros with templates, use proper objects instead of doing like stdio does with FILE*, use namespaces, etc...
That's exactly what Orthodox C++ is about. It is C++ for those who want to write C. There is absolutely nothing wrong with STL containers and smart pointers and all the fancy stuff that make up modern C++, there is also nothing wrong with using languages that have heavy runtimes and garbage collectors, it is just not the use case Orthodox C++ is addressing.
I think that's the good thing about the mess that is C++11 and beyond. You can pick what you want. You can stay low level and know exactly the memory layout of your program. Or you can choose not to have a single raw pointer and let it manage the memory for you.
Note: There are still a good reasons to use C over C++. A big one is that linkage is a lot simpler and more compatible in C. C++ compilers do name mangling to support things like namespaces and polymorphism and require the linker to understand their particular conventions, and you may need the right libstdc++ for your target. You also need to be aware of static initialization.
> In language benchmarks, C++ is often portrayed as less efficient/slower than C.
Source? Even in the somewhat silly computer language benchmark game C++ is fairly consistently ranked faster than C: https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
I thought this was a good point:
> Don't do this: http://archive.md/2014.04.28-125041/http://www.boost.org/doc...
Maybe he is looking to use golang?
That's actually a very good example of what's wrong with C++'s templates and also shows how we got there.
That entire writeup is a scathing indictment of C++'s template system. It just gets progressively more horrifying and less understandable the further you read, until you're left with a mess of angle brackets, scope resolution operators and a pounding headache.
I think it goes to show that the combination of C++ giving the developer a lot of flexibility in how to approach a problem, as well as being unopinionated, leaves room for stuff like this to be built.
There are a lot of decent ways to use templates; that link is definitely not one of them.
True. It must be said, however, that the Boost libraries are built using a peer-review system. So this is not some "random" C++ code.
It's a peer review in an echo chamber.
What Boost project churns out is absolutely terrifying in its lack of rudimentary elegance and good programming taste.
I think that about sums up my experience with boost. I've found boost to be hit and miss in terms of elegance. One of the things I've liked about the standards track is how they've cleaned up many of the same boost concepts that ultimately ended up in std.
All of that is so that most user code doesn’t have to worry about corner cases. Libraries (of all languages) are full of weird stuff so that users can blithely use them without thinking about it.
Agreed, but it should be possible to read and understand. This is solidly neither.
The sources to glibc are no clearer, for the same reasons.
I should say, after a quick glance most of the stuff is only adding greater typefullness and type saftey, which IMHO is good thing, considering the total type anarchy found in most large c projects.
When some projects/domains even now prefer C to C++, aren't they trading off safety for vague expectation of greater execution speed?
So, how would you write it instead ?
I find the Arduino C++ "dialect" being quite orthodox. Maybe a bit too rigorous, since it doesn't use the STL (which is mostly quite fine). I personally don't like C++ code which exploit all Turing completeness of the templating language. Code using such libraries can get a horror to debug on cryptic compiler error messages.
GCC isn't very clever about generating small binaries for embedded targets. Combining this with the code bloat from templates and heap allocator that must then also be linked into the program will make useful programs a tight squeeze in limited microcontroller program memory at the best of times. Dynamic memory management in long running programs with a small heap also invites fragmentation and spurious allocation failures.
I'm sorry, but the STL, especially the Stepanov designed parts are brilliant pieces of software. Written in a style unlike anything else. Generic programming is often misunderstood and that's a damn shame.
Orthodox C++ echoes the old "Embedded C++" scam.
In that case, the impetus was to dress up having failed to implement templates, exceptions, and other "new" language features, typically as a result of budget cuts.
In this case, it appears to be, rather, to avoid learning anything new.
Make no mistake: code written to this, or any old-school subset, is Bad Code. Features are not added to C++ on a whim. They are added because they make programming in new C++ a better experience than old C++. New C++ code is faster, safer, smaller, less bug-prone, and more fun than old C++. C++20 is much more fun than C++17, which is better than C++14, which filled out lots of features from C++11.
Do not trust anyone suggesting any virtue in more C-like C++ code, or in actual C code. We left those behind for reasons.
C++23 will be better than C++20.
I completely agree. The fact that the first example is using printf just shows that the best it can offer is some things we already know (template metaprogramming sucks etc.).
C++20 has std::format which is insanely more powerful, safe and performant than printf. To say that you should only be using the non-allocating functions is also bad advice. The whole thing reeks of someone who didn't bother to measure where he was actually spending time.
I didn't read the whole thing but I'm expecting that hes bashing exceptions too, even though they don't cost anything other than code size unless you actually throw. And it's perfectly fine to turn them off, if you're fine with abort.
Really, the only thing I can think of that I really dislike about both C and C++ is that backtraces should have been a first class citizens since the early 2000s. I think that's a huge reason why people pick up other languages more quickly too.
Reminds me of JavaScript: The Good Parts by Crockford:
https://www.youtube.com/watch?v=hQVTIJBZook
Strip away an older language's bad features. You end up with a sub-language with the advantage of portability and better maintainability.
The problem can be deciding (and agreeing on) which language features are good and which are bad.
Except in this case they’re stripping away the new features.
Came here to mention this. I had to learn C++ over the past few months, and have been looking for an analog to Crockford’s book.
i don’t get it. Why not spend ing the time and energy developing better language standards and then driving adoption of new standards?
Terry Davis did it first haha. https://en.m.wikipedia.org/wiki/TempleOS
I just use Go (golang) instead, whenever I can
Go can be understood as an improved C that keeps much of C's simplicity but adds small, powerful features like interfaces and channels and garbage collection
Go fixes C's well-understood flaws (declaration resembling use, unintuitive operator precedence, unrestricted address math, silent casting, zero-terminated strings, etc.)
Go puts essential C idioms directly into the language (pointer/length is formalized as slices, packages are part of the language instead of just being naming convention, etc.)
The longer I used C++, the more I despised it. I used C++ for 11 years and I literally hate the language. But C has always remained a pleasure, and Go is a continuation/modernization/enhancement of that
Go is mature, stable, widely-used, well-supported, well-understood, etc., so it's fully mainstream
If you like C, you will love Go
Do you have any thoughts on Zig? It's nowhere near stable yet, but it seems like the a promising C replacement, in terms of maintaining simplicity, strict memory control, etc. (speaking as someone who's only worked a few months in C, as a hobby)
Go is 13 years old, and it has a huge developer ecosystem
Being practical, I'm not interested in using an experimental language that could be dead/unused/unsupported in 5 years
But go is Google's project and it will be cancelled.
You misspelled Rust several times in your post here.
Rust is too complex. It's not simple like C
I’ve heard that Go code can make use of libraries written in C++, but that you can’t write libraries in Go and use them from C++. Is that true?
If so, it’s a significant impediment to introducing Go into a team that’s currently using C++ - any Go code would be isolated from the rest of the codebase.
Yes, if you're on a team developing a program in C++, everyone should use C++
My condolences
At least we have Lua, which we can call freely from our C++ application.
Like the OP approved subset of C++, the design of Go is anachronistic on purpose, and makes a point to ignore most of what we have learned about programming language design since the 1990s.
Go goes further and puts the blame on its users, who are “not capable of understanding a brilliant language […] the language that we give them has to be easy for them to understand and easy to adopt”[0].
This stands in a stark contrast to one of C++'s design principles, namely that the language shouldn't “force people to use a specific programming style”[1]. C++ not being opinionated is not aesthetically pleasing, and at times confusing. But that wide acceptance to new ideas is exactly what allowed those ideas to graduate from niche languages to the mainstream.
--
[0] Rob Pike at Lang.NEXT 2014. 20:52 @ https://channel9.msdn.com/Events/Lang-NEXT/Lang-NEXT-2014/Fr...
[1] Bjarne Stroustrup, pp.3 @ https://stroustrup.com/hopl-almost-final.pdf
Go and C (even moreso) are simple languages. Some people understand the reason for (and appreciate) that simplicity
Other people prefer profound language complexity; no comment
I don't think the motivation behind Go's and C's austerity is the same for both language, so I wouldn't lump them together in the same argument.
Go's austerity is an acknowledgement that C's austerity is a net win (as long as you fix some of C's well-understood deficiencies; e.g., you're better off with slices and maps built directly into the language)
When a language ignores a problem in the name of simplicity, every application now needs to choose some idiosyncratic way to solve the same problem without help. Mastering a larger toolbox takes work but it pays off.
> every application now needs to solve the same problem
Some applications do. Other applications, for which the problem is irrelevant, can now ignore it rather than having the complexity of a solution baked into their foundations.
I see you've revised your comment from 3 days ago, where you made the more sweeping statement that "the longer you use C++, the more you despise it".
My experience is that C++ is complex, but it gives you power. I think whether or not you like the complexity of C++ depends very much upon whether or not you _need_ the power that comes with it. If you do not need the power, I can understand your argument that a simpler language is preferable.
agree with you completely