Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Orthodox C++ (gist.github.com)
63 points by klaussilveira on May 22, 2024 | hide | past | favorite | 66 comments


I don't use orthodox C++, but the author of this is also the author of bgfx, which is a very popular graphics api abstraction. It runs on (and has commercial products on) Android, ios, Playstation, Xbox, PC, Mac, Linux, and wasm. While the coding style might be unpopular, it has successful projects.

https://github.com/bkaradzic/bgfx


Ah, this article makes a lot more sense - if you're running across that many different platforms and compilers, then you've been burned many times by modern C++. I don't really agree with the whole "As C-like as possible" approach, but this statement is something I strongly believe in:

> General guideline is: if current year is C++year+5 then it's safe to start selectively using C++year's features.


Ok, since it has real use, it deserves respect.


As a younger programmer, I used to listen to people hating about everything C++ does, offers, or wants to be. Specifically https://yosefk.com/c++fqa/ [1]. Then I tasted the RAII (probably up there with Dynamic Programming for "horribly named but super powerful paradigms") koolaid, and threw my caution to the wind. It's been a pleasant ride since, even if I've been living in a somewhat comfy C++ environment with few third-party integration woes and a reasonable compiler upgrade pace.

Sure, I agree, you should carefully evaluate which language best serves your needs, and what tools in a language you should use or stay away from. But some game dev ranting about which C++ features they consider useless (without even articulating their background or constraints) is worthless imo. Especially if this is the credit they give to their name:

> After over 20 years working with C/C++ I finally got clear idea how header files need to be organized.

[1] Yes, that FAQ parody is now very outdated, seeing as it precedes even C++11. I can believe that it was on-point at the time of writing, but C++ grew up since then.


C++ indeed has many gems like RAII, and many turds. My hope is that one of the last unavoidable turds (mandatory header files) gets replaced by modules but that seems eternally on the horizon.

Frankly I haven't used C++ since LLM copilots dropped, so that would address a lot of the tedium with managing headers in rapid greenfield development.


I have very mixed thoughts about headers, there is something I kind of love about being able to document the public declarations in one slim text file; and put the definitions elsewhere. There are lots of ways to do that, but other languages don't seem to bother. There are automated solutions, to pull these out, but I rarely see, for example, a python file will often just be a series of functions with no way to tell.

But they have a ton of warts, and require duplicate maintenance.


Python does enable this, actually. __init__.py can act as a "header" file that lets you define your porcelain interface separately from internal interfaces.


Typescript's .d.ts files are basically this


Modules nowadays mostly work, at least for VC++, clang 18 and cmake/msbuild.

My hobby C++ coding nowadays only uses modules.

For portable code, I imagine at least 5 years more, given experience with previous C++ standards.


I don't find the module workflow particularly valuable myself.

The main issue is that humans suck at defining module boundaries and managing dependencies.

Files are simple.


> The main issue is that humans suck at defining module boundaries and managing dependencies.

Thus microservices, nothing like putting process boundaries into what humans fail to learn to do properly, even better with a network in the middle.


Why not just use C? It seems to be what the author is getting at.


When I need to refactor a large C codebase the first thing I do is recompile it with C++ - and then fix the missing prototypes and other grit that gets in the way. The cost to do this is minimal but I then have more options (via macros, automatic assignment, etc) to start shoring it up like a retaining wall that's about to fail. At the end of the day, a POD C++ structure with const inline accessors is way easier to reason about and costs nothing once compiled. It probably helps that I started with CFront, so C++ is just C (and C is just assembler with macros.)


The hello world C+ example is literally hello world in C

    #include <stdio.h>
    
    int main()
    {
        printf("hello, world\n");
        return 0;
    }
¯\_(ツ)_/¯


Except that in C (at least prior to C23), it should be "int main(void)".

I'm fairly sure that all existing C compilers will quietly accept "int main()", but there's an argument that "int main()" causes the program to have undefined behavior. "int main(void)" is the form documented in the standard.

