Rendered at 15:03:56 GMT+0000 (Coordinated Universal Time) with Cloudflare Workers.
rfgplk 23 hours ago [-]
I've developed a style that I legitimately call Heterodox C++ (mainly due to the popularity of Orthodox C++), it is effectively a purely functional & metaprogramming heavy style of C++. Quite the opposite of this, not everyones cup of tea, and it won't fit into every codebase but it is incredibly powerful. The template metaprogramming C++ offers is the most powerful of any imperative language, and (subjective opinion) is second only to Lisp, but few people make use of it. With some of C++26 features you can almost even replicate most of Rusts safety features in pure C++ (via function tagging + reflection)
kartoffelsaft 22 hours ago [-]
> The template metaprogramming C++ offers is the most powerful of any imperative language
I'm curious what languages you're comparing to here. Feels like it's only slightly more expressive than pure generics, but I admittedly haven't done much template metaprogramming myself. How does it compare to, say, Zig's comptime?
lumrn 17 hours ago [-]
I’ll preface this by saying I mostly use C++ and have just basic experience in Zig, but as far as I understand comptime is much more procedural than C++ templates which are more declarative. With templates you get quite good pattern matching through the compiler’s machinery, for instance through template specialisation, while Zig, in my understanding, requires these to be handled manually in code. Personally, comptime feels like constexpr/consteval in C++ but with the ability to interact with the type system itself. The significant downside of C++ metaprogramming is that sometimes many features interact weakly and feel very much tacked on top of each other while Zig’s looks more cohesive. Perhaps someone with more Zig experience can weigh in.
dnautics 11 hours ago [-]
> The significant downside of C++ metaprogramming is that sometimes many features interact weakly and feel very much tacked on top of each other while Zig’s looks more cohesive.
spot on, in my experience. nothing in zig triggers my lite OCD.
flyingoat 12 hours ago [-]
Yuck. I'm sure your compile times suck and code is not readable (my subjective opinion as well). There's nothing worse than a ten-mile long lambda in C++. To each there own, however. C++ is four languages in one after all. :)
Templates are really amazing, once you learn the patterns there's basically a template version and a runtime version of things (policy types, etc.). All of that is great.
Legibility and understanding are in my opinion the most important aspect of any programming language, so Orthodox C++ is superior for maintainable code.
cyber_kinetist 22 hours ago [-]
The problem with metaprogramming-heavy C++ codebases is always compilation times and obtuse error messages...
Template metaprogramming is sometimes very useful to get around C++'s language restrictions, but I tend to use it sparingly.
fluoridation 15 hours ago [-]
I've been using C++ for about 20 years and I've yet to see these scary template error messages people always complain about. Yeah, they're long, but they contain all the information you need to fix the issue, which is damn near always at the deepest point in the call stack that you still control.
nly 22 hours ago [-]
> obtuse error messages
With concepts and constexpr-if and consteval it's increasingly less of a problem
undershirt 23 hours ago [-]
have you written about this anywhere, or hosting any examples?
rramadass 23 hours ago [-]
You might find Functional Programming in C++ by Ivan Cukic relevant.
rfgplk 23 hours ago [-]
Not yet, I might one day.
unnah 23 hours ago [-]
C++ template metaprogramming is in some ways more powerful than Common Lisp macros, because it works at the type level: you can generate new types and dispatch into separate implementations by type. In contrast, Common Lisp type declarations are not available at macro expansion time unless you implement a full source-to-source translator in macros.
aw1621107 23 hours ago [-]
Submitted a fair few times previously. HN's search turned up these submissions with some additional discussion:
Looks like the page was moved from a GitHub gist to a github.io page in October of last year.
rramadass 23 hours ago [-]
Yep, the article is a old one and not particularly well written. As somebody who has been using C++ from the early 90s and not particularly a fan of (all of) "Modern C++", there is not much information here.
badlibrarian 23 hours ago [-]
If you're in a market that requires using C++, many of these decisions are made for you by the platform above you, and you're screwed. Turn on RTTI, build a fort to deflect the random exceptions they'll throw at you, and may the gods allow you to recoup your R&D before some well-intentioned yokel in some media or game vertical changes everything and requires you to change everything.
On the other hand, if you control your own destiny and care about velocity and code quality, many of these choices eventually become self-evident.
If you are messing around with the latest and greatest esoteric C++ stuff in 2026, bless you, you beautiful nerd. But it may be time to start evaluating where you are in life, and how you got here. (And if you're on a C++ committee, I revoke those blessings.)
For those who remain: if you have a C++ code base yet somehow have enough time and energy to write opinionated blog posts, it's really hard to imagine why you think you'd have a better take on this than Google.
> build a fort to deflect the random exceptions they'll throw at you
Sounds like you hate exceptions, right? In which case why do you handle them at all? Just leave them all unhandled and suddenly every exception is a crash. Which is really no different from someone choosing to terminate. Which you have to worry about even without exceptions.
> if you have a C++ code base yet somehow have enough time and energy to write opinionated blog posts, it's really hard to imagine why you think you'd have a better take on this than Google.
"Given that Google's existing code is not exception-tolerant [...] Our advice against using exceptions is not predicated on philosophical or moral grounds, but practical ones. [...] Things would probably be different if we had to do it all over again from scratch."
patrick451 21 hours ago [-]
> Which is really no different from someone choosing to terminate.
If you std::abort(), you'll get a useful stack trace in the core dump. If you crash from an unhandled exception, you don't. That's a pretty huge difference and is one of the reasons exceptions suck.
dataflow 20 hours ago [-]
That's nice but it's certainly not guaranteed by anything, just something provided by your toolchain or platform. ("Core dumps" aren't even a thing in C++.)
If you're looking for implementation-specific guarantees then you could make that happen with exceptions too. I think on GCC replacing a function like __cxa_throw might be sufficient to let you capture a stack trace?
If you're looking for source-level-only guarantees then another option is to just replace your throw <expr> statements with one that attaches whatever extra info you want. You could literally script this to patch your external repos automatically too. Or heck, maybe you could even just define throw to be a macro that shoves your stack trace into some global variable before actually throwing.
StellarScience 13 hours ago [-]
Just FYI, finally in C++ you can add a top-level exception handler and call boost::stacktrace::from_current_exception (https://www.boost.org/releases/1.85.0/), and get a stack trace on exit as helpful as in Python or Java.
cpgxiii 18 hours ago [-]
> If you std::abort(), you'll get a useful stack trace in the core dump. If you crash from an unhandled exception, you don't. That's a pretty huge difference and is one of the reasons exceptions suck.
All of this is up to the implementation in practice. The codebases I work on generally follow the pattern that exceptions may be thrown but may not be caught*, and thus they practically serve as terminate. And we absolutely get stack traces in our core dumps (Linux, both GCC and clang), and basically all of the complex debugging I do starts with a coredump stacktrace.
* We follow this pattern for a few reasons, (1) it is generally safer for us to assume that libraries we consume (STL included) will behave as expected with exceptions enabled, (2) "don't catch exceptions" (or if you must, catch the as close-to-throw as possible) is a simple way to avoid exceptions-for-nonexceptional-cases control flow, and (3) most of the C++ codebase is exposed through bindings in Python, and propagating errors as exceptions is the only Python-friendly way to handle it.
jcelerier 7 hours ago [-]
> If you crash from an unhandled exception, you don't.
.. you absolutely get a stack trace from unhandled exception in c++, that starts where the exception is thrown? At least with clang and GCC, maybe MSVC isn't able to.
$ g++ foo.cpp -std=c++23 -g
$ ./a.out
terminate called after throwing an instance of 'std::runtime_error'
what(): boo
$ coredumpctl gdb
...
#7 0x00005555555551bb in bar () at foo.cpp:3
#8 0x00005555555551da in foo () at foo.cpp:4
#9 0x00005555555551e6 in main () at foo.cpp:5
it's a basic example, but it's how I've always done all my debugging in C++ since forever
throwaway173738 2 hours ago [-]
Sure, in a desktop program you do. In embedded exceptions are a scourge because they crash your program until someone can get back out there to power cycle it. At least with return-codes you can continue execution even if you failed to effect the change you wanted. If that was ancillary to the system’s core function then the system keeps running.
forrestthewoods 16 hours ago [-]
> Sounds like you hate exceptions, right? In which case why do you handle them at all?
One of the problems with exceptions is it’s utterly impossible to know if a given function call can return exceptions and if so what they are.
My code DOES want to handle errors. Exceptions are, imho, a very very poor way to report errors.
Python is the bloody worst because I never effing know what the hell any damn function can throw or return. It’s so so frustrating.
Error handling is a hard and unsolved problem. But I’ll take Rust results over exceptions 10,000% of the time. Not even a question.
fluoridation 14 hours ago [-]
Exceptions aren't meant to report errors, just in general. That's a misuse of them. Exceptions are meant to be thrown when a contract cannot be fulfilled. Yes, you're unable to know what exceptions a function may throw. That's the way it should be, because exceptions aren't supposed to be part of the function's contract.
For example, you're implementing an arithmetic operator and have reached an erroneous state, but the arithmetic type doesn't have an error value, the only way to communicate the error is by throwing. Another example: you've specified that a function must always succeed, but later on you find a case where the function cannot succeed. Instead of fixing all the possible call sites, throw an exception. All those callers could not have handled the error anyway, because they were coded under the assumption that no error would happen at that point. Throwing an exception and letting it unwind the stack way up (perhaps even all the way up to main()) is the sensible solution, because at that point you've reached a situation with no reasonable way for that code to handle.
Saying that you prefer Result over exceptions is like saying that you prefer strings to functions. They do different things. If you like Result, nothing prevents you from implementing a C++ equivalent.
dataflow 13 hours ago [-]
> Exceptions aren't meant to report errors, just in general. That's a misuse of them. Exceptions are meant to be thrown when a contract cannot be fulfilled. Yes, you're unable to know what exceptions a function may throw. That's the way it should be, because exceptions aren't supposed to be part of the function's contract.
I don't think these are true? What about std::vector::at(), std::optional::value(), etc.? And then there's std::system_error.
fluoridation 13 hours ago [-]
>std::vector::at(), std::optional::value()
Both functions must return T &. If the vector is not long enough, or the object is not set, then returning a T & is impossible. So we have a function that has already been called and which must return something valid, and cannot return something valid. The only two ways to resolve this contradiction is to throw, or to terminate.
(Well, you could also trigger undefined behavior like operator[]() and operator*(). No comment.)
>And then there's std::system_error.
And what am I supposed to conclude from the existence of a type?
dataflow 12 hours ago [-]
I think you missed my point. I was referring to the fact that some of these standard exceptions are very much a part of the contracts of their respective functions. In fact, that's their entire point. This directly contradicts what you wrote.
fluoridation 11 hours ago [-]
You're using "contract" in a different sense than I did. When I said "contract" I was referring to the required state of the program when the function is called and the guaranteed state of the program when the function returns. By definition an exception cannot be part of the contract in this sense, because a call that throws does not return. This narrower sense of contract is critical, because the entire point of exceptions is to enable alternate control path when it'd otherwise be impossible, such as in the examples I gave above with overloaded operators and code with evolving requirements.
dataflow 9 hours ago [-]
Okay, but if I may offer a suggestion: in the future, I would probably phrase what you said differently. I think it caused you trouble here not just with me but with other readers. Instead of "when the function cannot fulfill its contract", I would talk about "when returning would not fulfill a function's contract."
There are actually two reasons for this, not one:
- It violates standard terminology. The notion of a contract/postcondition/etc. is not specific to C++ or the particular implementation's mechanisms for exiting a function. It simply means a condition that must hold true after the execution of some piece of code. [1] The intent of this definition is to allow program composition: it enables one to reason about the greater program in terms of the sub-parts. Defining it to be anything else just throws people off, and rather misses the point and utility of the term.
- "Cannot" is actually too strong. A function might, in fact, be able to fulfill its contract, but still choose not to. An easy example is something like a constraint solver (SAT, chess, simulator, constexpr evaluator, or whatever). It's guaranteed to be able to find the solution eventually if it keeps going, but that's probably not always a good idea.
Now, going back to what you wrote here:
> Exceptions aren't meant to report errors, just in general. That's a misuse of them. Exceptions are meant to be thrown when a contract cannot be fulfilled.
I'm still not entirely sure I see what you mean by "report errors". How exactly have you seen people use exceptions to "report errors" that is not for the purpose of indicating that "a contract cannot be fulfilled"? The description makes it sound like using exceptions for the purpose of logging, but that would seem like a strawman... I have never seen anyone write throw instead of log. What are you referring to?
>How exactly have you seen people use exceptions to "report errors" that is not for the purpose of indicating that "a contract cannot be fulfilled"?
Reporting normal errors to the caller, as opposed to exceptional errors. The distinction between normal errors and exceptional errors is kind of nebulous, but it boils down to normal errors being those that the immediate caller would be interested in, and everything else is exceptional.
For example, you have a file system class that abstracts away different underlying hardware interfaces to present to the client code a virtual file system. Not only would it be impractical for the open() function to include as part of its interface every possible error condition of all the possible backends, it would be of no help to the caller. If you're manipulating an AbstractFileSystem class just to open a path and read data, what could you possibly do with a connection reset error, or with a file system structure corruption error? Do you see what I mean? They're errors happening at different levels of abstraction. Exceptions are meant to be used to communicate error conditions when your call stack looks like
often with a module boundary between high abstraction stack frames. You use them to pass errors directly between frames at the same level of abstraction, that are not in direct communication.
So to answer your question, exceptions are misused when they communicate relevant errors within the same level of abstraction, such as an open() function throwing a FileNotFoundException.
forrestthewoods 13 hours ago [-]
> The only two ways to resolve this contradiction is to throw, or to terminate.
Or they’re bad APIs that should be redesigned to be not bad.
They’re fallible functions. Don’t write fallible APIs that require exceptions to report errors! That’s bad API design!
forrestthewoods 13 hours ago [-]
> Throwing an exception and letting it unwind the stack way up (perhaps even all the way up to main()) is the sensible solution
No. I would never in a million years do this.
If the API is that a function is infallible and then I decide that it’s a fallible function then that’s a pretty major change and I’m just gonna have to update all the call sites to deal with a fallible return result.
Saying throw an exception and bubble up to main provides just about zero value. Might as well just call std::abort. Which is also something I would never do.
> Saying that you prefer Result over exceptions is like saying that you prefer strings to functions. They do different things.
So here’s the thing. In 20+ professional years as a C++ dev I have never ever once worked in a codebase where exceptions were used. Certainly never in first party code. Only when dealing with annoying thirdparty libraries that leveraged them.
I think your comment “contract can’t be fulfilled” is cheating. No. You’ve simply made a new contract and the new contract is that under certain cases an error is returned in the form of an exception.
dataflow 9 hours ago [-]
> Saying throw an exception and bubble up to main provides just about zero value. Might as well just call std::abort. Which is also something I would never do.
I'm sorry but this is where you're clearly wrong. The whole point is unwinding doesn't have to necessarily happen all the way to main(); there is a ton of value in doing this, and it is not at all equivalent to aborting. It lets someone in the call chain do something other than abort, or clean up stuff that they otherwise might not have a chance to. Like logging an error, telling the client there was an internal error, dumping additional information that wouldn't be useful in the successful case, and/or moving on to another task. All gracefully, without any intermediate functions having to care (aside from providing basic exception safety), and without having to throw your hands up and give up. Aborting without being asked is rather presumptuous and robs your callers of all opportunities to do anything about the problem you encountered.
People do this stuff and find it useful... you're effectively telling them all that they're doing something useless and they may as well just abort. That's... quite a claim.
fluoridation 13 hours ago [-]
>If the API is that a function is infallible and then I decide that it’s a fallible function then that’s a pretty major change and I’m just gonna have to update all the call sites to deal with a fallible return result.
What if you don't control those call sites?
>Might as well just call std::abort.
Sure. I mean, not really, because the caller cannot handle an abort. You're making a decision for the caller that the situation is unresolvable, where the caller might disagree.
>No. You’ve simply made a new contract and the new contract is that under certain cases an error is returned in the form of an exception.
If the function doesn't use exceptions for normal error conditions, then no, it's not a new contract, because you don't need to do or know anything specific to handle the situation. You could do something like
and not have to worry about the specifics. It's just an exception. You don't have to care about what exactly happened, you just care that something that couldn't be resolved happened. When exceptions are misused you see stuff like
Not always, but this does usually mean that the exception is part of the contract of the function. It's a condition that the caller must handle as part of the normal usage of the function. FileNotFound exceptions are quite often a prime example of exception abuse.
Replying to your other comment here:
>They’re fallible functions. Don’t write fallible APIs that require exceptions to report errors! That’s bad API design!
I disagree. You should ensure your arguments are valid before indexing vectors and dereferencing optionals. You wouldn't iterate a vector like this, I imagine?
for (size_t i = 0;; i++){
auto x = vector.at(i);
if (!x.has_value())
break;
//...
}
forrestthewoods 12 hours ago [-]
> What if you don't control those call sites?
If I am choosing to change the API contract then someone who wants to use the new API has to update. This is not a big deal.
> If the function doesn't use exceptions for normal error conditions, then no, it's not a new contract
I disrespectfully and emphatically disagree. I do not accept your definition of contract.
> You could do something like (try-catch wrapper)
Let me be clear. Having to add a bunch of random fucking try-catch bullshit around every fucking function call is EXACTLY why I hate exceptions and is EXACTLY what I think is bad software design.
If you think a function should return a value or some unspecified exception whose details are irrelevant then that function could return an option with no information loss, or a result with an Error that is ignored.
> You wouldn't iterate a vector like this, I imagine?
I wouldn’t use at(i) for iteration. The only reason for a function like at(i) to exist is because you want it to be fallible.
xigoi 3 hours ago [-]
> Let me be clear. Having to add a bunch of random fucking try-catch bullshit around every fucking function call is EXACTLY why I hate exceptions and is EXACTLY what I think is bad software design.
The whole point of exceptions is that you don’t need to handle errors at every call site. You can just have one central try-catch block at a place where you have a way to deal with the error, such as report it to the user.
zabzonk 11 hours ago [-]
> having to add a bunch of random fucking try-catch bullshit around every function call
Not the person you are replying to, but did you see where he said:
> When exceptions are misused
forrestthewoods 11 hours ago [-]
That’s a No True Scotsman argument. It is not a good or useful one imho.
fluoridation 11 hours ago [-]
>If I am choosing to change the API contract then someone who wants to use the new API has to update. This is not a big deal.
Throwing lets you handle the new situation without changing the API at all.
>Let me be clear. Having to add a bunch of random fucking try-catch bullshit around every fucking function call is EXACTLY why I hate exceptions and is EXACTLY what I think is bad software design.
See, that's what happens when you form your opinions on half-digested ideas. Let me be clear. You don't add "a bunch" of try-catch blocks. You don't wrap every call that's capable of failing exceptionally in a try-catch block. That's exactly how you don't use exceptions. The whole point of exceptions is that the compiler will handle the stack unwinding for you so you don't need to worry about it. If you don't want to, or don't know how, or can't handle an exception at a specific point then don't. Let it bubble up for someone else to catch. See the ellipsis in my example? Inside of it you might have a gigantic call tree that performs all sorts of different operations that may all fail in different and unexpected ways. You could write the whole thing and not have a single try-catch besides the one I wrote explicitly. Let me reiterate; this is what you DON'T do:
The only reason you would do something like this is to satisfy a specification such that you have to return different errors, specifically when each of the different calls fails. So... don't specify your functions such that you're required to do this? Just do
foo();
bar();
baz();
or if you really must not throw from the function,
If you add exceptions to a library that didn’t previously use them then I almost definitely have to update my code. The fact that it compiles and runs but will behave in undesirable ways makes it even worse, not better!
> or if you really must not throw from the function,
I’m aware.
But if your library that offers foo adds exceptions now I need to think about it at every single callsite, and probably wrap the function. It’s extremely irritating.
> learn how to use them properly.
In my 20+ years of professional C++ development I have a great experience not using exceptions and a strictly negative experience using them.
Perhaps sometimes I’ll stumble upon a library or codebase where exceptions make the code simpler, easier to understand, and easier to write. But my experience is exceptions make everything strictly worse and that not using exception is a strict win with zero downsides.
fluoridation 10 hours ago [-]
>The fact that it compiles and runs but will behave in undesirable ways makes it even worse, not better!
* Exceptions
* Unstable API
* Incorrect behavior
Pick your poison. I know what I prefer.
>But if your library that offers foo adds exceptions now I need to think about it at every single callsite
You really don't. Like I said, it's kind of the whole point of exceptions.
>In my 20+ years of professional C++ development I have a great experience not using exceptions and a strictly negative experience using them.
Hence my recommendation to learn how to use them. I can replace "exceptions" with anything else (computers, diesel engines, HR people), and there's probably someone who holds that belief. That doesn't make it true.
And GTFO with that No True Scotsman nonsense. That a tool can be misused doesn't delegitimize the tool. If you saw someone using classes instead of namespaces you wouldn't conclude classes are bad, you'd call that person a knobhead.
jcelerier 7 hours ago [-]
> If you add exceptions to a library that didn’t previously use them then I almost definitely have to update my code.
No, that's the whole point. You let them bubble up to the top of the event loop and you report the error to the user. As a user, anything else leads to shitty software where the programmer tries to outsmart the world around them (and fails, obviously, leading to worse end-user experience than just admitting that you don't and can't have control over everything)
throwaway173738 1 hours ago [-]
What user? I work on systems where there might not be a keyboard or a mouse or a display and where the use of there is one doesn’t care that the “flooblutz was out of turving.” The only thing stack unwinding gets me is it forces me to restart my program from the beginning.
dataflow 14 hours ago [-]
> One of the problems with exceptions is it’s utterly impossible to know if a given function call can return exceptions and if so what they are.
Could you please explain how exactly you know if a function might abort?
And how you figure out exactly which error codes a function might return if it does return an error?
And why/how your techniques for figuring out the above don't work for exceptions?
> Python is the bloody worst because I never effing know what the hell any damn function can throw or return. It’s so so frustrating.
No, it's pretty possible. Virtually any interesting function you call can throw AttributeError or TypeError, if nothing else, simply by virtue of you passing an object of the wrong type or behavior.
"But I don't mean those particular exceptions! I don't care about them." Well yeah that's kind of the point. If you can pretend that problems you don't know how to handle don't exist, then you can pretend the same for exceptions and errors. You're not supposed to care about the entire universe of possible error conditions; it's not only impossible but also you wouldn't be able to handle all of them anyway. You handle everything you can reasonably handle and then let the rest propagate, not the other way around. Same for error codes and exceptions.
"But the documentation would tell me which error codes I care about!" Well it can do that for exceptions too. If the documentation sucks then bring it up with the API developer not the language developer.
> But I’ll take Rust results over exceptions 10,000% of the time. Not even a question.
Sure, feel free to do that. Or use error codes in C++, whatever you prefer. Not like I'm trying to turn this into a Rust vs. C++ debate.
forrestthewoods 13 hours ago [-]
Functions aborting is not something I’ve ever really had to think about. Exception heavy codebases it’s something I have to ALWAYS think about.
Error codes are pretty bad. Global error code is awful. An error enum is pretty nice.
So here’s the thing. I’ve been a professional C++ programmer for 20 years. Not once have I ever worked in a codebase that used exceptions. It’s fine. Occasionally I use a thirdparty library that does use exceptions and it’s bloody awful.
dataflow 8 hours ago [-]
> Functions aborting is not something I’ve ever really had to think about.
There are certainly a lot of programs for which it matters a whole lot. In a lot of applications you absolutely don't want your program to crash. I'm not even talking about rocket launching or pacemakers or HFT here... I just mean something as simple as a web server or job server. It's the difference between taking down the server (say, getting DoS'd) vs. not.
> So here’s the thing. I’ve been a professional C++ programmer for 20 years. Not once have I ever worked in a codebase that used exceptions. It’s fine.
I work on codebases without exceptions too; it's not just you. "It's fine" in the same sense that working with a messy desk or floor "is fine". One certainly can live with it -- especially when there's no better option available -- but the clutter gets in the way, distracts you, and sometimes leaves you with a little papercut.
> Occasionally I use a thirdparty library that does use exceptions and it’s bloody awful.
You're not wrong -- this can certainly happen -- but I think you've misidentified the problems. There are other reasons for this than what you've listed in [1] or that I have the energy to fully expand on here, but the biggest one is probably the fact that you're getting the worst of both worlds by the mere virtue of mixing them. Nobody claimed that mixing code that uses exceptions with code that assumes they're disabled would lead to a good outcome. It neither lets you simplify the problem by assuming exceptions will work like they're supposed to, nor does it let you simplify the problem by assuming exceptions are nonexistent. It's like putting cars and bicycles on the same sections of the road all over the town. It's going to lead to more crashes than if you had just stuck with one or the other, and that's not because cars or bikes inherently suck.
> it's really hard to imagine why you think you'd have a better take on this than Google.
Do you mean this as an appeal to Google being the home to great talent, or more as an endorsement of the specific guidance provided by this specific style guide, Google or no?
Because if the former, I think I do almost everything better in my context than Google would. It would be hard not to considering the difference in organizational scale.
otabdeveloper4 22 hours ago [-]
I interview C++ developers often, and here in 2026 it seems pretty much everyone is using modern (C++20 and up) language versions.
Maybe the tooling finally caught up.
canyp 22 hours ago [-]
My codebase uses a fairly dumbed down version of C++, but I would have liked to see more depth in this post. As it is, it is not very useful.
The point on exceptions I think is also misleading. Compilers typically make throwing an exception the expensive part, and the happy path inexpensive (not more expensive than a branch checking for errors, which should be the baseline for comparison, not an implementation with zero error checking.) So to say that they are "expensive" doesn't really make a useful argument.
And there are more things that could be done in this camp, like proposing a set of compiler flags, and a linter to enforce the subset you are subscribing to. Unfortunately the post offers none of that.
gpderetta 21 hours ago [-]
[from the linked article]
> <deque>: Needs a major performance overhaul", acknowledging that the standard's mandated block size is too small and the design needs to be rebuilt at the next ABI break
Except of course the standard does not mandate a block size. That's purely msvc picking a wrong block size and being stuck with it.
The rant about lists is also nonsense.
canyp 16 hours ago [-]
I'm not sure about the standard part, but it does call out MS' STL in the sentence prior to what you quoted.
And why is the latter nonsense? lists have had terrible cache performance for decades now and vector is the better default choice of container.
PaulDavisThe1st 19 hours ago [-]
> There are many more things to avoid than just iostream.
But even "avoiding iostream" is stupid. The author presumably really means "avoid operator>> and operator<< for I/O". Even using type-safe printf-like stuff ultimately still sits on top of iostream.
canyp 16 hours ago [-]
<format> doesn't require iostream.
PaulDavisThe1st 15 hours ago [-]
fair, but it generates a std::string .. if you want to see it, what are you going to do with it? use .c_str() ??
tialaramex 13 hours ago [-]
If you want to see it where? If you want a string you've got a string. If you want to write text to something resembling file I/O (e.g. stdout) then std::print and std::println are what you need instead of std::format
Stroustrup's I/O Streams is a weird dead-end C++ technology. Bjarne is probably never going to get over it, but everybody else should forget about it ASAP.
zabzonk 13 hours ago [-]
Worth pointing out that the iostreams library is the work of many hands, so Stroustrup is not solely to blame. I just found this old SO question which I answered many years ago (I am anon/Neil) which addresses this:
Unlike your comment, which is the pinnacle of human thought?
You are also wrong.
Chaosvex 10 hours ago [-]
I'm not wrong. It's been removed elsewhere for being LLM generated. This discussion has already been had multiple times.
The author used Claude to generate it and instructed it to intentionally insert mistakes. They had comments posted on Reddit that discussed doing this and their account is a slew of AI generated responses. The short responses where it appears they didn't use AI don't reflect the behaviour you'd expect from somebody that claims to have over 30 years of experience in HFT but rather point toward them being a teenager or somebody rather immature. They also make a number of wild claims that when put together, are unlikely to be true.
Sorry if you can't recognise it for what it is and shame on you if it's your website, given that you've previously submitted content from there.
ephimetheus 7 hours ago [-]
Funny this predates even the original paper first introducing the attention mechanism underpinning modern LLM.
Chaosvex 6 hours ago [-]
What are you talking about? Are you claiming ~two weeks ago predates LLMs or am I misinterpreting what you're trying to say?
1 hours ago [-]
PaulDavisThe1st 23 hours ago [-]
You can take
for (auto const & ess : esses) {
...
}
from my cold dead hands.
Also, you can fight me if you want to take
dynamic_cast<Derived> (base_ptr)
and force me to implement my own typing system every time I need to upcast.
Basically, stick with C and leave C++ programmers alone. I haven't seen a less useful article about C++ in a long time, and as an HN reader, that's really saying something.
rfgplk 23 hours ago [-]
One thing I've noticed about a lot of these "strict C" developers is that quite often they actually refuse to learn C++. One of the most common complaints of C developers regarding C++ is "it does things behind the scenes/performs magic", often with regards to operator overloading. When they refuse to actually look at the implementation (y'know you can check if an operator has been overloaded) AND they refuse to acknowledge that a huge chunk of "pure C" does HEAPS of magic behind the scenes (that the developer has no idea about) unless they've actually studied the spec in detail. Malloc and memory allocation methods are at least 10k+ lines of code for instance.
fasterik 17 hours ago [-]
I don't think "refusing to learn C++" is the right way to frame it. I want to use the language features that are actually useful to me, without being forced into a specific programming style. I can't speak for every "orthodox C++" programmer, but for me that means using exclusively plain-old-data structs, non-member functions, and "dumb" pointers. I have no issue with learning to use a C++ feature when it's directly useful to a problem I'm trying to solve.
As one example, I recently found templated lambdas useful in making animations.
Right. Use/Choose those features you are most knowledgeable/comfortable with. That is why C++ has such a smorgasbord of features and supports multiple paradigms. Over time, as one learns "better ways" (for a certain definition and one is convinced of it) of doing something change/modify as needed.
That is all there is to it.
uecker 18 hours ago [-]
I was programming in C++ before switching to C and I would say that C++ adds a huge amount of mental load compared to C. I think one can understand how much of a relieve it is to not worry about everything C++ does only after not using C++ for a quite a while.
PaulDavisThe1st 15 hours ago [-]
Any time I go back to C from C++, it's only comfortable if I've got a C utility library that replaces most of std::
And that utility library (there are dozens of them) is just as subject to debate and issues as libstdc++
I am not going to implement my own C versions of 90% of the stuff std:: provides, sorry.
uecker 7 hours ago [-]
There is a difference between "needing a library" and "implement my own". One can just pick one. And even when implementing things yourself, one has to do this just once.
lelele 22 hours ago [-]
> y'know you can check if an operator has been overloaded
And there lies the problem with C++: to be sure, you have to check. C++ code can't be taken at face value -- the most innocuous-looking code could be a ticking bomb.
pjmlp 22 hours ago [-]
Just like any C function without looking into the translation unit, don't say you blindly believe on the function name.
lelele 20 hours ago [-]
Not really. C++ is on another level altogether: the code could be calling implicit conversion operators, the compiler could have instantiated some template code in an unforeseen way, and so on.
Years ago, I was really proficient in C++, but after a year of programming in C#, I realized that not once had the behavior of my code caught me off guard. In the following years, I only ran into quirky behavior a couple of times. I could finally program without the constant mental overhead of watching out for C++ pitfalls.
pjmlp 20 hours ago [-]
I suppose you're aware C# also has implicit conversion operators, operator overloading, reflection, aspect oriented programming, compiler plugins, interceptors.
Seems strange to talk down C++ while praising C#, which incidentally has been getting features to increase its use where Microsoft previously might have used C++ instead.
You catch pitfalls in any language the same way, using static analysis, which C authors introduced right in 1979, acknowledging the issues with language, which they decided to outsource to another tool, instead of improving the language.
A long tradition in computing.
lelele 19 hours ago [-]
Yes, C# is becoming more and more complex, but IMO C++ is still in a class of its own. Just compare how many different, sometimes competing ways there are to initialize variables in C++, each with its own subtleties.
I guess we'll have to agree to disagree here. And of course, even if C++'s user base seems to be shrinking, it still works well for some categories of programmers.
pjmlp 18 hours ago [-]
Even if it is shrinking, people leaving C++ aren't racing to go back to C, rather to languages with similar expressive type systems.
In D, we've introduced editions so that obsolete, problematic, or redundant features can be removed.
pjmlp 8 hours ago [-]
Finally happened! Thanks for the heads up.
Yeah C# is getting a bit unwidely.
WalterBright 17 hours ago [-]
My favorite was the regex engine that was implemented using C++ operator overloading. The author was very proud of it, but you could not tell what code was regex code and what code was math code.
I went to some lengths with D to discourage such abusive operating overloading practice.
kstrauser 15 hours ago [-]
Thank you. I don't even use D, but appreciate all efforts to socialize sane programming practices.
WalterBright 15 hours ago [-]
Thank you for the kind words! I recently did a presentation at Yale where I felt free to ridicule some insane practices, and how to do a better job.
But isn't this a problem with all code? Looking at a Rust function signature how can you be sure that it does what it says it does? Or python?
dwattttt 16 hours ago [-]
To the extent that one doesn't try to suppress compiler warnings & errors / actively break the language, you get what the compiler confirms is true about a function signature.
For Python, it's very little (nothing?). For Rust, you get more than most; lifetimes tell you whether it holds onto a pointer you give it.
A lot of us are too busy solving problems. Learning about the latest language features, which we often won't be able to use anyway due to the trouble of moving a large dev environment to a newer standard, feels like academic masturbation.
C++ folks are very much into their language, and can't seem to understand that most folks don't want to dedicate significant amounts of mental resources purely to language details.
bluGill 22 hours ago [-]
Moving to new C++ is a non event, change the compiler / flags and done. Using the new features requires some learning but not a big deal since you can figure out what you need from a summary and learn what is useful for your problem.
The problems of the code I'm writing far exceeds the complexity of the language. Your complaint about complexity fall flat to me, unless you are working on a trivial program you need to deal with things far more complex than any language.
pjmlp 22 hours ago [-]
Like implementing the compilers used by C devs.
uecker 18 hours ago [-]
GCC was implemented in C and there are plenty of other C compilers written in C. GCC has been converted to C++ at some point, but large parts are still essentially C and I do not think the change to C++ was actually helpful (but others may disagree). In any case, the idea that one needs C++ to have C compilers is certainly simply wrong.
WalterBright 16 hours ago [-]
My original C compiler was implemented in C and then bootstrapped. Zortech C++ was implemented in Zortech C, and then bootstrapped.
D was implemented in "C with Classes" and then translated to D and bootstrapped. There isn't a line of C code in it.
Over time, we gradually replace the C-isms and C++isms and anachronisms in it to native D-isms.
delta_p_delta_x 18 hours ago [-]
Clang/LLVM with its more permissive licence sees _much_ wider use and is written purely in C++. PlayStation, automotive, platforms and runtimes like Chromium/V8, Android, etc. are all built with Clang.
lelanthran 17 hours ago [-]
Clang usage is a fraction of GCC usage.
Regards, an embedded dev.
pjmlp 8 hours ago [-]
A decade ago surely, in 2026 I doubt it.
With Android, iDevices, PlayStation, Switch, and everyone that had proprietary compilers down using downstream forks from clang, due to the more appealing license.
Who is left still using GCC, other than existing projects lacking a clang backend for a snowflake embed CPU?
In any case, if it is a modern GCC release past 2012, it was compiled with C++ as well.
uecker 7 hours ago [-]
The license is indeed the big reason for many proprietary forks (not so much that llvm is written in C++). This is not a good thing though.
uecker 18 hours ago [-]
How is this relevant? (also wider use for C I would doubt)
pjmlp 18 hours ago [-]
Naturally GCC was originally written in C, given its age, and the original GNU coding standards document.
With time, the GCC developers acknowledged the benefits of using C++ over C, and migrated the code.
GCC requires a C++ compiler to bootstrap since around 2012, and GNU coding standards has been updated to several languages beyond C, time to go up with times.
uecker 18 hours ago [-]
I think the fallacy of this argument is obvious.
pjmlp 8 hours ago [-]
Not really, given that GCC no longer compiles with a pure C compiler.
Unless of course, you don't have anything to do, and feel like bootstraping GCC 16, using a compiler chain all the way back to 2012 thereabouts.
uecker 7 hours ago [-]
The question is whether GCC is a good example of the benefits of C++ for compilers. Considering the code looks to 95% like C code and uses data structures that we originally implemented in C, I don't see this argument.
lelanthran 17 hours ago [-]
All the foot guns in C are still in C++, and C++ adds a significant multiple more.
ReactiveJelly 16 hours ago [-]
The regular table saw is still in the new workshop, and the new workshop adds a SawStop and another regular table saw.
fluoridation 15 hours ago [-]
Literally untrue. Two words: stronger typing.
flohofwoe 6 hours ago [-]
Memory corruption magnets like iterator invalidation, std::string_view or std::span are on a whole different level than the footguns that were inherited from C. At least with C style raw pointers you know that you have to be careful when you see one, in C++ the unsafety is lurking in hidden places sprinkled all over the stdlib and comes in all shapes and forms.
21 hours ago [-]
jstimpfle 22 hours ago [-]
Trust me, I know more C++ than most or all of my peers (working two jobs simultaneously), and I know a million ways that C++ features suck. Also standard library and containers. If you want I'll point out the ways in which std::deque, and even std::map, std::unordered_map, even std::vector (!) suck. IMO, just don't do it.
rfgplk 22 hours ago [-]
The standard library implements really do suck (in some cases), but this should be separated from C++ (the language). Even the standard splits the language grammar from the standard library cleanly.
wavemode 22 hours ago [-]
You can't really separate the two, firstly because some parts of the standard library interact directly with the language's syntax (e.g. <initializer_list>), and secondly because the language standard dictates things about the behavior of the standard library that limit implementation options.
For example, the standard says that adding elements to an <unordered_map> is not allowed to invalidate references to keys or elements within the map. That makes it impossible for any standards-compliant C++ implementation to use a high-performance implementation in which keys and elements are stored contiguously in a flat array.
nly 20 hours ago [-]
Your map example only concerns the standard library, not the language.
wavemode 19 hours ago [-]
Its behavior is dictated by the language.
The context of this thread is that someone stated that the C++ standard library sucks, and someone replied to them saying that it's just some implementations that suck, but that's separate from the language. The point I'm trying to make, in response, is that it is about the language. It's not just "some" implementations - there is no implementation of the C++ standard library that doesn't have these inefficiencies, because the language's own standard requires them.
(This is tangential but - this is why I often say that C++ is not actually the most complex language in the world, it's just over-specified. If you took almost any popular programming language and wrote a document dictating the behavior of every single feature and library to the same level of detail, you would end up with a document similar in length or even longer than the C++ standard.)
jstimpfle 17 hours ago [-]
In my reading, they didn't say it's due to bad implementations, though. They were trying to separate the standard into two parts, the one about the language syntax and semantics, and the one about the standard library. And I think this is a fair separation actually. But that doesn't make the core language any better ;-)
wavemode 13 hours ago [-]
Hm. You're probably right.
gpderetta 21 hours ago [-]
Which sucks... unless you really need reference stability.
jstimpfle 20 hours ago [-]
std::vector has left the chat.
stinos 20 hours ago [-]
and even std::map, std::unordered_map, even std::vector (!) suck
It's really hard to take your comment serious because of generalization like this. Maybe they're not usable for your particular usecase but that doesn't mean they suck. Just like there's a 'million' ways that C++ sucks in your book, there's a reason there's millions of lines of code out there where these containers are valid usecases and hence work without issues whatsoever nor a need to replace them with something else.
jcranmer 19 hours ago [-]
std::map and std::unordered_map are just unbelievably shitty implementations. The former is a red-black tree, which in my entire programming career I have needed to reach for like... twice? It's just not the right container for almost any problem you have, yet it's the one that gets the short, sweet name. The latter is a bucket-based hashmap, which is about the worst kind of hashmap that can be built. On top of that, their APIs are also really annoying to use compared to, say, Python or Rust's implementation. At least C++20 finally added a simple contains method, but something like setdefault is just a chore to get implemented.
WalterBright 15 hours ago [-]
Pretty much the only collections I use are:
1. arrays
2. linked lists
3. hash tables
4. simple binary trees
jstimpfle 19 hours ago [-]
They're not useable for anything serious, i.e. high throughput, low frequency, massively concurrent work. In other words, most of the things for which you shouldn't better have chosen a different language in the first place.
They're also unusable by the way because of ergonomic and software architecture factors, such as bad modularity, terrible compile times, unreadable error messages, unreadable symbol names...
Yes that is overgeneralizing a little bit but it's largely true.
The problem is typically not the containers themselves but all the other bad decisions that they push you to make in order to work around their "small issues".
The huge problem is that these containers can get you started quickly, i.e. leetcode type stuff and single threaded stuff, but at some point you'll realize your architecture ended up completely in the wrong place because of that.
If you haven't been thinking deeply about memory management and concurrency, you won't be able to understand, no offense meant. I've just fixed another subsystem that was completely overwhelmed, seeing 8x bandwidth gains already on a small testsystem, but the factor is basically unbounded when moving to bigger systems, when it's about contended vs uncontended.
stinos 1 hours ago [-]
If you haven't been thinking deeply about memory management and concurrency, you won't be able to understand, no offense meant
I do think about that, when needed. My point is that these containers can be 'good enough' in places where it doesn't really matter, not that they're always the go-to thing. E.g. I really don't see any issue using a map as part of a configuration type of object which gets read from args or json and which only gets used once at application start.
delta_p_delta_x 19 hours ago [-]
> anything serious, i.e. high throughput, low frequency, massively concurrent work
Why is only 'high throughput, low frequency, massively concurrent work' considered 'serious'?
Pannoniae 17 hours ago [-]
They're just clearly inferior in pretty much any situation.
The map stuff the other posters summed up well but even std::vector is dogshit with pretty much all implementations having inlined grow code in push_back, a not too great API and missed optimisations e.g. no trivial relocation when growing the vector / moving it and no useful APIs such as "grow but don't initialise"...
jstimpfle 16 hours ago [-]
To be fair grow-but-don't-initialize is a pretty fundamental part of the API, the reserve() method.
But already the basic premise that you should push back without thinking is wrong. You will suffer reallocations and invalisations when you least expected them, and frankly you have to architect around that fact which is a terrible restriction. You can work around by pre reserving but at that point it's just a basic fixed heap allocated array but worse because the type gives you a weird look all the time, "I'll realloc as soon as you don't pay attention, harhar"!
tialaramex 15 hours ago [-]
std::vector lacks what I call the "bifurcated reservation API". It has Rust's Vec::reserve_exact but not Vec::reserve -- these APIs serve subtly different purposes, the former (which C++ calls just "reserve") says "Here's a hint for exactly how big this container will ever grow" while the latter says "Here's a hint for how big this container will get in my immediate future, but it might grow further later".
The implementation always tries to grow (if necessary) to the exact size chosen for Vec::reserve_exact, but for plain Vec::reserve if growth is needed it always grows exponentially, not to the exact size, preserving the O(1) push cost.
For a typical "doubling" growable array type, if we're pushing groups of ten items, reserve_exact or C++ grows like 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ... which is much worse than O(1) whereas the correct reserve grows 10, 20, 40, 80, 160 preserving O(1)
For trivial types you can work around this in C++ with a little work, and for the non-trivial types you can work around it with a bunch more extra code, but you probably won't.
Bjarne among other people teaching C++ recommend just not using the reservation API as a reservation API because of this problem†, and the resulting teaching definitely leaks into CS graduates and even into languages which have the correct API and so you have to un-teach the bad lesson.
In applications where you actually can't afford to pay for growth (or at least in some cases can't afford this) I also like Vec::push_within_capacity which I believe comes from Rust for Linux where the kernel legitimately needs this "If there's room push, otherwise I have a plan B" approach.
† To Bjarne this API is instead conceived of as a way to preserve reference validity. Since it won't grow, our references will still work.
jstimpfle 8 hours ago [-]
Vec::reserve() is the behaviour you get from C++ std::vector push_back() (implicit just-in-time reserve), so right now I can't see a situation where I'd want the explicit Rust version even if I didn't think the whole push realloc thing is mostly a bad idea.
Yes, Rust version allows you to maybe skip a reallocation step or two by doing explicit up front reallocation. But remember most allocation work is always from the last grow anyway. The Rust version seems like a microoptimization, giving a little bit more explicit control in a situation where you've already pretty much given up control and gone like, throw hands in the air, we're doing push_back()!
tialaramex 4 hours ago [-]
As with the likely/ unlikely branch hint the problem is that programmers are wrong much more often than they expect on both sides. They're too often wrong to think they know the final size - hence Bjarne's caution - but they're also too often wrong that they've got no idea how much capacity they need at all. So hence this API.
You're correct that this isn't a huge optimization. But it more than pulls its weight directly because it's a small boon when you're right and it doesn't have the terrible penalty that Vec::reserve_exact has when inevitably the programmer is sometimes wrong. It's very much about saving pennies, but the growable array type is so widely used that counting pennies makes sense.
I have a lot more thoughts about reservation, but these suffice for specifically the growable array type.
jstimpfle 9 hours ago [-]
Feels like typical improvement/perfection tunnel vision syndrome to me, though. That syndrome is engrained in C++ community and I think also Rust community, although it looks like the latter took the chance to do many things better from the start learning from C++'s mistakes.
Realloc is pretty much never the right way in whatever form, and I've never seen any need to include realloc in any of my own allocators (mostly blockallocators and linear allocators and pools/free lists, sometimes using malloc/free).
jstimpfle 18 hours ago [-]
You are free to make your own definition, what are your suggestions?
(Obviously I meant to say low latency, not low frequency)
delta_p_delta_x 5 hours ago [-]
That is a cop-out. You made the assertion, you define it.
What about these particular workloads (and the environments they're used in) make them 'serious' and why are other workloads 'lesser' and therefore the standard library 'suffices'? Why not use better containers for everything? Google, for instance, universally recommends Abseil.
miroljub 17 hours ago [-]
Because if you don't need any of these, any slop implementation will do.
nuc1e0n 17 hours ago [-]
c memory allocation functions can be implemented in 1000 lines or so. I've done it myself. Maybe more are needed to handle strange operating systems or architectures, as glib does.
flohofwoe 21 hours ago [-]
> Malloc and memory allocation methods are at least 10k+ lines of code for instance.
Only the really big ones, e.g. here is Emscripten's allocator that focuses on small binary size and is implemented in about 1.5 kloc (ignoring comments and whitespace it's actually under 1 kloc), and that allocator is perfectly fine for most use cases (especially C code bases which typically don't have a high allocation frequency):
Right tool for the job etc... big general-purpose allocators like jemalloc or mimalloc are usually a bandaid to somewhat salvage a failed memory management strategy.
EPWN3D 20 hours ago [-]
C programmers aren't complaining about the "magic" being tens of thousands of lines of code. They're complaining about the magic including bizarre side effects that brazenly violate the principle of least astonishment.
In C++, you can overload the comma operator to do shit. I've seen it done. There's no reason to do it, and no reasonable person would ever expect it in a code base they're unfamiliar with. To find bug in that ultimately roots back to that implementation, you have to go eliminate every other whack-job possibility before it even occurs to you that maybe the weirdo who wrote this code chose to overload the comma operator.
I'm not going to argue with anyone who wants to use C++ in their own projects, you do you. But let's be real about what C programmers are complaining about. It's not line count. It's syntactic obfuscation. I don't just level this criticism at C++ either. Basically every major new language has its own byzantine syntactic constructs to some degree.
amomchilov 10 hours ago [-]
“It’s so much magic! Now if you’ll excuse me, I have to go use my generic container library rewritten in 50 layers of preprocessor macros.”
IncreasePosts 23 hours ago [-]
If we accept the maximum that "any sufficiently advanced technology is indistinguishable from magic", then c++ is indeed magic. It's so advanced that one of the worlds foremost experts in the language(herb sutter) has determined that the language is too complex and we need a whole new language(confront) which is simpler and can be converted to c++.
rfgplk 22 hours ago [-]
C++ is actually obscenely complex, I don't deny that. Just mastering object lifetime rules is crazy difficult due to all the edge cases, but it comes with the territory.
tialaramex 21 hours ago [-]
> it comes with the territory
In the sense that C++ is complicated because it's C++, which is complicated? That's just a tautology. If you mean "the territory" in some other sense there's no reason to believe this.
Chaosvex 21 hours ago [-]
[dead]
flohofwoe 21 hours ago [-]
> for (auto const & ess : esses) {
This is allowed by Orthodox C++
> dynamic_cast<Derived> (base_ptr)
This isn't because it requires RTTI, but dynamic_cast is also a typical code smell.
Orthodox C++ isn't generally against new C++ features, it only advices to wait about 5 years (or at least one C++ version) for stabilization and to apply some common sense before adopting them.
The notes about not using RTTI, exceptions and stdlib features that allocate under the hood are all justified by painful experience with those things in the context of game development.
In general, the restrictions outlined in the post make a lot of sense when considering that Branimir (of BGFX fame: https://github.com/bkaradzic/bgfx) is coming out of the game dev hemisphere, and from that PoV none of the restrictions are controversial - on the contrary, it would be highly controversial to suggest going all in on Modern C++ features ;)
PaulDavisThe1st 19 hours ago [-]
> This is allowed by Orthodox C++
I can see no rationale for this whatsoever. It is nothing but syntactic sugar.
> Branmir (of BGFX fame
Appeals to authority don't really work for me.
I've been writing a cross-platform DAW (0) for 25+ years, in C++, and what a game dev has to say about the language in their own work might be of passing interest but not much more.
Being aware of the pitfalls of particular features of a language is an important task for anyone programming in that language. But that doesn't mean that the language is fundamentally broken or that programmers cannot make their own choices about which features to use.
(0) on at least the same level of complexity as a modern game
forrestthewoods 17 hours ago [-]
Your level of vitriol and anger at someone expressing an opinion is really weird.
Literally everyone who uses C++ decides which features to use/embrace and which to avoid. Someone sharing their particular preference is pretty normal and fine.
> Appeals to authority don't really work for me. I've been writing a cross-platform DAW (0) for 25+ years, in C++
I love how you reject appeal to authority and then try to establish yourself as an authority. That’s cute.
PaulDavisThe1st 16 hours ago [-]
Which wording was vitriolic and angry?
I wasn't seeking to establish my own authority in any way: "X is brilliant, we should listen to them" being countered by "there are lots of people with similar levels of experience with this thing who have many different opinions (I happen to be one of them)" isn't an appeal to authority. But sure, I could have left out the ("I happen to be one of them") part without changing my point much.
TFA is not about someone sharing their preferences. It's a direct call to not use many features, and claiming that to do otherwise is a mistake. Here's an example of sharing preferences:
"I've often tried to use C++'s variadic function templates, but I've found that just using initializer lists tends to be simpler and more readable".
Here's an example of how TFA would put that:
"do NOT use variadic function templates"
flohofwoe 6 hours ago [-]
> It's a direct call to not use many features, and claiming that to do otherwise is a mistake.
I think you are thouroughly misunderstanding the blog post. It's not an aggressive "you must start using this now" thing, but mainly a definition of what "Orthodox C++" is so that communication about coding styles is simplified.
When somebodies says "this project is using the Orthodox C++ style" then it is immediately clear to everybody what exactly that entails (and if not they can google the term it and find the blog post). It's also a counter proposal to some of "Modern C++" madness (like "almost always auto") that was all the rage around a decade ago when that post was first written.
FWIW I wrote a fairly similar blog post in 2013 to describe what our team coding style looked like back then:
(PS: back then I still thought that C++ could be salvaged)
forrestthewoods 16 hours ago [-]
> Which wording was vitriolic and angry?
“I haven't seen a less useful article about C++ in a long time, and as an HN reader, that's really saying something.”
The tone of all your comments reads as oddly intense imho. Perhaps not your intent. Opinions may vary.
> TFA is not about someone sharing their preferences. It's a direct call to not use many features, and claiming that to do otherwise is a mistake
Yeah that’s fine. The whole piece is an opinion piece on what someone thinks is a good approach to C++ development. There is no value in hedging every single bullet point with a bunch of flourish imho.
In game dev C++ circles nothing in this list is particularly controversial. It’s just writing down what many/most devs already did.
cyber_kinetist 22 hours ago [-]
LLVM uses a hand-rolled version of RTTI for the best performance (the parent constructor accepts its runtime type as an enum), and it seemed that the maintenance costs for it aren't that high. (See https://llvm.org/docs/HowToSetUpLLVMStyleRTTI.html)
Or if you're even lazier, you can easily make a more automatic RTTI system with some templates / macros that works much faster than dynamic_cast! (See https://github.com/royvandam/rtti)
badsectoracula 18 hours ago [-]
> for (auto const & ess : esses) {
The problem with this is that whoever is reading the code as-is does not know what type "ess" is. Sometimes you get the definition somewhere nearby, in which case it is probably fine - assuming it is close enough that it'll be included in a diff - but more often than not you don't know.
Yes, an IDE can probably tell you (probably, depends on the IDE and assuming everyone uses one) but even that requires some extra action like moving the mouse over the definition and hoping it'll give you something. However this wont show up in diffs, PRs, code reviews, etc.
IMO `auto` is one of those C++ features that really needs discipline to use - and when in doubt, i'd rather ban its use (except where you cannot do otherwise) than rely on everyone doing the right thing.
preg_match 15 hours ago [-]
C++ is an IDE type language in my opinion. C is not, because C doesn’t have an expressive enough type system anyway to justify it.
Yes, just use and IDE. This is a problem in Rust as well. And C#, Java, and others.
IMO you should use auto as much as possible. If the code can be written with auto, it should be. There’s no reason to repeat type definitions.
If you can use auto, what that means is the type is already statically known. C++ is a statically typed language; the compiler and tools know what type things are. So, just ask the tools, because they’re not wrong.
PaulDavisThe1st 16 hours ago [-]
It doesn't matter what the type is, that's the whole point!
Moreover, what's even more beautiful? You can change the type of things in the container "esses", and the code doesn't need to change.
If you have the experience, this construct tells you everything you need to know: it's an iteration over a container, visiting every element in order, without copying it, and without modifying it.
You don't need to know any more.
wk_end 18 hours ago [-]
How important is that, the ability to be certain of the type of some iterated value from a container from one line in total isolation? The odds are very good that it's clear from the context, which is why the compiler can infer it easily enough too. And then the consequence of not inferring correctly would be...the code doesn't compile?
Of course some discipline is required - as with just about everything in programming, especially in C++ - but developers in just about every other statically-typed language lean on type inference (including far more extensive type inference) and don't wring their hands about it. It's hard not to see this as a case of Blub - if you learned about typing with `Foo foo = new Foo()`, anything different might seem scary.
...anyway, in this case the real win probably is the range-based for loop, rather than the auto. `for (const Foo& foo : foos)` isn't so bad, but `for (std::vector<Foo>::const_iterator it = foos.begin(); it != foos.end(); ++it)` is pretty rough.
badsectoracula 18 hours ago [-]
> How important is that, the ability to be certain of the type of some iterated value from a container from one line in total isolation?
As important as the code to be readable.
> The odds are very good that it's clear from the context
As i wrote, if the actual type can be seen somewhere nearby (close enough to be included in diffs) then that's fine - it is already in the context. Though that is not usually the case and i personally had to work with code with which i was not familiar and which used `auto` all over the place and had to go hunting for the actual container declaration to see what it is (Visual Studio was not helpful in its tooltips).
So my actual experience is that i'd rather see the actual type.
However...
> anyway, in this case the real win probably is the range-based for loop
...yes, the range-based for loop is often the better choice when it comes to readability. And when compared with the iterators, it is pretty much always more readable than them :-P. `for (const Foo& foo : foos)` is basically what i'd prefer to see. It is the use of `auto` i pointed out.
my-next-account 22 hours ago [-]
Implementing STL iterators are a bloody PITA
flohofwoe 21 hours ago [-]
You don't need iterators for the range-based for loop, just a .begin() and .end() method which returns a raw pointer is enough.
(don't use that project though because it's been archived, I've switched back to plain old C in the meantime)
ok123456 22 hours ago [-]
stringstreams and fmt are a godsend compared to printf/scanf, which have historically led to most memory bugs in the first place!
Printf/scanf are implemented as variadic functions without type checking and rely on the compiler to perform its own internal metaprogramming to inspect and warn about format mismatches.
Anyone advocating the use of the old cstdio as a primary design decision about which C++ language features to use is not serious.
No exceptions or RTTI make sense in an embedded system that needs to ensure determinism, but are arbitrary and unnecessarily hobbling for high-level systems and application programming. How do you do runtime method dispatch without creating vtables and RTTI from first principles? How do you propagate a runtime error deep from the bowels of a component all the way to some top-level event loop? The "orthodox" approach would be a mess of integer return codes with associated enums (and none of that enum class nonsense!). No Thanks. It's clear the author has no idea what he's talking about.
nasretdinov 23 hours ago [-]
Such a missed opportunity to call it C <orthodox cross emoji>
greenbit 22 hours ago [-]
Was thinking "Hesitance C" could work
undershirt 23 hours ago [-]
that's one way to combine three plus signs!
23 hours ago [-]
23 hours ago [-]
asveikau 22 hours ago [-]
The criticisms of STL and allocation are fair, though move constructors improved the shallow vs deep copy problem on resize.
Smart pointers are good. People were doing them outside the standard in the late 90s.
Lambdas are a good feature.
Panzerschrek 9 hours ago [-]
> Don’t use RTTI.
That's the core feature of the language. Not using it doesn't make any sense.
> Don’t use stream (<iostream>, <stringstream>, etc.), use printf style functions instead.
printf is type-unsafe and bug-prone. Also modern C++ standards have better formatting utilities.
> Don’t use anything from STL that allocates memory, unless you don’t care about memory management.
In 99.9% of the time one should not care. Using something like std::vector is perfectly fine.
Overall I find such "Orthodox" C++ harmful. I call it "pure C heresy".
kabdib 22 hours ago [-]
I've been doing embedded systems in C++ since rocks were young, and this is a great summary of what to avoid.
I would sure love a good coroutine runtime, and first-class support for defer. You can do these manually, but language/toolchain/debugger support is nice to have.
(Pragmatically, I will be retired by the time they would be useful)
usrnm 22 hours ago [-]
A defer is just a dozen lines of code nowadays, if you really need it, but in most cases you don't if you're doing RAII
wglb 22 hours ago [-]
It is still a bit amazing to me that it was significantly easier to do coroutines in Sigma 5 assembly and likely most any assembly than in C or C++. Two languages supposedly close to the machine.
kabdib 22 hours ago [-]
I have seen a pure C/C++ implementation of coroutines (it used setjmp/longjmp, and memcpy to copy stacks in and out of the native arena). Not the most portable of constructions, but it worked absurdly well.
Being able to write "async" code essentially in-line is a superpower.
In embedded, I like wrapping a class around a set of registers. Nobody else gets to write to that piece of hardware except that class.
As far as avoiding things... avoid basically everything you don't need. Don't add language features that don't actually help you, just because they're there. Keep the subset you use small. But pick that subset to match your problem well, rather than out of dogma.
Martin_Silenus 22 hours ago [-]
Nothing surprising here. People who view C++ as just a better C always outnumbered those who view it as another language.
That's exactly how democratic governments make their decisions… you might think it's stupid, and you'd be right, but that's democracy. It's the majority that counts, not what's right. At least you can have a little fun with their arguments, they're pretty inventive you know.
yyx 23 hours ago [-]
At this point, if you want better C, just use Zig.
pjmlp 22 hours ago [-]
Except it is years away of being 1.0, with the industry support of C++.
greenbit 22 hours ago [-]
Sometimes I actually want objects that are transparent, fully public, and 'struct' is perfect for that. But if I then go and put methods into those structs, does that make me unorthodox?
blashyrk 22 hours ago [-]
That's simply because we live in a world where UFCS is restricted to niche languages and we're stuck with "methods" instead. At least Rust/Kotlin/Swift support type extensions (with a thousand papercuts, i. e orphan rules)
rramadass 22 hours ago [-]
No, it is perfectly valid if your design demands it.
For example, the Windows MFC framework had classes whose data members were all public. Some of reasons were;
1) MFC was a wrapper over lower-level Windows API/structs and therefore unnecessary getter/setter methods were avoided. The C++ class could be a simple wrapper over the underlying C-style POD.
2) The framework classes were supposed to be used by implementation inheritance to build one's skeleton application which led to tight coupling between base and derived classes. Hence the designers decided to make all members public which meant that users were not limited by any omissions in the basic design.
I actually used these ideas in a project where i implemented a C++ api over C state machines for H.323 protocols.
badlibrarian 22 hours ago [-]
[flagged]
gpderetta 23 hours ago [-]
Don't follow dogma.
BiraIgnacio 23 hours ago [-]
Holly bananas, that Boost Design Rationale post is, what's the word I'm looking for, intense.
rfgplk 23 hours ago [-]
This is gonna be a long critique, I'll try to keep it concise.
> C-like C++ is good start, if code doesn’t require more complexity don’t add unnecessary C++ complexities.
C is almost obsolete nowadays. Not to mention that C++ is effectively a strict superset of C (nearly 99% of the C standard is in C++) and the few features that aren't are included as compiler extensions (VLA, restrict keyword, nested functions). There are a handful of C features that aren't in C++, and for very good reason (most of them suck).
When was the last time you ran into a C library that a pure C++ compiler couldn't compile? Only if someone decided to spam the new keyword all over the codebase (or something similar).
> In general case code should be readable to anyone who is familiar with C language.
Most C++ already is? Even very template heavy C++.
> Don’t do this, the end of “design rationale” in Orthodox C++ should be immedately after “Quite simple, and it is usable. EOF”.
A lot of the methods in that document are necessary to make C++ shine, especially template metaprogramming.
> Don’t use exceptions.
Optional but irrelevant.
> Don’t use RTTI.
.. Why? Reimplementing RTTI in C will give you almost the same overhead.
> Don’t use C++ runtime wrapper for C runtime includes (<cstdio>, <cmath>, etc.), use C runtime instead (<stdio.h>, <math.h>, etc.)
.. Why? Those wrappers all include the "raw C runtime" under the hood (literally they do #include <stdio.h|xx>. Near 0 compiletime overhead?
> Don’t use stream (<iostream>, <stringstream>, etc.), use printf style functions instead.
This is a design decision.
> Don’t use metaprogramming excessively for academic masturbation. Use it in moderation, only where necessary, and where it reduces code complexity.
There are many programs that are _impossible_ to write in a finite time without metaprogramming. How will you (with zero runtime overhead) dispatch a function with a variable arity of random types to a handler that requires exactly that type of function? Arbitrarily? In C++ it's possible, in C it isn't.
ndiddy 22 hours ago [-]
> When was the last time you ran into a C library that a pure C++ compiler couldn't compile? Only if someone decided to spam the new keyword all over the codebase (or something similar).
In C, you can use goto to jump over a variable declaration, and you can't in C++. I understand why this is, but it's the thing I see the most often that makes C code not compile as C++.
pjmlp 22 hours ago [-]
For me it was already obsolete in 1992, when I was given a copy of Turbo C++ 1.0 for MS-DOS.
It was the next step from Turbo Pascal in terms of safety, with added benefits from cross platform.
Nowadays all C compilers that matter are written in C++ anyway.
AnimalMuppet 22 hours ago [-]
> > Don’t do this, the end of “design rationale” in Orthodox C++ should be immedately after “Quite simple, and it is usable. EOF”.
> A lot of the methods in that document are necessary to make C++ shine, especially template metaprogramming.
So? Is your goal to make C++ shine, or is it to produce useful, understandable code? My goal is good code, not being a showoff.
tialaramex 21 hours ago [-]
> nearly 99% of the C standard is in C++
But, one of the annoying habits of WG21 (the C++ committee) is sending stuff to WG14 (the C committee) to have them make it part of their language rather than accept that it's a C++ problem. Even the stupid type qualifiers are actually C++'s fault, K&R doesn't have this abomination but the pre-standard C++ did so too bad now it's in C89.
wseqyrku 17 hours ago [-]
Might as well use Rust. Basically if you have a choice, using C++ over Rust is kind of stupid in 2026, to be honest.
sudosteph 23 hours ago [-]
And here I was thinking this was going to be about a schism from Holy C [1]
I'm not opposed to the concept, but the definition is problematic:
> Orthodox C++ (sometimes referred as C+) is minimal subset of C++ that improves C, but avoids all unnecessary things from so called Modern C++. It’s exactly opposite of what Modern C++ suppose to be.
"Modern C++" is usually considered to mean the significant changes to the language in 2011, or 2011 and later. The thing is, that a "small subset improving over C", and without "unnecessary things" will not necessarily avoid 2011-and-later language features, and splurge with pre-2011 features. And this becomes clear as you read the recommendation. So, it's recommand to avoid:
* exceptions
* STL objects which allocate memory
* C++ streams
all C98 features. On the other hand, it's not recommended to avoid constexpr, and it is in fact hinted it is useful.
-----
C++ is a multi-paradigmatic language. It has lots of features, in the language and via the standard library. It is perfectly reasonable and legitimate to pick feautres which are well-tested enough; or well-regarded by, say, embedded or game developers, or doesn't seem too outlandish coming from C. Of course, different people will quibble over what exactly to adopt or discard, but I'm sure that different flavors of "orthodox C++", "sane C++", etc. are in fact used by many groups of developers.
aleksiy123 22 hours ago [-]
Somewhere within c++ there is a subset of c++ that is a great language.
The problem of course is that no one agrees on which subset that is.
jstimpfle 23 hours ago [-]
Orthodox C++, to me, is C plus the one good feature of C++: you don't have to type struct all the time.
maxvu 22 hours ago [-]
How about one of the C unorthodoxies that use typedef everywhere? (Namespaces seem suitable, too.)
jstimpfle 22 hours ago [-]
typedef is a little bit of a hassle but you can do it, even in a very strict mechanical way if writing plain C. But it's a hassle.
And namespaces suck too, so much noise for little gain. You know what, a big part of programming is naming. You just have to come up with good names. Namespaces don't magically make names better, if anything, they make them worse. And they add a lot of syntax noise.
PaulDavisThe1st 19 hours ago [-]
tell me you never use platform-provided or third party libraries without actually telling me.
jstimpfle 17 hours ago [-]
I won't, because obviously I have. Coincidentally, I've just spent more than a day to find a sane way to setup basic windowing and rendering on Windows for the 1000st time -- this time based on Direct3D compute shader and DirectComposition. Going to integrate the Direct2D/DirectWrite backend of my UI library with those technologies in a bit.
And I'm working on this project because the material removal simulation library that my company has been paying insane amounts of money for in the last 10 years just doesn't "cut" it for our grinding purposes (independently of the cruelfully bad C++ API, and the ~10x worse performance in their debug build because of all those dumb C++ objects inside), and it requires an insane amount of BS architecture around on our part to even use it, so I've concluded (after my own short periods of serious well-meaning suffering with this library on and off during the last 2 years), it should actually be way easier to make our own grinding simulation in a short time after running the numbers and concluding a realistic magnitude.
Guess what, I still see no reason why my little project shouldn't succeed, I've seriously come closer to realizing it, but COM Objects by Microsoft are once again not the reason why that is so. On the contrary, they are annoying to use (I'm not a newcomer to them), even though there are a lot of nice things to say about Microsoft APIs as well (and OpenGL should just die), it seems like a layer of bullshit to wade through for no obvious reason.
The nicest libraries in terms of usability have always been those with a simple & straightforward plain C interface.
PaulDavisThe1st 16 hours ago [-]
We're talking about names. Names like "Rectangle" and "Point" (thanks, Apple).
It's a bit like concrete - there's two kinds (of programmers): the ones who have experienced namespace issues (i.e. name collisions) and the ones who haven't, yet.
jstimpfle 9 hours ago [-]
As most, I have indeed not encountered collisions, yet, not that I could remember anything serious (I remember a macro called sth like WIN32_NO_MINMAX though).
A name like "Rectangle" will only have collisions if there is a C file that simultaneously includes headers from two distinct libraries that define that name, which frankly is very unlikely and very easy to work around. As long as you don't define a silly constructor that creates a symbol Rectangle::Rectangle(float, float, float, float) or whatever, there will not even be linker collisions when distinct parts of a software include distinct definitions of the Rectangle name.
Nevertheless, I simply name my types like "VxRect" or whatever in case they are supposed to be consumed by other components. Much better than Vx::Rect or even Company::Math::Types::Rect, isn't it?
fithisux 22 hours ago [-]
I wish the site listed compiler flags for the most popular compilers.
amomchilov 10 hours ago [-]
No benefits to modules? Oh come on.
Hard to take seriously.
netbioserror 22 hours ago [-]
Man, all of the confusion and gnashing of teeth in the C++ world really makes me grateful for my job. Smaller company, I solo develop a central module on the product stack, and I was able to evaluate languages for the project.
Nim became the obvious choice, and I wasn't a fanboy before. Simple semantics, in a very functional style oriented around data's value. References and identity have to be trapdoored. Everything is single-owner unique lifetimes by default, no annotations or best-practices required. You end up writing extraordinarily functional/procedural code that produces very fast and memory-safe binaries, it fits right into C++'s niche.
The only objection I could steel man was that the standard library and most packages are composed of relatively pure functions that return new values, so allocations are happening there. But when types as complex as data frames can be semantically used as just values, and you know they have scoped lifetimes by default, the benefits are obvious.
With all of C++'s insanely specific, subtle, implicit, compiler- and platform-dependent behaviors, I've often wondered when the industry will finally consider its dominance an artifact of first-mover inertia and simply move on. There are vastly better ways to do all of the things it does, while easily exposing levers for the the things it's considered to do exceptionally well.
I'm curious what languages you're comparing to here. Feels like it's only slightly more expressive than pure generics, but I admittedly haven't done much template metaprogramming myself. How does it compare to, say, Zig's comptime?
spot on, in my experience. nothing in zig triggers my lite OCD.
Templates are really amazing, once you learn the patterns there's basically a template version and a runtime version of things (policy types, etc.). All of that is great.
Legibility and understanding are in my opinion the most important aspect of any programming language, so Orthodox C++ is superior for maintainable code.
Template metaprogramming is sometimes very useful to get around C++'s language restrictions, but I tend to use it sparingly.
With concepts and constexpr-if and consteval it's increasingly less of a problem
https://news.ycombinator.com/item?id=40445536 (2 years ago, 63 points, 66 comments)
https://news.ycombinator.com/item?id=25554018 (5 years ago, 70 points, 102 comments)
https://news.ycombinator.com/item?id=13751244 (9 years ago, 29 points, 14 comments)
Looks like the page was moved from a GitHub gist to a github.io page in October of last year.
On the other hand, if you control your own destiny and care about velocity and code quality, many of these choices eventually become self-evident.
If you are messing around with the latest and greatest esoteric C++ stuff in 2026, bless you, you beautiful nerd. But it may be time to start evaluating where you are in life, and how you got here. (And if you're on a C++ committee, I revoke those blessings.)
For those who remain: if you have a C++ code base yet somehow have enough time and energy to write opinionated blog posts, it's really hard to imagine why you think you'd have a better take on this than Google.
https://google.github.io/styleguide/cppguide.html
Sounds like you hate exceptions, right? In which case why do you handle them at all? Just leave them all unhandled and suddenly every exception is a crash. Which is really no different from someone choosing to terminate. Which you have to worry about even without exceptions.
> if you have a C++ code base yet somehow have enough time and energy to write opinionated blog posts, it's really hard to imagine why you think you'd have a better take on this than Google.
"Given that Google's existing code is not exception-tolerant [...] Our advice against using exceptions is not predicated on philosophical or moral grounds, but practical ones. [...] Things would probably be different if we had to do it all over again from scratch."
If you std::abort(), you'll get a useful stack trace in the core dump. If you crash from an unhandled exception, you don't. That's a pretty huge difference and is one of the reasons exceptions suck.
If you're looking for implementation-specific guarantees then you could make that happen with exceptions too. I think on GCC replacing a function like __cxa_throw might be sufficient to let you capture a stack trace?
If you're looking for source-level-only guarantees then another option is to just replace your throw <expr> statements with one that attaches whatever extra info you want. You could literally script this to patch your external repos automatically too. Or heck, maybe you could even just define throw to be a macro that shoves your stack trace into some global variable before actually throwing.
All of this is up to the implementation in practice. The codebases I work on generally follow the pattern that exceptions may be thrown but may not be caught*, and thus they practically serve as terminate. And we absolutely get stack traces in our core dumps (Linux, both GCC and clang), and basically all of the complex debugging I do starts with a coredump stacktrace.
* We follow this pattern for a few reasons, (1) it is generally safer for us to assume that libraries we consume (STL included) will behave as expected with exceptions enabled, (2) "don't catch exceptions" (or if you must, catch the as close-to-throw as possible) is a simple way to avoid exceptions-for-nonexceptional-cases control flow, and (3) most of the C++ codebase is exposed through bindings in Python, and propagating errors as exceptions is the only Python-friendly way to handle it.
.. you absolutely get a stack trace from unhandled exception in c++, that starts where the exception is thrown? At least with clang and GCC, maybe MSVC isn't able to.
foo.cpp:
running: it's a basic example, but it's how I've always done all my debugging in C++ since foreverOne of the problems with exceptions is it’s utterly impossible to know if a given function call can return exceptions and if so what they are.
My code DOES want to handle errors. Exceptions are, imho, a very very poor way to report errors.
Python is the bloody worst because I never effing know what the hell any damn function can throw or return. It’s so so frustrating.
Error handling is a hard and unsolved problem. But I’ll take Rust results over exceptions 10,000% of the time. Not even a question.
For example, you're implementing an arithmetic operator and have reached an erroneous state, but the arithmetic type doesn't have an error value, the only way to communicate the error is by throwing. Another example: you've specified that a function must always succeed, but later on you find a case where the function cannot succeed. Instead of fixing all the possible call sites, throw an exception. All those callers could not have handled the error anyway, because they were coded under the assumption that no error would happen at that point. Throwing an exception and letting it unwind the stack way up (perhaps even all the way up to main()) is the sensible solution, because at that point you've reached a situation with no reasonable way for that code to handle.
Saying that you prefer Result over exceptions is like saying that you prefer strings to functions. They do different things. If you like Result, nothing prevents you from implementing a C++ equivalent.
I don't think these are true? What about std::vector::at(), std::optional::value(), etc.? And then there's std::system_error.
Both functions must return T &. If the vector is not long enough, or the object is not set, then returning a T & is impossible. So we have a function that has already been called and which must return something valid, and cannot return something valid. The only two ways to resolve this contradiction is to throw, or to terminate.
(Well, you could also trigger undefined behavior like operator[]() and operator*(). No comment.)
>And then there's std::system_error.
And what am I supposed to conclude from the existence of a type?
There are actually two reasons for this, not one:
- It violates standard terminology. The notion of a contract/postcondition/etc. is not specific to C++ or the particular implementation's mechanisms for exiting a function. It simply means a condition that must hold true after the execution of some piece of code. [1] The intent of this definition is to allow program composition: it enables one to reason about the greater program in terms of the sub-parts. Defining it to be anything else just throws people off, and rather misses the point and utility of the term.
- "Cannot" is actually too strong. A function might, in fact, be able to fulfill its contract, but still choose not to. An easy example is something like a constraint solver (SAT, chess, simulator, constexpr evaluator, or whatever). It's guaranteed to be able to find the solution eventually if it keeps going, but that's probably not always a good idea.
Now, going back to what you wrote here:
> Exceptions aren't meant to report errors, just in general. That's a misuse of them. Exceptions are meant to be thrown when a contract cannot be fulfilled.
I'm still not entirely sure I see what you mean by "report errors". How exactly have you seen people use exceptions to "report errors" that is not for the purpose of indicating that "a contract cannot be fulfilled"? The description makes it sound like using exceptions for the purpose of logging, but that would seem like a strawman... I have never seen anyone write throw instead of log. What are you referring to?
[1] https://en.wikipedia.org/wiki/Postcondition
Reporting normal errors to the caller, as opposed to exceptional errors. The distinction between normal errors and exceptional errors is kind of nebulous, but it boils down to normal errors being those that the immediate caller would be interested in, and everything else is exceptional.
For example, you have a file system class that abstracts away different underlying hardware interfaces to present to the client code a virtual file system. Not only would it be impractical for the open() function to include as part of its interface every possible error condition of all the possible backends, it would be of no help to the caller. If you're manipulating an AbstractFileSystem class just to open a path and read data, what could you possibly do with a connection reset error, or with a file system structure corruption error? Do you see what I mean? They're errors happening at different levels of abstraction. Exceptions are meant to be used to communicate error conditions when your call stack looks like
(low abstraction) ---> (high abstraction) ---> (high abstraction) ---> (low abstraction)
often with a module boundary between high abstraction stack frames. You use them to pass errors directly between frames at the same level of abstraction, that are not in direct communication.
So to answer your question, exceptions are misused when they communicate relevant errors within the same level of abstraction, such as an open() function throwing a FileNotFoundException.
Or they’re bad APIs that should be redesigned to be not bad.
They’re fallible functions. Don’t write fallible APIs that require exceptions to report errors! That’s bad API design!
No. I would never in a million years do this.
If the API is that a function is infallible and then I decide that it’s a fallible function then that’s a pretty major change and I’m just gonna have to update all the call sites to deal with a fallible return result.
Saying throw an exception and bubble up to main provides just about zero value. Might as well just call std::abort. Which is also something I would never do.
> Saying that you prefer Result over exceptions is like saying that you prefer strings to functions. They do different things.
So here’s the thing. In 20+ professional years as a C++ dev I have never ever once worked in a codebase where exceptions were used. Certainly never in first party code. Only when dealing with annoying thirdparty libraries that leveraged them.
I think your comment “contract can’t be fulfilled” is cheating. No. You’ve simply made a new contract and the new contract is that under certain cases an error is returned in the form of an exception.
I'm sorry but this is where you're clearly wrong. The whole point is unwinding doesn't have to necessarily happen all the way to main(); there is a ton of value in doing this, and it is not at all equivalent to aborting. It lets someone in the call chain do something other than abort, or clean up stuff that they otherwise might not have a chance to. Like logging an error, telling the client there was an internal error, dumping additional information that wouldn't be useful in the successful case, and/or moving on to another task. All gracefully, without any intermediate functions having to care (aside from providing basic exception safety), and without having to throw your hands up and give up. Aborting without being asked is rather presumptuous and robs your callers of all opportunities to do anything about the problem you encountered.
People do this stuff and find it useful... you're effectively telling them all that they're doing something useless and they may as well just abort. That's... quite a claim.
What if you don't control those call sites?
>Might as well just call std::abort.
Sure. I mean, not really, because the caller cannot handle an abort. You're making a decision for the caller that the situation is unresolvable, where the caller might disagree.
>No. You’ve simply made a new contract and the new contract is that under certain cases an error is returned in the form of an exception.
If the function doesn't use exceptions for normal error conditions, then no, it's not a new contract, because you don't need to do or know anything specific to handle the situation. You could do something like
and not have to worry about the specifics. It's just an exception. You don't have to care about what exactly happened, you just care that something that couldn't be resolved happened. When exceptions are misused you see stuff like Not always, but this does usually mean that the exception is part of the contract of the function. It's a condition that the caller must handle as part of the normal usage of the function. FileNotFound exceptions are quite often a prime example of exception abuse.Replying to your other comment here:
>They’re fallible functions. Don’t write fallible APIs that require exceptions to report errors! That’s bad API design!
I disagree. You should ensure your arguments are valid before indexing vectors and dereferencing optionals. You wouldn't iterate a vector like this, I imagine?
If I am choosing to change the API contract then someone who wants to use the new API has to update. This is not a big deal.
> If the function doesn't use exceptions for normal error conditions, then no, it's not a new contract
I disrespectfully and emphatically disagree. I do not accept your definition of contract.
> You could do something like (try-catch wrapper)
Let me be clear. Having to add a bunch of random fucking try-catch bullshit around every fucking function call is EXACTLY why I hate exceptions and is EXACTLY what I think is bad software design.
If you think a function should return a value or some unspecified exception whose details are irrelevant then that function could return an option with no information loss, or a result with an Error that is ignored.
> You wouldn't iterate a vector like this, I imagine?
I wouldn’t use at(i) for iteration. The only reason for a function like at(i) to exist is because you want it to be fallible.
The whole point of exceptions is that you don’t need to handle errors at every call site. You can just have one central try-catch block at a place where you have a way to deal with the error, such as report it to the user.
Not the person you are replying to, but did you see where he said:
> When exceptions are misused
Throwing lets you handle the new situation without changing the API at all.
>Let me be clear. Having to add a bunch of random fucking try-catch bullshit around every fucking function call is EXACTLY why I hate exceptions and is EXACTLY what I think is bad software design.
See, that's what happens when you form your opinions on half-digested ideas. Let me be clear. You don't add "a bunch" of try-catch blocks. You don't wrap every call that's capable of failing exceptionally in a try-catch block. That's exactly how you don't use exceptions. The whole point of exceptions is that the compiler will handle the stack unwinding for you so you don't need to worry about it. If you don't want to, or don't know how, or can't handle an exception at a specific point then don't. Let it bubble up for someone else to catch. See the ellipsis in my example? Inside of it you might have a gigantic call tree that performs all sorts of different operations that may all fail in different and unexpected ways. You could write the whole thing and not have a single try-catch besides the one I wrote explicitly. Let me reiterate; this is what you DON'T do:
The only reason you would do something like this is to satisfy a specification such that you have to return different errors, specifically when each of the different calls fails. So... don't specify your functions such that you're required to do this? Just do or if you really must not throw from the function, TL;DR: Instead of bitching about exceptions, learn how to use them properly.I do not disagree. https://xkcd.com/1172/
If you add exceptions to a library that didn’t previously use them then I almost definitely have to update my code. The fact that it compiles and runs but will behave in undesirable ways makes it even worse, not better!
> or if you really must not throw from the function,
I’m aware.
But if your library that offers foo adds exceptions now I need to think about it at every single callsite, and probably wrap the function. It’s extremely irritating.
> learn how to use them properly.
In my 20+ years of professional C++ development I have a great experience not using exceptions and a strictly negative experience using them.
Perhaps sometimes I’ll stumble upon a library or codebase where exceptions make the code simpler, easier to understand, and easier to write. But my experience is exceptions make everything strictly worse and that not using exception is a strict win with zero downsides.
* Exceptions
* Unstable API
* Incorrect behavior
Pick your poison. I know what I prefer.
>But if your library that offers foo adds exceptions now I need to think about it at every single callsite
You really don't. Like I said, it's kind of the whole point of exceptions.
>In my 20+ years of professional C++ development I have a great experience not using exceptions and a strictly negative experience using them.
Hence my recommendation to learn how to use them. I can replace "exceptions" with anything else (computers, diesel engines, HR people), and there's probably someone who holds that belief. That doesn't make it true.
And GTFO with that No True Scotsman nonsense. That a tool can be misused doesn't delegitimize the tool. If you saw someone using classes instead of namespaces you wouldn't conclude classes are bad, you'd call that person a knobhead.
No, that's the whole point. You let them bubble up to the top of the event loop and you report the error to the user. As a user, anything else leads to shitty software where the programmer tries to outsmart the world around them (and fails, obviously, leading to worse end-user experience than just admitting that you don't and can't have control over everything)
Could you please explain how exactly you know if a function might abort?
And how you figure out exactly which error codes a function might return if it does return an error?
And why/how your techniques for figuring out the above don't work for exceptions?
> Python is the bloody worst because I never effing know what the hell any damn function can throw or return. It’s so so frustrating.
No, it's pretty possible. Virtually any interesting function you call can throw AttributeError or TypeError, if nothing else, simply by virtue of you passing an object of the wrong type or behavior.
"But I don't mean those particular exceptions! I don't care about them." Well yeah that's kind of the point. If you can pretend that problems you don't know how to handle don't exist, then you can pretend the same for exceptions and errors. You're not supposed to care about the entire universe of possible error conditions; it's not only impossible but also you wouldn't be able to handle all of them anyway. You handle everything you can reasonably handle and then let the rest propagate, not the other way around. Same for error codes and exceptions.
"But the documentation would tell me which error codes I care about!" Well it can do that for exceptions too. If the documentation sucks then bring it up with the API developer not the language developer.
> But I’ll take Rust results over exceptions 10,000% of the time. Not even a question.
Sure, feel free to do that. Or use error codes in C++, whatever you prefer. Not like I'm trying to turn this into a Rust vs. C++ debate.
Error codes are pretty bad. Global error code is awful. An error enum is pretty nice.
So here’s the thing. I’ve been a professional C++ programmer for 20 years. Not once have I ever worked in a codebase that used exceptions. It’s fine. Occasionally I use a thirdparty library that does use exceptions and it’s bloody awful.
There are certainly a lot of programs for which it matters a whole lot. In a lot of applications you absolutely don't want your program to crash. I'm not even talking about rocket launching or pacemakers or HFT here... I just mean something as simple as a web server or job server. It's the difference between taking down the server (say, getting DoS'd) vs. not.
> So here’s the thing. I’ve been a professional C++ programmer for 20 years. Not once have I ever worked in a codebase that used exceptions. It’s fine.
I work on codebases without exceptions too; it's not just you. "It's fine" in the same sense that working with a messy desk or floor "is fine". One certainly can live with it -- especially when there's no better option available -- but the clutter gets in the way, distracts you, and sometimes leaves you with a little papercut.
> Occasionally I use a thirdparty library that does use exceptions and it’s bloody awful.
You're not wrong -- this can certainly happen -- but I think you've misidentified the problems. There are other reasons for this than what you've listed in [1] or that I have the energy to fully expand on here, but the biggest one is probably the fact that you're getting the worst of both worlds by the mere virtue of mixing them. Nobody claimed that mixing code that uses exceptions with code that assumes they're disabled would lead to a good outcome. It neither lets you simplify the problem by assuming exceptions will work like they're supposed to, nor does it let you simplify the problem by assuming exceptions are nonexistent. It's like putting cars and bicycles on the same sections of the road all over the town. It's going to lead to more crashes than if you had just stuck with one or the other, and that's not because cars or bikes inherently suck.
[1] https://news.ycombinator.com/item?id=48522500
Do you mean this as an appeal to Google being the home to great talent, or more as an endorsement of the specific guidance provided by this specific style guide, Google or no?
Because if the former, I think I do almost everything better in my context than Google would. It would be hard not to considering the difference in organizational scale.
Maybe the tooling finally caught up.
There are many more things to avoid than just iostream. HFT university has a good recap: https://hftuniversity.com/post/the-c-standard-library-has-be...
The point on exceptions I think is also misleading. Compilers typically make throwing an exception the expensive part, and the happy path inexpensive (not more expensive than a branch checking for errors, which should be the baseline for comparison, not an implementation with zero error checking.) So to say that they are "expensive" doesn't really make a useful argument.
And there are more things that could be done in this camp, like proposing a set of compiler flags, and a linter to enforce the subset you are subscribing to. Unfortunately the post offers none of that.
> <deque>: Needs a major performance overhaul", acknowledging that the standard's mandated block size is too small and the design needs to be rebuilt at the next ABI break
Except of course the standard does not mandate a block size. That's purely msvc picking a wrong block size and being stuck with it.
The rant about lists is also nonsense.
And why is the latter nonsense? lists have had terrible cache performance for decades now and vector is the better default choice of container.
But even "avoiding iostream" is stupid. The author presumably really means "avoid operator>> and operator<< for I/O". Even using type-safe printf-like stuff ultimately still sits on top of iostream.
Stroustrup's I/O Streams is a weird dead-end C++ technology. Bjarne is probably never going to get over it, but everybody else should forget about it ASAP.
https://stackoverflow.com/questions/2753060/who-architected-...
You are also wrong.
The author used Claude to generate it and instructed it to intentionally insert mistakes. They had comments posted on Reddit that discussed doing this and their account is a slew of AI generated responses. The short responses where it appears they didn't use AI don't reflect the behaviour you'd expect from somebody that claims to have over 30 years of experience in HFT but rather point toward them being a teenager or somebody rather immature. They also make a number of wild claims that when put together, are unlikely to be true.
Sorry if you can't recognise it for what it is and shame on you if it's your website, given that you've previously submitted content from there.
Also, you can fight me if you want to take
and force me to implement my own typing system every time I need to upcast.Basically, stick with C and leave C++ programmers alone. I haven't seen a less useful article about C++ in a long time, and as an HN reader, that's really saying something.
As one example, I recently found templated lambdas useful in making animations.
https://www.youtube.com/watch?v=cw-h0dePYZM
That is all there is to it.
And that utility library (there are dozens of them) is just as subject to debate and issues as libstdc++
I am not going to implement my own C versions of 90% of the stuff std:: provides, sorry.
And there lies the problem with C++: to be sure, you have to check. C++ code can't be taken at face value -- the most innocuous-looking code could be a ticking bomb.
Years ago, I was really proficient in C++, but after a year of programming in C#, I realized that not once had the behavior of my code caught me off guard. In the following years, I only ran into quirky behavior a couple of times. I could finally program without the constant mental overhead of watching out for C++ pitfalls.
Seems strange to talk down C++ while praising C#, which incidentally has been getting features to increase its use where Microsoft previously might have used C++ instead.
You catch pitfalls in any language the same way, using static analysis, which C authors introduced right in 1979, acknowledging the issues with language, which they decided to outsource to another tool, instead of improving the language.
A long tradition in computing.
I guess we'll have to agree to disagree here. And of course, even if C++'s user base seems to be shrinking, it still works well for some categories of programmers.
Swift, C#, Kotlin, Scala, Rust, Typescript, Python...
Yeah C# is getting a bit unwidely.
I went to some lengths with D to discourage such abusive operating overloading practice.
Slides:
https://walterbright.com/ElegantD.pdf
For Python, it's very little (nothing?). For Rust, you get more than most; lifetimes tell you whether it holds onto a pointer you give it.
You can't take C code at face value either. The name of a method or type doesn't tell you what it does. It could longjmp for all you know.
C++ folks are very much into their language, and can't seem to understand that most folks don't want to dedicate significant amounts of mental resources purely to language details.
The problems of the code I'm writing far exceeds the complexity of the language. Your complaint about complexity fall flat to me, unless you are working on a trivial program you need to deal with things far more complex than any language.
D was implemented in "C with Classes" and then translated to D and bootstrapped. There isn't a line of C code in it.
Over time, we gradually replace the C-isms and C++isms and anachronisms in it to native D-isms.
Regards, an embedded dev.
With Android, iDevices, PlayStation, Switch, and everyone that had proprietary compilers down using downstream forks from clang, due to the more appealing license.
Who is left still using GCC, other than existing projects lacking a clang backend for a snowflake embed CPU?
In any case, if it is a modern GCC release past 2012, it was compiled with C++ as well.
With time, the GCC developers acknowledged the benefits of using C++ over C, and migrated the code.
GCC requires a C++ compiler to bootstrap since around 2012, and GNU coding standards has been updated to several languages beyond C, time to go up with times.
Unless of course, you don't have anything to do, and feel like bootstraping GCC 16, using a compiler chain all the way back to 2012 thereabouts.
For example, the standard says that adding elements to an <unordered_map> is not allowed to invalidate references to keys or elements within the map. That makes it impossible for any standards-compliant C++ implementation to use a high-performance implementation in which keys and elements are stored contiguously in a flat array.
The context of this thread is that someone stated that the C++ standard library sucks, and someone replied to them saying that it's just some implementations that suck, but that's separate from the language. The point I'm trying to make, in response, is that it is about the language. It's not just "some" implementations - there is no implementation of the C++ standard library that doesn't have these inefficiencies, because the language's own standard requires them.
(This is tangential but - this is why I often say that C++ is not actually the most complex language in the world, it's just over-specified. If you took almost any popular programming language and wrote a document dictating the behavior of every single feature and library to the same level of detail, you would end up with a document similar in length or even longer than the C++ standard.)
It's really hard to take your comment serious because of generalization like this. Maybe they're not usable for your particular usecase but that doesn't mean they suck. Just like there's a 'million' ways that C++ sucks in your book, there's a reason there's millions of lines of code out there where these containers are valid usecases and hence work without issues whatsoever nor a need to replace them with something else.
1. arrays
2. linked lists
3. hash tables
4. simple binary trees
They're also unusable by the way because of ergonomic and software architecture factors, such as bad modularity, terrible compile times, unreadable error messages, unreadable symbol names...
Yes that is overgeneralizing a little bit but it's largely true.
The problem is typically not the containers themselves but all the other bad decisions that they push you to make in order to work around their "small issues".
The huge problem is that these containers can get you started quickly, i.e. leetcode type stuff and single threaded stuff, but at some point you'll realize your architecture ended up completely in the wrong place because of that.
If you haven't been thinking deeply about memory management and concurrency, you won't be able to understand, no offense meant. I've just fixed another subsystem that was completely overwhelmed, seeing 8x bandwidth gains already on a small testsystem, but the factor is basically unbounded when moving to bigger systems, when it's about contended vs uncontended.
I do think about that, when needed. My point is that these containers can be 'good enough' in places where it doesn't really matter, not that they're always the go-to thing. E.g. I really don't see any issue using a map as part of a configuration type of object which gets read from args or json and which only gets used once at application start.
Why is only 'high throughput, low frequency, massively concurrent work' considered 'serious'?
The map stuff the other posters summed up well but even std::vector is dogshit with pretty much all implementations having inlined grow code in push_back, a not too great API and missed optimisations e.g. no trivial relocation when growing the vector / moving it and no useful APIs such as "grow but don't initialise"...
But already the basic premise that you should push back without thinking is wrong. You will suffer reallocations and invalisations when you least expected them, and frankly you have to architect around that fact which is a terrible restriction. You can work around by pre reserving but at that point it's just a basic fixed heap allocated array but worse because the type gives you a weird look all the time, "I'll realloc as soon as you don't pay attention, harhar"!
The implementation always tries to grow (if necessary) to the exact size chosen for Vec::reserve_exact, but for plain Vec::reserve if growth is needed it always grows exponentially, not to the exact size, preserving the O(1) push cost.
For a typical "doubling" growable array type, if we're pushing groups of ten items, reserve_exact or C++ grows like 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ... which is much worse than O(1) whereas the correct reserve grows 10, 20, 40, 80, 160 preserving O(1)
For trivial types you can work around this in C++ with a little work, and for the non-trivial types you can work around it with a bunch more extra code, but you probably won't.
Bjarne among other people teaching C++ recommend just not using the reservation API as a reservation API because of this problem†, and the resulting teaching definitely leaks into CS graduates and even into languages which have the correct API and so you have to un-teach the bad lesson.
In applications where you actually can't afford to pay for growth (or at least in some cases can't afford this) I also like Vec::push_within_capacity which I believe comes from Rust for Linux where the kernel legitimately needs this "If there's room push, otherwise I have a plan B" approach.
† To Bjarne this API is instead conceived of as a way to preserve reference validity. Since it won't grow, our references will still work.
Yes, Rust version allows you to maybe skip a reallocation step or two by doing explicit up front reallocation. But remember most allocation work is always from the last grow anyway. The Rust version seems like a microoptimization, giving a little bit more explicit control in a situation where you've already pretty much given up control and gone like, throw hands in the air, we're doing push_back()!
You're correct that this isn't a huge optimization. But it more than pulls its weight directly because it's a small boon when you're right and it doesn't have the terrible penalty that Vec::reserve_exact has when inevitably the programmer is sometimes wrong. It's very much about saving pennies, but the growable array type is so widely used that counting pennies makes sense.
I have a lot more thoughts about reservation, but these suffice for specifically the growable array type.
Realloc is pretty much never the right way in whatever form, and I've never seen any need to include realloc in any of my own allocators (mostly blockallocators and linear allocators and pools/free lists, sometimes using malloc/free).
(Obviously I meant to say low latency, not low frequency)
What about these particular workloads (and the environments they're used in) make them 'serious' and why are other workloads 'lesser' and therefore the standard library 'suffices'? Why not use better containers for everything? Google, for instance, universally recommends Abseil.
Only the really big ones, e.g. here is Emscripten's allocator that focuses on small binary size and is implemented in about 1.5 kloc (ignoring comments and whitespace it's actually under 1 kloc), and that allocator is perfectly fine for most use cases (especially C code bases which typically don't have a high allocation frequency):
https://github.com/emscripten-core/emscripten/blob/main/syst...
...and Seb Aaltonen's offset allocator (used for allocating GPU buffers in his Vulkan API wrapper) has under 500 lines of code:
https://github.com/sebbbi/OffsetAllocator/blob/main/offsetAl...
Right tool for the job etc... big general-purpose allocators like jemalloc or mimalloc are usually a bandaid to somewhat salvage a failed memory management strategy.
In C++, you can overload the comma operator to do shit. I've seen it done. There's no reason to do it, and no reasonable person would ever expect it in a code base they're unfamiliar with. To find bug in that ultimately roots back to that implementation, you have to go eliminate every other whack-job possibility before it even occurs to you that maybe the weirdo who wrote this code chose to overload the comma operator.
I'm not going to argue with anyone who wants to use C++ in their own projects, you do you. But let's be real about what C programmers are complaining about. It's not line count. It's syntactic obfuscation. I don't just level this criticism at C++ either. Basically every major new language has its own byzantine syntactic constructs to some degree.
In the sense that C++ is complicated because it's C++, which is complicated? That's just a tautology. If you mean "the territory" in some other sense there's no reason to believe this.
This is allowed by Orthodox C++
> dynamic_cast<Derived> (base_ptr)
This isn't because it requires RTTI, but dynamic_cast is also a typical code smell.
Orthodox C++ isn't generally against new C++ features, it only advices to wait about 5 years (or at least one C++ version) for stabilization and to apply some common sense before adopting them.
The notes about not using RTTI, exceptions and stdlib features that allocate under the hood are all justified by painful experience with those things in the context of game development.
In general, the restrictions outlined in the post make a lot of sense when considering that Branimir (of BGFX fame: https://github.com/bkaradzic/bgfx) is coming out of the game dev hemisphere, and from that PoV none of the restrictions are controversial - on the contrary, it would be highly controversial to suggest going all in on Modern C++ features ;)
I can see no rationale for this whatsoever. It is nothing but syntactic sugar.
> Branmir (of BGFX fame
Appeals to authority don't really work for me.
I've been writing a cross-platform DAW (0) for 25+ years, in C++, and what a game dev has to say about the language in their own work might be of passing interest but not much more.
Being aware of the pitfalls of particular features of a language is an important task for anyone programming in that language. But that doesn't mean that the language is fundamentally broken or that programmers cannot make their own choices about which features to use.
(0) on at least the same level of complexity as a modern game
Literally everyone who uses C++ decides which features to use/embrace and which to avoid. Someone sharing their particular preference is pretty normal and fine.
> Appeals to authority don't really work for me. I've been writing a cross-platform DAW (0) for 25+ years, in C++
I love how you reject appeal to authority and then try to establish yourself as an authority. That’s cute.
I wasn't seeking to establish my own authority in any way: "X is brilliant, we should listen to them" being countered by "there are lots of people with similar levels of experience with this thing who have many different opinions (I happen to be one of them)" isn't an appeal to authority. But sure, I could have left out the ("I happen to be one of them") part without changing my point much.
TFA is not about someone sharing their preferences. It's a direct call to not use many features, and claiming that to do otherwise is a mistake. Here's an example of sharing preferences:
"I've often tried to use C++'s variadic function templates, but I've found that just using initializer lists tends to be simpler and more readable".
Here's an example of how TFA would put that:
"do NOT use variadic function templates"
I think you are thouroughly misunderstanding the blog post. It's not an aggressive "you must start using this now" thing, but mainly a definition of what "Orthodox C++" is so that communication about coding styles is simplified.
When somebodies says "this project is using the Orthodox C++ style" then it is immediately clear to everybody what exactly that entails (and if not they can google the term it and find the blog post). It's also a counter proposal to some of "Modern C++" madness (like "almost always auto") that was all the rage around a decade ago when that post was first written.
FWIW I wrote a fairly similar blog post in 2013 to describe what our team coding style looked like back then:
https://floooh.github.io/2013/06/21/sane-c.html
(PS: back then I still thought that C++ could be salvaged)
“I haven't seen a less useful article about C++ in a long time, and as an HN reader, that's really saying something.”
The tone of all your comments reads as oddly intense imho. Perhaps not your intent. Opinions may vary.
> TFA is not about someone sharing their preferences. It's a direct call to not use many features, and claiming that to do otherwise is a mistake
Yeah that’s fine. The whole piece is an opinion piece on what someone thinks is a good approach to C++ development. There is no value in hedging every single bullet point with a bunch of flourish imho.
In game dev C++ circles nothing in this list is particularly controversial. It’s just writing down what many/most devs already did.
Or if you're even lazier, you can easily make a more automatic RTTI system with some templates / macros that works much faster than dynamic_cast! (See https://github.com/royvandam/rtti)
The problem with this is that whoever is reading the code as-is does not know what type "ess" is. Sometimes you get the definition somewhere nearby, in which case it is probably fine - assuming it is close enough that it'll be included in a diff - but more often than not you don't know.
Yes, an IDE can probably tell you (probably, depends on the IDE and assuming everyone uses one) but even that requires some extra action like moving the mouse over the definition and hoping it'll give you something. However this wont show up in diffs, PRs, code reviews, etc.
IMO `auto` is one of those C++ features that really needs discipline to use - and when in doubt, i'd rather ban its use (except where you cannot do otherwise) than rely on everyone doing the right thing.
Yes, just use and IDE. This is a problem in Rust as well. And C#, Java, and others.
IMO you should use auto as much as possible. If the code can be written with auto, it should be. There’s no reason to repeat type definitions.
If you can use auto, what that means is the type is already statically known. C++ is a statically typed language; the compiler and tools know what type things are. So, just ask the tools, because they’re not wrong.
Moreover, what's even more beautiful? You can change the type of things in the container "esses", and the code doesn't need to change.
If you have the experience, this construct tells you everything you need to know: it's an iteration over a container, visiting every element in order, without copying it, and without modifying it.
You don't need to know any more.
Of course some discipline is required - as with just about everything in programming, especially in C++ - but developers in just about every other statically-typed language lean on type inference (including far more extensive type inference) and don't wring their hands about it. It's hard not to see this as a case of Blub - if you learned about typing with `Foo foo = new Foo()`, anything different might seem scary.
...anyway, in this case the real win probably is the range-based for loop, rather than the auto. `for (const Foo& foo : foos)` isn't so bad, but `for (std::vector<Foo>::const_iterator it = foos.begin(); it != foos.end(); ++it)` is pretty rough.
As important as the code to be readable.
> The odds are very good that it's clear from the context
As i wrote, if the actual type can be seen somewhere nearby (close enough to be included in diffs) then that's fine - it is already in the context. Though that is not usually the case and i personally had to work with code with which i was not familiar and which used `auto` all over the place and had to go hunting for the actual container declaration to see what it is (Visual Studio was not helpful in its tooltips).
So my actual experience is that i'd rather see the actual type.
However...
> anyway, in this case the real win probably is the range-based for loop
...yes, the range-based for loop is often the better choice when it comes to readability. And when compared with the iterators, it is pretty much always more readable than them :-P. `for (const Foo& foo : foos)` is basically what i'd prefer to see. It is the use of `auto` i pointed out.
E.g.:
https://github.com/floooh/oryol/blob/eb08cffe1b1cb6b05ed14ec...
(don't use that project though because it's been archived, I've switched back to plain old C in the meantime)
Printf/scanf are implemented as variadic functions without type checking and rely on the compiler to perform its own internal metaprogramming to inspect and warn about format mismatches.
Anyone advocating the use of the old cstdio as a primary design decision about which C++ language features to use is not serious.
No exceptions or RTTI make sense in an embedded system that needs to ensure determinism, but are arbitrary and unnecessarily hobbling for high-level systems and application programming. How do you do runtime method dispatch without creating vtables and RTTI from first principles? How do you propagate a runtime error deep from the bowels of a component all the way to some top-level event loop? The "orthodox" approach would be a mess of integer return codes with associated enums (and none of that enum class nonsense!). No Thanks. It's clear the author has no idea what he's talking about.
Smart pointers are good. People were doing them outside the standard in the late 90s.
Lambdas are a good feature.
That's the core feature of the language. Not using it doesn't make any sense.
> Don’t use stream (<iostream>, <stringstream>, etc.), use printf style functions instead.
printf is type-unsafe and bug-prone. Also modern C++ standards have better formatting utilities.
> Don’t use anything from STL that allocates memory, unless you don’t care about memory management.
In 99.9% of the time one should not care. Using something like std::vector is perfectly fine.
Overall I find such "Orthodox" C++ harmful. I call it "pure C heresy".
I would sure love a good coroutine runtime, and first-class support for defer. You can do these manually, but language/toolchain/debugger support is nice to have.
(Pragmatically, I will be retired by the time they would be useful)
Being able to write "async" code essentially in-line is a superpower.
As far as avoiding things... avoid basically everything you don't need. Don't add language features that don't actually help you, just because they're there. Keep the subset you use small. But pick that subset to match your problem well, rather than out of dogma.
That's exactly how democratic governments make their decisions… you might think it's stupid, and you'd be right, but that's democracy. It's the majority that counts, not what's right. At least you can have a little fun with their arguments, they're pretty inventive you know.
For example, the Windows MFC framework had classes whose data members were all public. Some of reasons were;
1) MFC was a wrapper over lower-level Windows API/structs and therefore unnecessary getter/setter methods were avoided. The C++ class could be a simple wrapper over the underlying C-style POD.
2) The framework classes were supposed to be used by implementation inheritance to build one's skeleton application which led to tight coupling between base and derived classes. Hence the designers decided to make all members public which meant that users were not limited by any omissions in the basic design.
I actually used these ideas in a project where i implemented a C++ api over C state machines for H.323 protocols.
> C-like C++ is good start, if code doesn’t require more complexity don’t add unnecessary C++ complexities.
C is almost obsolete nowadays. Not to mention that C++ is effectively a strict superset of C (nearly 99% of the C standard is in C++) and the few features that aren't are included as compiler extensions (VLA, restrict keyword, nested functions). There are a handful of C features that aren't in C++, and for very good reason (most of them suck). When was the last time you ran into a C library that a pure C++ compiler couldn't compile? Only if someone decided to spam the new keyword all over the codebase (or something similar).
> In general case code should be readable to anyone who is familiar with C language.
Most C++ already is? Even very template heavy C++.
> Don’t do this, the end of “design rationale” in Orthodox C++ should be immedately after “Quite simple, and it is usable. EOF”.
A lot of the methods in that document are necessary to make C++ shine, especially template metaprogramming.
> Don’t use exceptions.
Optional but irrelevant.
> Don’t use RTTI.
.. Why? Reimplementing RTTI in C will give you almost the same overhead.
> Don’t use C++ runtime wrapper for C runtime includes (<cstdio>, <cmath>, etc.), use C runtime instead (<stdio.h>, <math.h>, etc.)
.. Why? Those wrappers all include the "raw C runtime" under the hood (literally they do #include <stdio.h|xx>. Near 0 compiletime overhead?
> Don’t use stream (<iostream>, <stringstream>, etc.), use printf style functions instead.
This is a design decision.
> Don’t use metaprogramming excessively for academic masturbation. Use it in moderation, only where necessary, and where it reduces code complexity.
There are many programs that are _impossible_ to write in a finite time without metaprogramming. How will you (with zero runtime overhead) dispatch a function with a variable arity of random types to a handler that requires exactly that type of function? Arbitrarily? In C++ it's possible, in C it isn't.
In C, you can use goto to jump over a variable declaration, and you can't in C++. I understand why this is, but it's the thing I see the most often that makes C code not compile as C++.
It was the next step from Turbo Pascal in terms of safety, with added benefits from cross platform.
Nowadays all C compilers that matter are written in C++ anyway.
> A lot of the methods in that document are necessary to make C++ shine, especially template metaprogramming.
So? Is your goal to make C++ shine, or is it to produce useful, understandable code? My goal is good code, not being a showoff.
But, one of the annoying habits of WG21 (the C++ committee) is sending stuff to WG14 (the C committee) to have them make it part of their language rather than accept that it's a C++ problem. Even the stupid type qualifiers are actually C++'s fault, K&R doesn't have this abomination but the pre-standard C++ did so too bad now it's in C89.
[1] https://en.wikipedia.org/wiki/TempleOS#HolyC
> Orthodox C++ (sometimes referred as C+) is minimal subset of C++ that improves C, but avoids all unnecessary things from so called Modern C++. It’s exactly opposite of what Modern C++ suppose to be.
"Modern C++" is usually considered to mean the significant changes to the language in 2011, or 2011 and later. The thing is, that a "small subset improving over C", and without "unnecessary things" will not necessarily avoid 2011-and-later language features, and splurge with pre-2011 features. And this becomes clear as you read the recommendation. So, it's recommand to avoid:
* exceptions
* STL objects which allocate memory
* C++ streams
all C98 features. On the other hand, it's not recommended to avoid constexpr, and it is in fact hinted it is useful.
-----
C++ is a multi-paradigmatic language. It has lots of features, in the language and via the standard library. It is perfectly reasonable and legitimate to pick feautres which are well-tested enough; or well-regarded by, say, embedded or game developers, or doesn't seem too outlandish coming from C. Of course, different people will quibble over what exactly to adopt or discard, but I'm sure that different flavors of "orthodox C++", "sane C++", etc. are in fact used by many groups of developers.
The problem of course is that no one agrees on which subset that is.
And namespaces suck too, so much noise for little gain. You know what, a big part of programming is naming. You just have to come up with good names. Namespaces don't magically make names better, if anything, they make them worse. And they add a lot of syntax noise.
And I'm working on this project because the material removal simulation library that my company has been paying insane amounts of money for in the last 10 years just doesn't "cut" it for our grinding purposes (independently of the cruelfully bad C++ API, and the ~10x worse performance in their debug build because of all those dumb C++ objects inside), and it requires an insane amount of BS architecture around on our part to even use it, so I've concluded (after my own short periods of serious well-meaning suffering with this library on and off during the last 2 years), it should actually be way easier to make our own grinding simulation in a short time after running the numbers and concluding a realistic magnitude.
Guess what, I still see no reason why my little project shouldn't succeed, I've seriously come closer to realizing it, but COM Objects by Microsoft are once again not the reason why that is so. On the contrary, they are annoying to use (I'm not a newcomer to them), even though there are a lot of nice things to say about Microsoft APIs as well (and OpenGL should just die), it seems like a layer of bullshit to wade through for no obvious reason.
The nicest libraries in terms of usability have always been those with a simple & straightforward plain C interface.
It's a bit like concrete - there's two kinds (of programmers): the ones who have experienced namespace issues (i.e. name collisions) and the ones who haven't, yet.
A name like "Rectangle" will only have collisions if there is a C file that simultaneously includes headers from two distinct libraries that define that name, which frankly is very unlikely and very easy to work around. As long as you don't define a silly constructor that creates a symbol Rectangle::Rectangle(float, float, float, float) or whatever, there will not even be linker collisions when distinct parts of a software include distinct definitions of the Rectangle name.
Nevertheless, I simply name my types like "VxRect" or whatever in case they are supposed to be consumed by other components. Much better than Vx::Rect or even Company::Math::Types::Rect, isn't it?
Hard to take seriously.
Nim became the obvious choice, and I wasn't a fanboy before. Simple semantics, in a very functional style oriented around data's value. References and identity have to be trapdoored. Everything is single-owner unique lifetimes by default, no annotations or best-practices required. You end up writing extraordinarily functional/procedural code that produces very fast and memory-safe binaries, it fits right into C++'s niche.
The only objection I could steel man was that the standard library and most packages are composed of relatively pure functions that return new values, so allocations are happening there. But when types as complex as data frames can be semantically used as just values, and you know they have scoped lifetimes by default, the benefits are obvious.
With all of C++'s insanely specific, subtle, implicit, compiler- and platform-dependent behaviors, I've often wondered when the industry will finally consider its dominance an artifact of first-mover inertia and simply move on. There are vastly better ways to do all of the things it does, while easily exposing levers for the the things it's considered to do exceptionally well.