And the "return 0;" is unnecessary both in C++ and in all versions of C starting with C99. (Some will argue that it's better style to be explicit.)

These are admittedly minor points.


Meh, the program execution language about main() has included from the beginning an escape hatch: it allows main to be defined “in some other implementation-defined manner.” “int main(void)” is one of four forms documented in the standard (or one of two specific shapes of code; the other two are “or equivalent” and “or in some other implementation-defined manner.”


Ed: ah, I see it's from TFA: "Hello World in Orthodox C++"

Is that really the hello world of c++ though?

It's a bit long in the tooth perhaps, bit I think still highlights some differences between C and C++: "Learning Standard C++ as a New Language":

https://www.stroustrup.com/new_learning.pdf

Ed: I suppose if you want to throw out a lot of c++, then the article might not apply...


Not any longer, this is the modern C++ hello word.

    import std;

    int main()
    {
        std::println("hello, world");
    }
Although your point stands, that is also valid C++.


He is LITERALLY referring to the the example on the page in question.


And I am literally showing that the example is outdated in C++23.


Some programs will work in C as well as in C++. It does not mean that it is not a C++ program.


3 out of 3 people replying haven't been able to differentiate C+ (orthodox C++) from Bjarne Stroustrup's C++. I posted the hello world code to show it's a poor example as it displays similitude between C+ and C.


One of the powers of C++ really is that it just gives you a vast toolset of mostly zero-cost abstractions. You can be the type of person that uses almost all of it. You can be like the author and decide to ignore most of it. You can be like most people and be somewhere in the middle and use some modern stuff and not other stuff, or whatever you're comfortable with.

People see some feature X of C++ that they don't like the design or performance of, but .. just don't use it if you don't like it, it's great. People see this setup as complexity, and in a way it is; they're not sure when to use tool X over tool Y over tool Z, what's "modern" and what's "old", but once you're experienced that's what's great about C++. Program with it however you want, for whatever purpose you want, using whichever piece you want. Ignore the stuff you don't like.


The best part about C++ is that it has every other programming language inside of it.

The worst part about C++ is that you need to know every other programming language to understand it.


I'm still looking for a conclusive list on every possible way to create an object on c++


https://timsong-cpp.github.io/cppwp/intro.object

I don't think they're fully known.


"don't use the STL if it allocates memory" is pretty aggressive change, especially compared to "some templates are okay"

Gimme std::vector/map/unordered_map/list. The performance cost is pretty miniscule to not have to worry about which dependency is better. The debuggabilty of those classes is kinda nasty tho.


> The debuggabilty of those classes is kinda nasty tho.

It’s not too bad as long as you have the gdb pretty-printers installed, and explicitly instantiate each of the member functions for each of the template specializations that you use, and link against a debug libstdc++ with bounds checking enabled, and… okay fair point.


I use debuggers all the time and have been writing c++ for years and don't know how to do that stuff.


> The performance cost is...

Measured how? Being good enough or not for which of a million uses? Without that these statements may well be true for the author but meaningless for the reader & potential user.

(Avoiding allocations is frequently a reasonable idea when minimizing latency, for example - maybe not for you).


I'm a game developer who writes games that run with rollback netcode even on android web browsers, so yeah, performance critical code. There's only one data structure in any of our games that shows memory operations in the profiler at all, the one that holds the game states on the clients. This is quite a bit of data and is operated on frequently. We use a robin hood flat map for that. For all the other stuff we basically use std::vector and it never shows up in the profiler.

I think it's wild how people insinuate I'm a fool if I literally say it never is a problem for us. I think it creates a "boy who cried wolf" problem if you always harp on stuff that doesn't actually manifest.

In general, I'm with you on memory allocation. I recently rewrote our animation system to not allocate memory at runtime, but to say "never use std::vector because it's performance is bad" is a statement I can't get behind.


There's a big C++ hater behind every Rust developer; they should just join up.


Using <algorithm> makes your code faster, simpler, and safer. Using <format> makes your code faster, simpler, and safer.

I’m so sick of people beating up on C++. The C++ community is in a moral panic to explain why it’s not as safe as rust and coming up with a ten step plan to fix all of the things wrong with it, but the reality is that it’s users like this that give it a bad rep. The same program in idiomatic modern C++ is on average slightly faster than C, significantly safer than C, and usually shorter than C. C++ does not need to apologize for its sins. C++ exceptions are not the end of the world. Templates since C++20 with concepts are ergonomically nicer to use than Java generics (and using them generously can make your code safer), and above all else, it’s a universal language that’s worth investing time in. Mastering C++, or at least attempting to in earnest, will make you a better programmer no matter what language you use for work.

/rant from a C developer that misses C++ every day


What I had read somewhere is that some of the features of C++ are slow and will require runtime support with extra functions, but some features of C++ are as fast and efficient as C so is not a problem.

I dislike many of the features of C++ and would do them differently, although some things do help. For example, I think that they should not have allowed to overload = and , operators, and I think that features of GNU C such as zero-length arrays and zero-length structures are helpful even though apparently in C++ a structure cannot really have zero length (but in C it can), but my opinion is I think it should be possible for your own structure or union type to overload any operators that are not already defined, including reading through and writing through a pointer (if you define the structure type as having a specific type that it points to; by default it does not point to anything). For example:

  struct X x;
  struct X y;
  (...)
  *x=*y; // This line can be overloaded assignment and dereferencing
  x=y; // This line cannot be overloaded assignment
  x+y; // This line can be overloaded addition, since addition on structure types is not normally defined


> I think that they should not have allowed to overload = and , operators

Comma operator I would agree is really only used for shenanigans. But operator= is as crucial as the copy/move constructors for ownership, so I'm not sure how you picture this...

> apparently in C++ a structure cannot really have zero length

Yes it can: https://en.cppreference.com/w/cpp/language/attributes/no_uni...

> I think it should be possible for your own structure or union type to overload any operators that are not already defined

I see that you are trying to solve the problem of "when reading this code, I don't know if these are the built-in operators or custom ones". I would be interested in learning about situations in which this was a problem for you, because I have not made that experience myself. From my perspective, you need to know the types of x and y to know in the first place which operations are possible - default or custom - so this rule does not really buy you much. But again, I don't think I understand your problem well enough.

FWIW, you can define a custom operator-> and operator* (they have to return pointers, or types that themselves have operator->/operator* which are then applied recursively). I am not convinced that mixing pointer-related semantics into e.g. object assignment is a sufficiently elegant solution from a conceptual perspective though.


> > apparently in C++ a structure cannot really have zero length

> Yes it can: https://en.cppreference.com/w/cpp/language/attributes/no_uni...

A structure with no members can be made to have zero length as a member of something else but not as part of its own definition. I.e., the `[[no_unique_address]]` attribute is a property of the member, not of the struct on its own. It's somewhat annoying that you can't flag an empty struct as being really empty, and have no-unique-address-ness applied everywhere it's used automatically; you have to flag every use with the attribute manually.


C++ for people who hate C++


That’s me. I am not smart enough to be a C++ programmer.


None of these are realistic, if you have to use third_party software, because that one may require them, and then all bets are off.

The only way to enforce this, if you are in full control of the source code you are in - for example the Game Engine / Runtime may fall into this, but it's impossible, or rather impractical to think you can do for the rest of the Game Tools / Plugins / etc.

So with this comes conundrum - folks working predominantly on the Game Engine side tend to push this "orthodox" style as the one that should be accepted, while the folks working on the "tools" style simply can't accept it, because otherwise we have to reinvent tons of code, or not being able to link to closed-source third_party libs in order to make a tool.

It's bananas, and wishful thinking.


Related:

Orthodox C++ - https://news.ycombinator.com/item?id=35688013 - April 2023 (3 comments)

Orthodox C++ - https://news.ycombinator.com/item?id=25554018 - Dec 2020 (102 comments)

Orthodox C++ - https://news.ycombinator.com/item?id=13751244 - Feb 2017 (14 comments)


Orthodox, huh? Looks more heterodox than just about anything I've seen. "Scientology C++" would be more descriptive.

Just to shame one out of this bundle of dumb ideas, the motivations behind avoiding exceptions range from "ugh" to "there's extra code injected in there that might be slow or something." This of course kills RAII patterns and a majority of the advantages of using C++.

IMO, the author should develop a new language and name it C+-, preferably using C preprocessor macros instead of a separate parser.


This guideline is basically banning the use of std::string.

Having a string abstraction beyond C null-terminated string is a huge deal both for convenience and for safety. It is super easy to get strings wrong in C.

This is also one of the reasons why printf won't work. You cannot pass a type like std::string to a variable args function.


I can understand banning it. It’s important to understand std::string is more of a string builder implementation, which is rarely what you want, but people treat it as the default way to handle strings in C++.

Too often I have to fix code that makes liberal use of std::string, making copies and allocations everywhere for no apparent reason, when string_view or better const char* would do.


> This is also one of the reasons why printf won't work. You cannot pass a type like std::string to a variable args function.

Can't you just call .c_str() on the std::string first to get to the null-terminated char array? https://cplusplus.com/reference/string/string/c_str/


Was gonna say, this guide is too light on examples. So an example of string usage would be passing around null-terminated char arrays.


std::string_view with %.*s as a string formatter work well enough if you're set on using printf.


I have to use std::string_view with %.*s as a string formatter every day. It is an incredible pain. Of course the solution is getting rid of printf style formatting, not string_view.


The hardest part of passing `std::string` to `printf` was getting the macro to convince the pre-standard-C++11 compiler that it was `constexpr` enough so that `-Werror=format` would work.

I found it was easiest to sometimes allocate a temporary, if the input wasn't known to be NUL-terminated.


Are you suggesting

  printf("%.*s", static_cast<int>(sv.size()), sv.data())

?


Well, wrap it up in something more ergonomic, but it works. I'm usually putting strings into some sort of I/O API (like a logger) instead of hardcoding copies to stdout everywhere.

https://stackoverflow.com/a/72575650


Wrap it way up into something ergonomic and idiomatic, like fmt.


Sure. I do. But there are tradeoffs even then. Look at the error messages compared to printf for example.


  std::string str;
  printf("%s\n", str.c_str());


I think not using an Orthodox Cross for the logo is a missed opportunity https://www.quora.com/What-does-the-emoji-symbol-stand-for


The best parts of C++ are generics and RAII, neither of which have anything to do with OOP and both of which Rust wisely stole (well, only the latter is really unique to C++).


This sounded good until you said no RTTI or exceptions. Sorry, that's a bridge too far.


Why call it “Orthodox”? Opposite of “modern?”

Maybe what we really need is “Catholic C++.”


Miaphysite C++.


C++ has only one paradigm that is functional and object-oriented.


Amish C++


One of my professors was an Amish mathematician. Had a Ph. D from MIT.

Actual Amish. Lived on an Amish community near the university.

He never used computers. Always brought a ruler, compass, and protractor to class, which was cool.

Forced us to read Euclid in the original Greek, which was slightly less cool.


Sounds like a legendary person. Do you think he would have used a computer if it was purely mechanical and driven by 1,000 hamsters? (serious question)


No. He was Amish, and had the Amish respect for his tools.

The tools were compass, ruler, and protractor.

He started off as a carpenter, but was a mathematical prodigy.

He still saw himself as a carpenter.


That is what HolyC is.


Ah yes another sub language made from c++, every author has their own

What we really need is C2

C with constexpr, type traits, and generics… oh wait this exists! Rust, Zig, etc

Sadly tool chains for some platforms are proprietary variants of llvm clang and fail to include rustc


Yes yes yes yes yes.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: