gpderetta 3 days ago

> [...] The printed result would be 0. This is because we value-initialize t and, since T has a non-user-provided default constructor, the object is zero-initialized (hence t.x is zero-initialized) then default-initialized (calling the implicitly-defined default constructor, which does nothing).

T̶h̶a̶t̶ d̶o̶e̶s̶n̶'t̶ s̶e̶e̶m̶ c̶o̶r̶r̶e̶c̶t̶:̶ a̶ d̶e̶f̶a̶u̶l̶t̶e̶d̶ c̶o̶n̶s̶t̶r̶u̶c̶t̶o̶r̶ s̶t̶i̶l̶l̶ d̶e̶f̶a̶u̶l̶t̶-̶i̶n̶i̶t̶i̶a̶l̶i̶z̶e̶s̶ t̶h̶e̶ m̶e̶m̶b̶e̶r̶s̶, n̶o̶t̶ v̶a̶l̶u̶e̶ i̶n̶i̶t̶i̶a̶l̶i̶z̶e̶. I̶ d̶o̶n̶'t̶ t̶h̶i̶n̶k̶ t̶h̶e̶r̶e̶ i̶s̶ a̶n̶y̶ d̶i̶f̶f̶e̶r̶e̶n̶c̶e̶ b̶e̶t̶w̶e̶e̶n̶ d̶e̶f̶a̶u̶l̶t̶i̶n̶g̶ i̶n̶l̶i̶n̶e̶ a̶n̶d̶ o̶u̶t̶ o̶f̶ l̶i̶n̶e̶. G̶C̶C̶ s̶e̶e̶m̶s̶ t̶o̶ a̶g̶r̶e̶e̶:̶ h̶t̶t̶p̶s̶:̶//g̶c̶c̶.g̶o̶d̶b̶o̶l̶t̶.o̶r̶g̶/z̶/r̶4̶r̶e̶5̶T̶E̶5̶a̶

edit: I missed that the author is actually value-initializing x!!! The result definitely violates expectations!

Generally, the details of the rules are arcane and sometimes have non-sensical dark corners having been extended and patched up for the last 40 years. But 99.9%[1] of the time you get what you expect.

I big improvement would be making default initialization explicit, and otherwise always value initialize. Explicit value initialization is so common that the very rare times I want default initialization (to avoid expensively zeroing large arrays) I need to write a fat comment. Writing "std::array<int, 100> = void;" (or whatever the syntax would be) would be much better.

[1] I had an extra 9 here... I hedged.

  • adrianN 3 days ago

    Once every thousand lines you don’t get what you expect? Rip

    • gpderetta 3 days ago

      Once every 1000 initializations. But hey, I would sign up for only one bug every 1000 lines.

      • klysm 3 days ago

        One of this particular class!

  • chipdart 3 days ago

    > I big improvement would be making default initialization explicit

    Actually initializing your instances, which is what's expected of every single instantiation, is enough to not experience any problem. This also means that if you want to call a constructor, you need to define it.

    This is a case of people trying to be too clever for their own sake, and complaining that that's too much cleverness for them to handle.

nickysielicki 3 days ago

The whole thing is wrong. Don’t put const references in your structs. Use std::reference_wrapper if you must.

Edit: this response is a bit dismissive but honestly my main beef with this article is that its conclusion is just straight up wrong. Do not write your own constructors, do follow the rule of 5/3/0, and if you find yourself needing to hold a const reference, you should look out for whether you’re passing in an rval temporary… none of this is really scary.

  • chipdart 3 days ago

    > Edit: this response is a bit dismissive but honestly my main beef with this article is that its conclusion is just straight up wrong.

    That's my take as well. The blogger clearly went way out of his way to find something to whine about while purposely ignoring a few of the most basic principles and guidelines.

    In the meantime, everyone who ever went through a basic tutorial just casually steps over these artificial scenarios.

  • Too 3 days ago

    Rule of 5/3/0 is about destructors, move and copy constructors. Apart from the last example, the article mainly talks about quirks with the standard constructor, if you can call it that.

    • nickysielicki 3 days ago

      main takeaway of the article according to the author, quoting:

      > In my humble opinion, here’s the key takeaway: just write your own fucking constructors! You see all that nonsense? Almost completely avoidable if you had just written your own fucking constructors. Don’t let the compiler figure it out for you. You’re the one in control here. Or is it that you think you’re being cute? You just added six instances of undefined behaviour to your company’s codebase, and now twenty Russian hackers are fighting to pwn your app first. Are you stupid? What’s the matter with you? What were you thinking? God.

      The problem with C++ and the danger with an article like this is someone might actually follow this advice, instead of eg: the core guidelines.

      Every other example is a violation of the core guidelines in some form or another. There is no other problem.

  • forrestthewoods 3 days ago

    I’ve never used std::reference_wrapper in my life. Nor have I seen it used in any of the numerous C++ code bases I’ve worked in. Although I’m sure it’s used in deep, gnarly template BS.

    Your statement may be correct! But it’s certainly not common knowledge in my experience.

    • nickysielicki 3 days ago

      My stance is just based on cpp core guidelines:

      https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines...

      https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines...

      std::reference_wrapper still can’t save you from yourself, but it’s better than violating the first link and ending up in this limbo that OP is talking about.

      See also: https://youtu.be/YxSg_Gzm-VQ (about 3:30 in)

      • lenkite 3 days ago

        That link says nothing about std::reference_wrapper ?

        • nickysielicki 3 days ago

          I’m just saying, nothing up my sleeve, no arcane templatelord bullshit, this is isocpp: don’t use references in your structs because it’s subtly broken. It doesn’t mention reference wrappers but that’s the escape hatch.

    • shrimp_emoji 3 days ago

      I use them now and then -- and I never touch templates. It's just a reference you can reassign. Or, in other words, truly a pointer that can't be null. :D (And that you can actually use in collections that require reassignable types.)

    • Guvante 3 days ago

      If you want to use assignment operators with references that is what it does.

      Nothing else.

bookofjoe 3 days ago

>I Have No Mouth, and I Must Scream (1967)

https://talesofmytery.blogspot.com/2018/10/harlan-ellison-i-...

  • e40 2 days ago

    Came for this. I once talked with Harlan Ellison. He called my house when I was 17. At 2am. This was in the days of sharing a phone via a 30ft cord and taking it into your bedroom and closing the door. As teenagers would do. Martin H Greenberg and his family were staying at our house. He was calling for Martin. I was big into SciFi and had read a few of his books. The conversation was odd. I said Martin was asleep and asked who he was. Everything after hearing his name was sort of a blur. Yes, I did wake Martin and hand him the phone. It was hard to sleep after that.

amluto 3 days ago

I’m surprised there were no snarky comments about:

> So, here’s the glue between list-initialization and aggregate initialization: if list-initialization is performed on an aggregate, aggregate initialization is performed unless the list has only one argument, of type T or of type derived from T, in which case it performs direct-initialization (or copy-initialization).

The word “unless” is even bold.

We have fancy syntax:

    T t{v0};
And we also have:

    T t{v0, v1};
And so on. But the one-element case does not reliably work like the 2+-element case. And this is in a language that increasingly works toward making it straightforward to create a struct from a parameter pack and has support for variable length array-ish things that one can initialize like this. And the types can, of course, be templated.

So you can write your own constructors, and you can initialize a tuple or array with only one element supplied, and you might trip over the wrong constructor being invoked in special cases.

I remember discovering this when C++11 initializer lists were brand new and thinking it was nuts.

  • TinkersW 3 days ago

    Initializer lists is irreverent, nobody uses it anyway. Other than a few standard containers that use it, you can completely ignore the silly thing.

    • alex_lav 3 days ago

      As is the nature of bad design, “nobody uses it other than some people sometimes” is a silly sentiment and indicative of a problem.

    • Maxatar 3 days ago

      That's the biggest problem with them. Something isn't as dangerous when it's in your face and you need to confront it on a regular basis. What's dangerous are the things that rarely need to think about, except for that very rare moment when it bites you in the ass precisely because it's not on your mind.

    • shadowgovt 3 days ago

      I've definitely seen initializer lists recommended as best-practice in safety-critical code.

AlexandrB 3 days ago

For more C++ wackiness, I recommend the C++ FQA: https://yosefk.com/c++fqa/

It's 15 years out of date now, but also timeless since C++ rarely/never removes old features or behaviours.

gattilorenz 3 days ago

What an beautiful blog theme, obviously inspired by the DEC-era computers but also clean and minimal. Refreshing!

  • georgestagg 3 days ago

    I like how the rules to the right of the headings react to the page width and how many lines are used.

jakewins 3 days ago

Man I get vertigo reading this. Reminds me of trying to understand Java constructors and object initialisation.

It’s been a while now, and at least in my experience so far Go and Rusts choice of not having special constructors really simplifies a lot.

Is there anyone that’s had the experience of missing constructors once you swapped away from them?

  • masklinn 3 days ago

    There are a few somewhat esoteric cases where constructors working in-place allow magic which can be hard to replicate otherwise e.g. Rust is still missing guaranteed “placement new” type behaviour.

    Unless you want to `ptr::write` individual fields by hand into a `MaybeUninit`, which you can absolutely do mind but that… is not very ergonomic, and requires structs to be specifically opted into this.

    • wongarsu 3 days ago

      Which can be an issue if you want to initialize a 2MB large heap-allocated object (e.g. heap-allocating a large nested struct or a big array).

      Without guaranteed “placement new” that can mean that your 2MB object gets constructed on the stack and copied to the heap. And while Linux defaults to a 4MB stack, Windows defaults to 1MB and will crash your program. Or it might work if the compiler optimizes in your favor.

      It's not something you encounter frequently, it can be worked around, and Rust will eventually solve it ergonomically without introducing constructor hell (probably with just a keyword). But finding the best language-level solution isn't straightforward (efforts to fix this for rust are ongoing for 9 years)

      • godshatter 3 days ago

        >Which can be an issue if you want to initialize a 2MB large heap-allocated object (e.g. heap-allocating a large nested struct or a big array).

        >Without guaranteed “placement new” that can mean that your 2MB object gets constructed on the stack and copied to the heap. And while Linux defaults to a 4MB stack, Windows defaults to 1MB and will crash your program. Or it might work if the compiler optimizes in your favor.

        C gets a lot of hate, often for good reasons, but at least you know where your memory is coming from when you are allocating it yourself. If you're allocating a large heap-allocated object, you're grabbing the memory directly from the heap.

        • wongarsu 3 days ago

          Memory allocation is one of the areas where currently C/C++ has or had genuine advantages over Rust. Custom allocators took Rust years, and giving standard library constructs like a Vector a custom allocator that isn't the global allocator is still experimental (=opt-in nightly-only). Similarly while Rust gives you good control over where the data ends up being stored, there is no way to make sure it isn't also put on the stack during function execution. One of the implicit assumptions underlying the language seems to be that the stack is cheap and effectively infinite while the heap is expensive. So you have a lot of control over what touches the heap, but less control over what touches the stack.

          Those are temporary pains that have remedies in the works. Rust is a fairly young language, and a lot of good-enough solutions get thrown out before ever getting beyond the experimental stage. But if you are writing software today then needing absolute control over where exactly your data touches is a good reason to prefer C/C++ today. Not that that's a very common need.

          • tialaramex 3 days ago

            I'm not persuaded that scribbling on a Box<MaybeUninit<T>> until it's initialised is less ergonomic than the C. Which isn't to say it's a desirable end state, I just don't see C as a more ergonomic alternative even for this application.

      • bennettnate5 3 days ago

        It can also be an issue if you want to wrap any API that requires fixed memory locations for objects (such as POSIX semaphores). It's UB to call a POSIX semaphore from any other memory location than where it was initialized, so making a `Semaphore::new()` API is just asking for trouble. You can deal with it by `Box`ing the semaphore, but then you can't construct the semaphore in a shared memory segment (one of the stronger use cases for process-shared semaphores).

        I have a hunch this is why there's no Semaphore implementation in the Rust standard library, though it could be due to fundamental inconsistencies in semaphore APIs across OSs as well ¯\_(ツ)_/¯

        • masklinn 3 days ago

          No, Rust doesn't have semaphores in the stdlib[0] because it was not clear what precise semantics should be supported, or what purpose they would serve since by definition they can't mitigate exclusive and thus write access to a resource and mitigating access to code isn't much of a rust convention. And nobody has really championed their addition since.

          Furthermore, they still present a fair amount of design challenges in the specific context of Rust: https://neosmart.net/blog/implementing-truly-safe-semaphores...

          [0] technically they were there, added in 0.4, never stabilised, deprecated in 1.7, and removed in 1.8

  • chipdart 3 days ago

    > It’s been a while now, and at least in my experience so far Go and Rusts choice of not having special constructors really simplifies a lot.

    This take makes no sense. Think about it: you're saying that not having the compiler do any work for you "really simplifies things a lot". Cool, so you have to explicitly declare and define all constructors. That's ok. But think about it, doesn't C++ already offer you that option from the very start? I mean, you are talking about a feature in C++ that is not mandatory or required, and was added just to prevent those programmers who really really wanted to avoid writing boilerplate code to lean on the compiler in and only in very specific corner cases. If for any reason you want the compiler to do that work for you, you need to be mindful of the specific conditions where you can omit your own member functions. For the rest of the world, they can simply live a normal life and just add them.

    How is this complicated?

    Complaining that special member functions make obvious things less simple is like complaining that English is not simple jus because you can find complicated words in a dictionary. Yes, you can make it complicated if that's what you want, but there is nothing forcing you to overcomplicate things, is there?

    • tyg13 2 days ago

      You're mistaken. Rust does not require you to define all constructors. Rust does not have constructors.

      All structs in Rust must be initialized using brace syntax, e.g. `Foo { bar: 1, baz: "" }`. This is commonly encapsulated into static functions (e.g. `Foo::new(1, "")`) that act similarly to constructors, but which are not special in any way compared to other functions. This avoids a lot of the strangeness in C++ that arises from constructors being "special" (can't be named, don't have a return type, use initializer list syntax which is not used anywhere else).

      This combined with mandatory move semantics means you also don't have to worry about copy constructors or copy-assignment operators (you opt into copy semantics by deriving from Clone and explicitly calling `.clone()` to create a copy, or deriving from Copy for implicit copy-on-assign) or move constructors and move-assignment operators (all non-Copy assignments are moves by default).

      It's actually rather refreshing, and I find myself writing a lot of my C++ code in imitation of the Rust style.

      • chipdart 2 days ago

        > You're mistaken. Rust does not require you to define all constructors. Rust does not have constructors.

        I don't think you managed to understand what I actually said, and consequently you wrote a whole wall of text that's not related to the point I made.

        • tyg13 2 days ago

          Your post starts with the flawed assumption that you have to define constructors in Rust, and then your own wall of text (ironically longer than mine) about avoiding boilerplate which doesn't apply to Rust. I'm not sure you understood my point.

          • chipdart 2 days ago

            > Your post starts with the flawed assumption that you have to define constructors in Rust (...)

            I did not. Read what I wrote.

        • tyg13 2 days ago

          Just to further illustrate what I'm saying, are you really trying to say that

          ``` //explicitly annotating this struct is default initializable and copyable #[derive(Default, Copy, Clone)] struct Foo { ... } ```

          is actually worse than

          ``` struct Foo {...}; // rule of zero, copy/move/default are defined/deleted based arcane rules predicated on the contents of Foo ```

          • chipdart a day ago

            > Just to further illustrate what I'm saying, are you really trying to say that (...)

            If you read what I wrote you'll notice I was pointing out the absurdity of claiming that being forced to write each and every single factory method/constructor is somehow better and simpler than allowing the compiler to write them for us for trivia classes but still having the compiler step off when we opt to write each and every single factory method/constructor ourselves.

    • jakewins 2 days ago

      > How is this complicated?

      Here are some ways:

      - As a junior programmer, it made the language harder to learn. Language complexity increases super-linearly as each new feature has rules of interaction with several existing features

      - Although one eventually learns to avoid the anti-features, you cannot control the actions of others. These features meant to help save keystrokes are happily employed every day, producing hard to read code

      Particularly when writing library code for other to use or when maintaining large codebases shared by hundreds of engineers, my experience is that complex features in the language end up used by junior engineers or require consideration in API design.

      • chipdart a day ago

        > As a junior programmer, it made the language harder to learn.

        Can you elaborate on your opinion? I mean, I don't think that argument makes any sense. You're talking about an optional feature that, under very specific circumstances, you can get the compiler to fill in for you default implementations for factory methods/constructors.

        As a junior developer, it should be very clear to you that if you want to call a function, including copy constructor or copy assignment operators, you need to define them first. Is that too much of a burden to place on a junior developer?

        > Although one eventually learns to avoid the anti-features (...)

        There are none of those, and obviously special member functions don't pose a problem to anyone.

        > Particularly when writing library code for other to use or when maintaining large codebases shared by hundreds of engineers, my experience is that complex features in the language end up used by junior engineers or require consideration in API design.

        I don't think you have a good grasp on the subject. I've worked on C++ libraries shared by hundreds of engineers, and API design was always from the start the primary concern. This is not a function of seniority: it's the very basics of writing modular code intended to be consumed by third parties.

        Still, special member functions are the very least of anyone's concerns because anyone remotely competent in this domain knows very well that the public interface needs to be explicitly designed to support or reject specific types of uses, and the decision of whether a component could/should be copied/moved is tied to the component's architecture and semantics.

  • _ZeD_ 3 days ago

    dude, java constructor are easy... that C++ stuff is really black magic

    and from what I understand rust constructors are basically the same as java, no?

    • DougBTX 3 days ago

      Inside a constructor you can access a partially initialised "this" value, and even call methods on it, which leads to rules like: "Do not call overridable methods in constructors"[0], as they can lead to surprising, non-local, bugs.

      Rust has functions associated with types which are conventionally used like constructors, but critically the new objects must have all their fields provided all at once, so it is impossible to observe a partially initialised object.

      [0] https://learn.microsoft.com/en-us/dotnet/fundamentals/code-a...

      • titzer 3 days ago

        Virgil solved this a little differently. The initialization expressions for fields (outside of constructors) as well as implicit assignment of constructor parameters to fields happens before super constructor calls. Such initialization expressions cannot reference "this"--"this" is only available in _constructor bodies_. Initializing fields before calling super and then the chaining of super calls guarantees the whole chain of super constructor calls will finish before entering the body of a constructor, and all fields will be initialized. Thus by construction, virtual methods invoked on "this" won't see uninitialized fields.

        https://github.com/titzer/virgil/blob/master/doc/tutorial/Cl...

      • zozbot234 3 days ago

        You can most likely use session types to soundly observe a partially initialized MaybeUninit<MyObject> in Rust. The proper use of session types could ensure that the object is only assumed to be initialized after every field of it has been written to, and that no uninitialized fields are ever accessed in an unsound way. The issue though is that this is not automated in any way, it requires you to write custom code for each case of partial initialization you might be dealing with.

    • masklinn 3 days ago

      Rust does not have constructors at all[0], it uses factory functions (conventionally named `new_somethignsomething`) but those are not special to the language.

      [0] except in the more generalised haskell-ish sense that structs or enum variants can be constructed and some forms (“tuple structs” and “tuple variants”) will expose an actual function

      • collinvandyck76 3 days ago

        I've often longed for first class constructors in Go and Rust. It was more of a problem for me with Go because you can omit a struct field when building a value, something you can't do in Rust unless it has an explicit Default impl and even then you have to explicitly add ..Default::defualt() when you're building the value.

        I never thought that constructors were that burdensome and therefore do not understand the omission in other languages like Go and Rust that followed. Quite the opposite really -- knowing that a type always went through a predefined init was comforting to me when writing Java.

        • gavindean90 3 days ago

          I think people don’t like constructors because of the potential side effects of something happening in constructors, especially if the constructor is big or doesn’t finish properly.

    • mmaniac 3 days ago

      Rust doesn't have constructors. By convention, a static method called new returns a struct - no magic.

    • jakewins 3 days ago

      I think if you think constructors in Java are easy, you are much, much smarter than I am or have missed some really, really subtle footguns.

      Eg:

      - Java constructors can return the object before they complete construction, finishing at a later time; this is visible in concurrent code as partially constructed objects

      - Java constructors can throw exceptions and return the partially constructed object at the same time, giving you references to broken invalid objects

      - Just.. all the things about how calling super constructors and instance methods interleaved with field initialization works and the bazillion ordering rules around that

      - Finalizers in general and finalizers on partially constructed objects specifically

      I don't in any way claim it's on the same level as C++, but any time I see a Java constructor doing any method calls anymore - whether to instance methods or to super constructors - I know there are dragons

      • cvoss 3 days ago

        > bazillion ordering rules

        There are 3 which pertain to object initialization in Java.

        1. super is initialized in it's entirety by an implicit or explicit call to `super()`

        2. All instance initializers of the present class are invoked in textual order.

        3. Constructor code following the `super()` call is executed.

        The only awkward thing here is the position of #2 in between #1 and #3, whereas the text of a constructor body suggests that #1 and #3 are consecutive. It gets easier to remember when you recognize that, actually, there's a defect in the design of the Java syntax here. A constructor looks like a normal function whose first action must be a `super()` call. It's not. The `super()` call is it's own thing and shouldn't rightly live in the body of the constructor at all.

        Edit: Tweaks for clarity.

      • marcosdumay 3 days ago

        Those are the normal issues inherent to constructors as a concept (except for the finalizer one).

        Any language that has constructors has some complex rules to solve those things. And it's always good to check what they are when learning the language. Java has one of the simplest set of those rules that I know about.

      • SpaghettiCthulu 3 days ago

        > - Java constructors can return the object before they complete construction, finishing at a later time; this is visible in concurrent code as partially constructed objects > > - Java constructors can throw exceptions and return the partially constructed object at the same time, giving you references to broken invalid objects

        Java constructors do not actually return the object. In Java code, it would appear to the caller as though the contructor returns the new instance, but that is not really the case. Instead, the new object is allocated and then the constructor is called on the object in (almost) the same manner as an instance method.

        Additionally, Java constructors can only leak a partially initialized object if they store a `this` reference somewhere on the heap (for example, by spawning a thread with a reference to `this`). The assertion that this gives you a reference to a "broken invalid object" is only potentially correct from the perspective of invariants assumed by user-written code. It is perfectly valid and well-defined to the JVM.

        > - Just.. all the things about how calling super constructors and instance methods interleaved with field initialization works and the bazillion ordering rules around that

        This is a gross mischaracterization of the complexity. There is only a single rule that really matters, and that is "no references to `this` before a super constructor is called". Until very recently, there was also "no statements before a super constructor is called".

        > - Finalizers in general and finalizers on partially constructed objects specifically

        Finalizers are deprecated.

      • vips7L 3 days ago

        I think you’re exaggerating the complexity here. There are corner cases yes, but the compiler will warn you about them.

      • throwaway2037 3 days ago

            > Java constructors can throw exceptions and return the partially constructed object at the same time
        
        Can you show some sample code to demonstrate this issue?
OptionOfT 3 days ago

    T::T() = default;
> You’d expect the printed result to be 0, right? You poor thing. Alas—it will be garbage. Some things can never be perfect, it seems. Here’s a relevant excerpt from our description of value-initialization:

Link: https://consteval.ca/2024/07/03/initialization/#:~:text=You%...

That actually isn't that weird, because it would allow any consumer of your library to change how your library behaves.

  • Twisol 3 days ago

    The alternative not making sense doesn't automatically make this solution make sense :( It just highlights how many corners C++ has backed its design into.

jolj 3 days ago

Is there a C++ tool that adds/shows all the implicit stuff that happens behind the scenes?

Such as all the constructors that are being added, implicit copy constructor and all the other surprises?

  • fouronnes3 3 days ago

    Best you're gonna get is a combination of godbolt and cppinsights.

    • jolj 3 days ago

      cppinsights looks like what I was looking for, there's even a vscode extension thanks

IAmLiterallyAB 3 days ago

Horrific. One of the things that scares me about C++. A real shame because it has some cool features and some brilliant minds working on it.

I'm hoping something like Herb's C++ syntax 2 will make the language useable for mortals like me.

  • chipdart 3 days ago

    > Horrific.

    I think you're whining about something that doesn't pose any problem to anyone with any passing experience in software development.

    The examples in the blog post boil down to far-fetched cases devised to trigger corner-cases of a feature where a programming language in exceptional cases auto-generates specific functions when programmers somehow explicitly decided not to do it themselves. The blogger then proceeds to explore edge conditions that lead these exceptional cases to either be enabled or disabled.

    In the meantime, be mindful of the fact that this is about a language with a clear design goal of paying only for what you use,and also the widely established rule of 3/rule of 5, which is C++101 and states that when anyone defines one of these special member functions, they should define them all. Why? Because it's C++101 that these special member functions are only generated by the compiler in specific corner cases, and given their exceptional nature the compiler will not generate them automatically if any of the requirements is not met.

    Therefore, any programmer who goes through the faintest introduction knows that they have to set the constructors they will use. Is this outlandish?

    Also, does it catches anyone by surprise that you need to initialize instances when you instantiate them? Is this too much of a gotcha to justify being called "horrific"?

    I think people like you just feel the need to have something to complain about. In the meantime, everyone in the real world happily does real work with them without any fuss.

    • quietbritishjim 2 days ago

      Are you joking? The profusion of initialisation types and exceptions to those rules (and exceptions to those exceptions...) absolutely pose a practical problem to all C++ programmers everywhere.

    • IAmLiterallyAB 2 days ago

      > I think people like you just feel the need to have something to complain about

      Thanks for the ad hominem

marton78 3 days ago

After almost 20 years of experience with C++, there are still some gnarly details I wouldn't have imagined. What a God awful language!

Kudos to that author for the great, eye catching title and the in depth detail!

  • wavemode 3 days ago
    • CoastalCoder 3 days ago

      Thanks, I'd never seen that one!

      So horrifyingly true.

      • fouronnes3 3 days ago

        If you enjoy this, a few years ago I made the "C++ iceberg" meme (with clickable links!). I've been thinking about making an updated V2 with all the "you forgot about X" messages I've been getting.

        https://fouronnes.github.io/cppiceberg/

        • CoastalCoder 3 days ago

          > If you enjoy this

          Well, I do appreciate your work, and the information is certainly helpful.

          But it's a bit like a urologist explaining what it will be like when you pass a kidney stone.

          And then find out that the C++ standards committee is working on a new kidney-stone shape that's backwards compatible, but adds more jagged spikes.

        • throwup238 3 days ago

          God bless you for making this. I plan to incorporate several of these features at work in the hope of summoning Cthulu and killing the company once and for all.

          What's your favorite "you forgot X"? You should definitely make an updated v2 because every link I've opened from the bottom half has been absolutely bonkers.

          Three dimensional analog literals drawn using ASCII? What the flying hell was the standards committee thinking.

        • java-man 3 days ago

            else while
          
          is perfectly fine, not only in c++.
  • unwind 3 days ago

    In case it's not known to everyone, the title is an obvious nod to "I Have No Mouth, and I Must Scream" [1], a 1960s US sci-fi story by Harlan Ellison.

    1: https://en.wikipedia.org/wiki/I_Have_No_Mouth,_and_I_Must_Sc...

    • tannhaeuser 3 days ago

      The question is which IHNMAIMS character the poster identifies with to have deserved his OOP misery, given all protagonists are imprisoned for life (or for eternity, actually, I believe) as a sentence for the bad things they did ;) Note there's also the adventure game created after the book, overseen and with a script also by Ellison.

    • shadowgovt 3 days ago

      I assume AM is filled with so much hate because it's just three billion lines of C++.

    • _vaporwave_ 3 days ago

      That plot summary is... dark. Does anyone know how long the story is? Most of the copies I found online are collections of short stories.

    • tpoacher 3 days ago

      pc adventure game was good too (i.e., messed up)

  • GuB-42 3 days ago

    C++ is a popular multi-paradigm language that is both cutting edge and 40 years old (more if you count C), there is simply no way around that level of complexity.

    You have "C with classes" that coexist with the "modern" way, full of smart pointers and functional programming. It is popular in embedded systems, video games, servers, and GUIs (mostly Qt). And if you look at the code, it is as if it was a different language, because the requirements are all very different. Embedded system need low level hardware access, video games are all about performance, servers want safety, and GUIs want flexibility.

    There are less awful alternative to C++. For example C on one end of the spectrum and Rust on the other end. But none of them cover every C++ use case.

    • bee_rider 3 days ago

      C++ needs a different name from multi-paradigm. Java is a multi-paradigm language. C++ is an omni-paradigm language. If there’s a paradigm,

      - There’s at least an ugly library to do it in C++

      - There might be support baked directly into the language

      - Or you could do it in Lisp, but that would be too easy

      • codeflo 3 days ago

        And if you dare to combine two of the paradigms it supports, you get UB.

        • snappythrowaway 3 days ago

          What is UB?

          • shadowgovt 3 days ago

            It's what happens when you make a union of an int and a float and write it as an int and read it as a float.

            Most compilers will do something like "treat the bits like they represent a float, even though they mean something else when they're treated as an int."

            But the language spec says the compiler is allowed to send an email to Bjarne Stroustrup with your home address so he can come over and personally set your computer on fire.

            • GuB-42 a day ago

              I bet on Bjarne Stroustrup being too busy setting other programmers computers on fire before coming to mine.

              More seriously, the typical response to undefined behavior is for the compiler to optimize out whatever code may trigger it. It is great for performance and one of the reasons C and C++ often top the charts, but it may lead to weird bugs that depend on the optimization level.

              For the union case, the compiler could completely remove the code that reads the float: because it is UB, it is something you shouldn't do, so it considers that you will never do it, so it can just remove the code. In the end your conversion function may do nothing at all (very optimized!). In practice, because it is so common, even though it is UB, it will usually do what you want it to do.

    • marcosdumay 3 days ago

      > multi-paradigm

      Well, it does unstructured imperative, structured imperative, and OOP imperative!

      Except if you count template programming, because that one is pure functional, but only runs at compile time.

    • aaroninsf 3 days ago

      > But none of them cover every C++ use case.

      Literal lol... this is not an argument in favor of C++.

    • slashdave 3 days ago

      I would sort of agree, except when c++ was invented, it was even more awful in practice (does anyone remember the chaos around STL and template caches?). So, age isn't really a factor.

    • hu3 3 days ago

      Zig looks promising too.

    • wredue 3 days ago

      If you’re looking for a language with less complexity than C++, you’re surely not going to find that in rust.

      • kelnos 3 days ago

        I disagree. To me, the complexity described in this article is more complex than anything you'll find in Rust.

        Actually, strike that: I'm not sure if it's true or not (though I suspect it is), but it doesn't actually matter. What I'm really getting at here is that there is nothing in Rust that behaves so confusingly or ambiguously as what's described in this article. If you're writing Rust, you'll never have to remember these sorts of rules and how they are going to be applied to your code.

        I do agree that reading someone else's Rust can be a challenge, if they're using Rust's type system to its fullest, and you're (quite reasonably and understandably) not up to speed on the entirety of it. And that is a problem, agreed; though, at least, fortunately it's not a problem of ambiguity, but more of a learning-curve issue.

        But I have never been writing Rust and have been confused about what the code I'm writing might do, never had to consult and puzzle out some obscure documentation in order to ensure that the code I was writing was going to do what I expected it to do. C++ falls so incredibly flat in this department, and that's why I avoid using it like the plague.

  • ghosty141 3 days ago

    I've been working with C++ at my job for 2.5 years now and I've already come to this conclusion. Wouldn't wanna use it if there is any other way.

    The fact that you can do almost anything IS pretty cool, but without having at least one C++ wizard at hand it can drive you nuts.

    • philsnow 3 days ago

      I don’t think I’ve ever gotten paid for a line of c++ but Google has a “style guide” for internal c++ code that omits something like 3/4 of the language, and people seemed pretty happy with it overall. Maybe not “happy” but “grudgingly accepting because it beats the Wild West alternative”.

      • marcosdumay 3 days ago

        C with classes and text-template generics would be an ok subset of the language, if external concepts didn't keep creeping into its semantics. The problem is that they do.

        Almost every part of C++ creeps into almost every other part, and C was already complex enough... and let's just ignore that C++ is not completely compatible with C.

      • jimbobthrowawy 3 days ago

        Is it really 3/4ths the language? (mostly culled libraries, or features?) I remember reading an old pdf published by the US air force about the subset of c++ features you're allowed to use for contracted software, and it's so different it may as well be a different language.

        I think I found it via a stackexchange answer about how the "Wiring" language for Arduino sketches differs from regular c++. In Wiring, it's mostly things like no rtti, no virtual methods not resolvable at compile time, no exceptions, unsafe-math, permissive casting, pre-build system concatenates all .ino files into one .cpp file, very limited libraries, and some default includes.

        • vitus 2 days ago

          There are a number of standard libraries that we (Google) ban because we have in-house alternatives that we generally prefer. (In some cases, this is due to compatibility with our threading model. In others, it's probably due to inertia.)

          From a skim: <chrono>, <filesystem>, <thread> (along with other concurrency libraries like <mutex> and <future>) are the main ones.

          As far as language features that we ban, it's notable that we ban exceptions. Rvalue references are generally discouraged outside of well-trod paths (e.g. move constructors). We ban runtime type information (dynamic_cast, typeid, etc). There are some newer features in C++20 that we haven't accepted yet (modules, ranges, coroutines), due to lack of tooling, concerns about performance, and more.

          Sometimes these bans get reversed (there was a long-standing ban on mutable reference parameters that was overturned after many years of discussion).

          One of the key points we try to emphasize is that readability is primarily about reading other people's code, and so we provide well-trod paths that try to avoid pitfalls and generally surprising behavior (e.g. this article's focus on initialization? https://abseil.io/tips/88). There is value in moving closer to Python's "There should be one-- and preferably only one --obvious way to do it."

          We assert that one of the goals of the style guide (the second section, after Background) is that rules should pull their own weight; we explicitly cite this as a reason why we don't ban goto. I imagine this is also why there isn't an explicit ban on alternative operator representations (e.g. writing `if (foo and bar) <% baz(); %>`).

          I don't think I agree that every rule pulls its own weight -- I like to cite that I copy-pasted the internal version of the style guide into a Google doc earlier this year to see how long it was... and it clocked in at over 100 pages. But maybe that's an indicator of how complex C++ is, where we have to make the tradeoff between being concise, being precise, and providing sufficient context.

      • fsckboy 3 days ago

        is google's "internal" style guide this?

        https://google.github.io/styleguide/cppguide.html

        • JasonSage 3 days ago

          Nit: parent didn't call it an internal style guide, but a style guide for their internal C++.

          (I'm sure it is.)

          • fsckboy 3 days ago

            Nit nit: I don't accept your quibble, I think my usage was well within English usage standards; I even put "internal" in quotes! Consider this hypothetical conversation:

            "Is the style guide they use for internal projects the same as this style guide that they have published externally?"

            "could you clarify which ones you're talking about?"

            "Is the internal style guide you described the same as this one I found in google's account on github?"

            "oh, I see what you mean"

            will you send your second, or shall we simply pistols-at-dawn?

            • kelnos 3 days ago

              I think this is a tough one, and different people are going to interpret it differently.

              The fact that you put "internal" in quotes suggested to me a mild level of sarcasm or disbelief, i.e, I read your message as "You mean this style guide, published on the internet, for all to see? Clearly that's not 'internal'!"

              Either way, to me, "internal style guide" (regardless of any quotes placed around any word) means "style guide that is internal" (that is, the style guide itself is private or unpublished).

              But the person you were replying to called it a "style guide for internal c++ code": that word ordering makes it clear that "internal" is describing "c++ projects", and that the internal/external (or unpublished/published or private/public) status of the style guide itself is not being talked about at all.

              (As an aside, if the commenter upthread had instead said "internal style guide for c++ code", that could have also meant the same thing, but would have been ambiguous, as it wouldn't have been clear if "internal" was describing "style guide" or "c++ code", or both, even. But "style guide for internal c++ code" is about as unambiguous as you can get.)

              • fsckboy 3 days ago

                you are mistaken how English works, how punctuation works, and how context works wrt parsing and semantics. Freighting what I said with your misinterpretations is on you, not me.

                what noun cluster would you use for the style guide that google uses internally? Their internal style guide is perfectly accurate. If they publish it externally, does make it no longer their internal style guide? Nope. Would it make somebody exploring the topic wish to add the clarification "oh, their internal and external style guides are one and the same"? Yes.

                None of that conflicts with what I wrote, but it does conflict with what you wrote.

                • philsnow 3 days ago

                  “External internal style guide” vs “internal external style guide”, both could make sense in different contexts.

                  Like I said, I never wrote c++ there so I’m quite likely misremembering details, but IIRC the published style guide linked to upthread is more or less a snapshot of the previously-unpublished style guide used internally for internal code. It may have some omissions and it’s quite likely out of date.

                  I’m just amused that this discussion about semantics reminds me so much of the gif linked from https://news.ycombinator.com/item?id=40882247

                  • fsckboy 2 days ago

                    the point is not whether your suggestions also work, but whether "internal style guide" does as I showed in the context as I wrote it. You need to reply to arguments made, not change the subject.

                • JasonSage 3 days ago

                  > you are mistaken how English works, how punctuation works, and how context works wrt parsing and semantics. Freighting what I said with your misinterpretations is on you, not me.

                  Uhm, no. I think you've got that completely backwards. Agree entirely with what they said, not at all with your interpretation. I think you're flat-out wrong here.

                  • fsckboy 2 days ago

                    I like how you don't reply to the specifics of what explanatory text I wrote: not replying to the riposte being the last refuge of a scoundrel

                    and btw, the Hesperus/Phosphorus reference I made many commments ago refers to a famous example of these exact questions in the Philosophy of Language. Read up on it and you may begin to know as much about this as I did before we started.

            • samatman 3 days ago

              Nit^3: this point would have been effectively conveyed as '"Google's internal style guide"'. By putting only "internal" into quotes, you call into question whether its public existence invalidates the internal nature of it.

              Whereas the respondent said this:

              > Google has a “style guide” for internal c++ code

              This is a style guide for definitely-internal c++ code, with the internality of the style guide itself unspecified. I'm not sure what the effect of the scare quotes around "style guide" is meant to be, just that it doesn't move the internal part next to the style guide part.

              Putting the whole thing in quotes, rather than just "internal", questions whether the guide you found is the guide referred to, rather than the internal nature of the style guide itself, which the quoted sentence takes no position on.

              This has been your daily dose of Hacker News style guides for discussing style guides.

            • Maxatar 3 days ago

              >"Is the style guide they use for internal projects the same as this style guide that they have published externally?"

              Consider that the style guide that AirBnB uses for internal projects is not the same as the style guide they publish externally, and you can sympathize with why the distinction matters :P.

              • fsckboy 3 days ago

                "the distinction" is literally the question I was asking to clarify, so telling me the distinction matters is divorced from the reality of my comment

                • Maxatar 3 days ago

                  Yikes! Tough day today?

                  • fsckboy 2 days ago

                    autistic with high IQ. every day I have to listen to illogic is a tough day, so yes, every day is a tough day :) don't worry about me though, as long as I feel I've pointed out the minute distinctions I was drawing, I feel happy.

                    • Maxatar 2 days ago

                      Fair enough man, my original comment was mostly in jest but I understand where you're coming from.

        • vitus 2 days ago

          Yes, modulo some text that's inappropriate to post externally. (Often, because they reference internal libraries in google3 -- this includes things like the ban on <thread> and <future>).

    • ryandrake 3 days ago

      Just another person’s opinion: I’ve been using C++ for my entire career, and to be honest, if I’m starting a new solo project, I reach for it unless there is some major technical reason not to. Yes, it can be messy. Yes, there are footguns. But as a developer, you have the power to keep it clean and not shoot the footguns, so I’m still ok with the language.

      If I was starting a new work project with a lot of junior team members, or if I was doing a web project, or a very simple script, fine I’ll use a different language. There can definitely be good reasons not to use C++. But I’m at the point in my expertise that I will default to C++ otherwise. I’m most productive where I am most familiar.

      • kmoser 3 days ago

        > Yes, there are footguns. But as a developer, you have the power to keep it clean and not shoot the footguns, so I’m still ok with the language.

        With all due respect to your expertise, the whole idea of a footgun is that it tends to go off accidentally. The more footguns a language contains, the more likely you are of accidentally firing one.

        • jeffbee 3 days ago

          I think what he's saying is that C++ users don't need to go to the Footgun Outlet Mall and wantonly brandish each of their credit cards. You can leave the subtle parts of the language on the shelf, in many cases.

      • alex_lav 3 days ago

        > I’ve been using C++ for my entire career, and to be honest, if I’m starting a new solo project, I reach for it

        This is “I use the language I always use because I always use it”, and not actually a praise or C++ specifically.

        • sqeaky 3 days ago

          Presumably "entire career" means some amount of exposure to other things.

          In my 21 years coding professionally, I will settle on C++ or Ruby for most problems depending on context. Ruby for quick, dirty, and "now!", while I use C++ for Long lasting, performance, strongly typed, and compatible things. Those aren't perfect categories and there are reasons to pick other tools, but Choosing C++ after a long career does mean something more than "I am comfortable with this".

          • alex_lav 3 days ago

            > Choosing C++ after a long career does mean something more than "I am comfortable with this

            Does it?

            • sqeaky 2 days ago

              Yes, feel free to re-read the all the rest of the comment where share a little bit of experience, and try to understand that people other than you have experience.

              • alex_lav a day ago

                I did, it was yet another “I use it because I always use it” post, but longer. Absolutely 0 value.

                But if you’re going to behave like a child I’ll skip the rest. Enjoy!

      • 725686 3 days ago

        "you have the power to keep it clean and not shoot the footguns". Really? Do you think footguns are intentionally shot?

        • sqeaky 3 days ago

          There are more an less risky behaviors. This is really well explored space in C++. Just using value semantics, shared_ptr, and RAII instead of naked news and reckless mallocs would improve several "old" codebase I have worked in. Maybe people shouldn't be reaching for const_cast so often, and similar. In some new languages some corner case may be unexplored.

          If you are fortunate enough for your domain to have good IO libraries then there is a chance you can do everything the "Modern" way avoid a lot of the headache and avoid most of the footguns entirely. That maturity and omni-pattern availability is potent, but all that power does come with the possibility of mistakes or surprises.

          Compare to newer languages where we don't know what might break or might need to do something the language omits as part of its paradigm. I have done a ton of Ruby projects and about half the time we need more performance so I need to bust out C to rewrite a hotspot in a performance sensitive way. Or sometimes you just really want a loop not a functional stream enumerator like is the default in Ruby. For a new language, Theo tried the 1 billion row challenge in Gleam and the underlying file IO was so slow the language implementers had to step in.

          This is an engineering and a business choice. There are reasons to avoid C++ and footguns, like any risk, are among them. These aren't risks without mitigation, but that mitigation has a cost. Just like newer languages have reasons not to use them. A team needs to pick the tools with risks and other cons they can handle and the pros that help them solve their problem.

          • kelnos 3 days ago

            > There are more an less risky behaviors.

            The problem is that your definition of risk may not be the same as others', and so there isn't always agreement on what is ok and not ok to do. And regardless, humans are notoriously bad at risk assessment.

            > This is really well explored space in C++. Just using value semantics, shared_ptr, and RAII instead of naked news and reckless mallocs would improve several "old" codebase I have worked in. Maybe people shouldn't be reaching for const_cast so often, and similar.

            Right, and all that is exactly the point: all of that stuff is in wide use out there, and I suspect not just in "old" code bases. So there's still not consensus on what's safe to use and what's too risky.

            And regardless, I have enough to think about when I'm building something. Remembering the rules of what language features and APIs I should and shouldn't use is added noise that I don't want. Having to make quick risk assessments about particular constructs is not something I want to be doing. I'd rather just write in a safer language, and the compiler will error out if I do something that would otherwise be too risky. And as a bonus, other people are making those risk assessments up-front for me, people in a much better position than I am to do so in the first place, people who understand the consequences and trade offs better than I do.

            I really like this value proposition: "if the compiler successfully compiles the code, there will be no buffer overruns or use-after-free bugs in it" (certainly there's the possibility of compiler bugs, but that's the only vector for failures here). For C++, at best, we can only say, "if you use only a particular subset of the language and standard library that the compiler will not define or enforce for you (find a third party definition or define it yourself, and then be very very careful when coding that you adhere to it, without anyone or anything checking your work), then you probably won't have any buffer overruns or use-after-free bugs." To me, that's almost worse than useless; even if I find a C++-subset definition that I think is reasonable, I'm still not really protected, because I still have to perfectly adhere to it. And even if I do, I'm still at the mercy of that subset definition being "correct".

            • sqeaky 7 hours ago

              You need to think about every language's rules and APIs when starting a project. You do raise valid concerns about C++ and those are real issues. Every language has real issues. Some have fewer up front and obvious choices, but they all have issues.

              Choosing no to decide you still have made a choice.

        • bee_rider 3 days ago

          What even is a footgun supposed to be? The analogy doesn’t really make sense, in that… I mean the first thing anybody learns about guns is that they are “always loaded” (even when you know they aren’t) and you only point them at things you want shot.

          Is a footgun a gun that only aims at feet? Because that seems like a dumb thing to invent in the first place. Or is it a gun that happens a to be aiming at feet? That seems like something that could only exist by user error.

          I think “enough rope to hang yourself” is a more accurate description of almost every programming languages, since rope is at least intended to be useful (although it is a bit more morbid of an analogy).

          • rmwaite 3 days ago

            Imagine that you had a gun and one of the features of the gun was that if you had sunglasses on and something in your left pocket, holstering the gun would cause it to immediately fire. You could argue that the gun shouldn’t behave this way, but it’s also possible that others are dependent on this behavior and you can’t remove it.

            This is a footgun - the way to avoid the holster firing is to simply not wear sunglasses, or keep something in your left pocket, and then it would never fire. But the problem is that both of those things are extremely common (for good reason). It’s a poorly thought out feature because it has severe consequences (potentially shooting your foot) for extremely common situations (wearing sunglasses and using your left pocket).

            • bee_rider 3 days ago

              I basically don’t agree that anybody could depend on this holstering-causes-it-to-fire behavior. Or at least, their use-case requires design compromises that are so unthinkably ridiculous as to make the gun they want something that no reasonable person without that use-case would use.

              It is possible that the entire field of programming is full of ridiculous people. But it seems more likely that C++ is like a gun with no safety, or something along those lines.

              • AnthonyMouse 3 days ago

                A lot of the footguns come from compiler authors wanting to make things UB because it allows them to perform certain optimizations, but then you end up with a lot of things that are formally UB even though in practice they usually do the intuitively expected thing. But then, because the widely done thing is actually UB, the compiler is allowed to do something counterintuitive which causes your program to blow up.

                An obvious example is omitting NULL pointer checks. Passing a NULL pointer to certain system library functions is UB even if it would ordinarily be expected to be reasonable, e.g. memset(NULL, 0, 0), so some compilers will see that you passed a pointer to memset, and passing a NULL pointer to memset is UB, therefore it can omit a subsequent NULL pointer check guarding a call to something else when the something else isn't going to behave reasonably given a NULL pointer.

                This is an insane footgun, but it also allows the compiler to omit a runtime NULL pointer check, which makes the program faster, so people who care most about performance lobby to keep it.

              • kelnos 3 days ago

                > I basically don’t agree that anybody could depend on this holstering-causes-it-to-fire behavior.

                It's an idiom. It's not supposed to be entirely logically consistent. It means what it means because people have decided that it means what it means. Your objections aren't really relevant.

                "Footgun" is I think a fairly recent addition to the English lexicon, but it's based on the centuries-old "to shoot yourself in the foot". It seems silly to argue with centuries of English idiomatic usage; no one, by definition, is going to win an argument against that.

            • wvenable 3 days ago

              Yeah I don't think that's a good analogy. Instead, you have guns that don't let you point at your feet. So you can never shoot yourself there. However, if you ever need to shoot straight down for a legitimate reason, you're out of luck. In C++, you can shoot everywhere without restrictions and sometimes that means shooting yourself in the foot or the head.

          • kelnos 3 days ago

            "Footgun" comes from the English idiom "to shoot yourself in the foot", which means "to act against your own interests" (usually accidentally). (Consider similar idioms, like "to dig your own grave".)

            I think you're being a bit too literal. It's not an analogy at all, and this has nothing to do with firearms best practices. If we were to define a footgun as "a gun that is only capable of shooting you in the foot" (or perhaps more broadly usefully, "a gun that in theory can be useful, but it is nearly impossibly difficult to make it do anything other than shoot you in the foot"), then the entire point of using the term is to describe something that has no useful, logical purpose, and is unsafe to use, even as designed.

            Being "given enough rope to hang yourself" is indeed another good idiom to use for things like this, but the implication is different, I think: when you're given enough rope to hang yourself, the outcome is still very much in your hands. You can intentionally or unintentionally use that rope to hang yourself, or you can be reasonably expected to use that rope in another way that would turn out to be safe or useful.

            "Footgun", by contrast, is used to describe something that has no (or negligible) safe uses. Maybe the original intent behind the design of what's being described that way was to have safe uses, but ultimately those safe uses never really panned out, or were so dwarfed by the unsafe uses that the safe use isn't worth the thing existing in the first place. But, unfortunately, there are some people -- maybe only 0.01% of people -- who are able use it safely, and critically depend on that safe use, so we can't completely destroy all these footguns and save everyone else from the error of their ways. And unfortunately most everyone else sees these 0.01% of uses, and believes they are so useful, so efficient, so brilliant, they want to try it too... but in their hubris they end up shooting themselves in the foot, like most others before them.

          • samatman 3 days ago

            I've long been partial to this formulation:

            > 1972 - Dennis Ritchie invents a powerful gun that shoots both forward and backward simultaneously. Not satisfied with the number of deaths and permanent maimings from that invention he invents C and Unix.

            Some of us learn to lean to the side right before pulling the trigger...

            http://james-iry.blogspot.com/2009/05/brief-incomplete-and-m...

          • sqeaky 3 days ago

            At this point a footgun can stand alone in this industry as a term with its own meaning outside of analogy.

            It is any trap in something technical that is likely to cause problems from perceived normal use.

            Compare to related terms: "Pit of failure", "Turing tarpit", and "Pit of success".

  • FreezerburnV 3 days ago

    “There are only two kinds of languages: the ones people complain about and the ones nobody uses.” - Bjarne Stroustrup

    Not disagreeing that C++ is awful in a lot of ways and super difficult though. But I still weirdly like it, personally. I find it a fun challenge/puzzle to work with.

    • diffxx 3 days ago

      I truly loathe that quote. It is a tautology that is used to deflect legitimate criticism.

      • RogerL 3 days ago

        And it is not true (for any reasonable reading of the quote). There are very popular languages that don't get the deserved hate that C++ does. Sure, Python is slow, packaging/versioning is painful, but it is nothing like C++ complaints.

        I mean, a standard (and stupid IMO) interview question is rate your C++ expertise from 1-10, and if you answer more than about 5-6 you get bounced for lying or not recognizing your limitations, while they gleefully point out Stroustrup wouldn't answer 9-10.

        • wredue 3 days ago

          I mean. Python:

          Bolted on, very terrible OO that should never be touched

          Some of the most batshit insane ideas of encapsulation anyone has ever seen

          Some of the most batshit insane return rules anyone has ever seen

          Encouraged inconsistency from the language in the form of functions like “Len” that are added because sometimes someone feels it reads better?

          Encouraged unwinding exceptions as regular flow control (lol. Yikes)

          It is nearly universally agreed that Python has significant code management issues as code bases scale

          This is all ignoring debates of fundamental typing issues. Personally, I hate what Python does, but some people seem to prefer it.

          Let us not pretend Python doesn’t have some language problems on top of its tooling problems.

          • nottorp 3 days ago

            > It is nearly universally agreed that Python has significant code management issues as code bases scale

            That's the worst part. But you have to agree it's the best for throwing small sized glue code around.

          • gpderetta 3 days ago

            You forgot "it is so slow you might be faster with pen and paper".

        • kelnos 3 days ago

          It absolutely is true! You can certainly argue that different languages get different levels of complaints and hate, but every language that anyone uses gets a non-zero amount of complaints, regardless of severity.

          > Sure, Python is slow, packaging/versioning is painful

          Those are complaints. That is evidence that people complain about Python. You just did it yourself.

          But maybe your complaints about C++ are an order of magnitude more plentiful than for Python. And maybe quite a few of your C++ complaints are about much worse things. But that's not the point: they are all complaints.

          And that's the problem with the Stroustrup quote: he's implicitly saying that all complaints are created equal, and there's no difference between having 10 complaints or 10,000 complaints (where 3 of the first are major, and 5,000 of the second are major).

          It's used, as the GP points out, to shut down legitimate complaints. "Oh, you don't like $REALLY_BIG_HORRIBLE_ISSUE with my language? Psh, whatever, people complain about all languages, I dare you to find another language that you won't find something to complain about." Not the point! Is $REALLY_BIG_HORRIBLE_ISSUE a problem or not? If not, actually explain and justify, with specific arguments, why you don't think it's a problem. And if you do agree it's a problem, stop deflecting attention, admit that's it's a problem, and try to find a solution!

    • umanwizard 3 days ago

      I think we can say Rust is beyond the “nobody uses” stage by now, and it’s much simpler and easier than C++. (And people who use it tend to like it, proving Bjarne wrong).

      • saurik 3 days ago

        I'm sorry; you think people don't complain about Rust? There are tons of articles posted here from people complaining about Rust in various ways. Bjarne wasn't saying whether most people like it... that's orthogonal: I actually like C++, yet I have been complaining about it--at times quite bitterly--since before it was even standardized!

        • tomjakubowski 3 days ago

          Indeed, I am a huge proponent of Rust and have been using it since before 1.0 (even contributed to it, in the past) -- and I complain about Rust a lot, too. Trying to restate Bjarne's point here: if I wasn't using Rust, then I wouldn't have any reason to complain about it.

      • fiddlerwoaroof 3 days ago

        Or, because there’s so many languages around now, they just use something else. I really don’t like working with Rust myself and so I use other languages.

      • dgfitz 3 days ago

        Rust is neither simple nor easy. Full stop.

        • umanwizard 3 days ago

          Where did I say it was?

          • dgfitz 3 days ago

            Well you did say: it’s much simpler and easier than C++

            And if the basis of c++ is c with classes, you’re horribly incorrect. C is a small language, easy to understand and hard to master.

            Rust is fucking miserable to understand, and as such, hard to master.

            Why are you being pedantic?

            • umanwizard 2 days ago

              C++ isn't "C with classes" and hasn't been for years. Yes, Rust is bigger and more complicated than C, but much less so than C++.

              > Why are you being pedantic?

              I'm not. I'm making a real point: Rust is much simpler and easier than C++, so the spirit of Bjarne's quote, which is that for a language to become popular it necessarily has to have as many drawbacks as C++, is wrong.

            • smrq a day ago

              "Rust is more complicated than C++" is a pretty hot take.

      • alex_lav 3 days ago

        Saying people complain about something is not the same as saying nobody likes it…

    • catlifeonmars 3 days ago

      I feel that if the language is a challenge to work with, it better give you your money’s worth. In 2024, there are plenty of other languages with better ROI, if you want a challenge.

      In any case, I think the primary goal of any programming language is to get out of your way and let you tackle more interesting problems related to the problem domain that led you to start writing a program in the first place.

    • zarathustreal 3 days ago

      I find it annoying to have to solve a puzzle to make progress solving my intended puzzle (i.e. whatever I’m computing)

    • nottorp 3 days ago

      > a fun challenge/puzzle to work with

      You're basically saying the language gets in the way of solving your problem :)

  • qsdf38100 3 days ago

    So you hate C++. Great, thanks for your informative insights.

  • queuebert 3 days ago

    I should print this and put it on my wall for all those times when I'm frustrated with Rust lifetimes.

kazinator 3 days ago

> Otherwise, zero-initialize and then default-initialize.

That can't be right ... is it? Things cannot be initialized twice. Isn't it more like "Otherwise, recurse the value-initialization over the bases and members". Then, those that are not classes or arrays get zero-initialized.

  • leni536 3 days ago

    Both "zero-initialize" and "default-initialize" are terms that have precise definitions. In this context if you substitute the definitions it just means first zero-initializing the non-static data members (and zeroing the padding), then calling the default constructor.

    It doesn't mean that the lifetime of the object starts twice, or anything weird like that.

  • SpaghettiCthulu 3 days ago

    I think it would be perfectly legal to zero-initialize the entire thing and then default-initialize, because initialization assumes the value is undefined.

    • bregma 3 days ago

                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          You can only initialize once. After it's been initialized you're just assigning values, and that's not what happens during initialization. It's either a misunderstanding on behalf of the author or the words as written are not conveying the correct idea.
shadowgovt 3 days ago

Realizing the spec for C++23 is about two-and-some-change-times the length of the King James Bible has really reframed my thinking on this language.

For good or for ill, I don't really trust anything that long to be something the average human can wrestle down. And when you mix in undefined behavior and the fact it's still used on safety-critical systems... It's a bit horrifying really.

echelon 3 days ago

A language shouldn't be this complicated. This is dangerous and impossible for teams full of juniors and busy people with deadlines. We're only human.

  • dataflow 3 days ago

    They can't pull the rug out now, but I highly recommend making your own clang-tidies to flag confusing constructs (like defaulted out-of-line constructors) and preventing them from being committed.

  • gosub100 3 days ago

    My guess is all these details are necessary to provide C++ "strong exception guarantee" against partially constructed objects. Perhaps if your member objects can throw exceptions, some of these pedantic initialization rules can come to the rescue and allow, say, a library implementor to limit initialization code to places where exceptions can be handled.

  • 082349872349872 3 days ago

    I believe those teams just use constructors; this is a corner case, not SOP.

    • echelon 3 days ago

      C++ should start pulling things out of the language with new editions. It would improve quality of life dramatically.

      • pjmlp 3 days ago

        Rust style editions don't work with binary libraries, across compilers, or template code across editions, with semantic differences.

        That is why the epochs proposal was rejected.

        Additionally, the main reason many of us, even C++ passionate users, reach out to C++ instead of something else, is backwards compatibility, and existing ecosystem.

        When that is not required for the project at hand, we happily reach out to C#, D, Java, Go, Rust, Zig, Swift, Odin,.... instead.

        • estebank 3 days ago

          > don't work with binary libraries,

          None of the edition changes that Rust has made have any effect on the ABI. It also has no stable Rust ABI, so there wasn't an effort to formalize that, but 1) ABIs should always be versioned (to avoid getting stuck with a bad ABI) and 2) you can use editions for other kinds of change in the meantime.

          > across compilers,

          This is almost tautological. Yes, having two C++ compilers agree to their support of editions is the same as them agreeing to their support of concepts. I don't see how this is a critique of the strategy.

          > template code across editions, with semantic differences.

          Rust defines editions at the compilation unit level: the crate. But it has macros (which are akin to templates) and editions are handled at that boundary (you can have code in one edition invoke macros in another) because the compiler tracks editions at the token level (the information is attached to their Span). There's no reason editions in C++ can't work with templates. You would have to specify the edition of the template, and given C++ semantics you might have to have an opt-in scope to say "use this edition here, override the rest of the file", but it would be possible.

          • pjmlp 3 days ago

            Rust editions are very conservative, expect everything to be built from source, with the same compiler, and don't touch semantic changes across versions, mostly grammar changes.

            Example, there is no story for scenario, where a callback defined in one version, is used in another crate version, calling into code, using yet another version, while passing a closure with a specific type with semantic changes across all versions.

            I am not yet convinced they will scale at the size of industry use cases for C and C++, with a plethora of compilers, and mixing several editions on a 30 year old codebase.

        • jamincan 3 days ago

          If Rust style editions can work across modules, why couldn't they work across binary libraries and so forth? The whole point is to allow the language to progress while maintaining backward compatability.

          • dathinab 3 days ago

            they don't work with a backward compatible application binary interface

            or more specifically they only work with ABI stability if they ABI doesn't change between epochs

            which isn't a issue for Rust because:

            - it is cleanly modularized

            - it build a whole module "at once"

            - it doesn't have a "stable" ABI (outside of "extern/repr C" parts which don't contain non reprC parts rust doesn't even guarantee ABI compatibility between two builds in exactly the same context*(1))

            - tends to build everything from source (with caching)

            - a lot of internees are intentionally kept "unstable" so that they can change at any time

            on the other side due to how C/C++ build things, doesn't have clean module isolation, how it chooses build units, how all of that is combined, how it's normal to include binaries not build by your project (or even you), how such binaries contain metadata (or don't) and how too much tooling relies on this in ways which make changes hard, how it doesn't have build-in package management, how it you specify compiler options and how compiler defaults are handled etc. come together to make that impossible

            in a certain way how you specify that you use C++11,17 etc. is the closest C++ can get to rust editions

            like initially it might seem easy to introduce syntax braking changes (which most rust edition changes boil down to) but then you realize that build units using other editions have to be able to read the header file and the header file e.g. in context of templates can contains any kind of code and that header includes aren't that much different too copy pasting in the header and that you don't have a standard package manager which can trace which edition a header has and endless different build systems and you kinda give up

            purely technically it _is fully possible to have rust like editions in C++_ but practically/organizationally in context of e.g. backward compatibility with build systems it's just way to disruptive to be practical

        • 0xffff2 3 days ago

          > When that is not required for the project at hand, we happily reach out to C#, D, Java, Go, Rust, Zig, Swift, Odin,.... instead.

          Which is all well and good for us, the application developers. But if C++ wants to exist in the future as a thriving language (as opposed to moving in to the nursing home with Cobol and Fortran), then it needs to come up with some solution to remove cruft.

          • Someone 3 days ago

            It has a solution: obsoleting features and then removing them. For examples, see

            https://en.wikipedia.org/wiki/C%2B%2B17#Removed_features

            https://en.wikipedia.org/wiki/C%2B%2B20#Removed_and_deprecat...

            https://en.wikipedia.org/wiki/C%2B%2B23#Removed_features_and...

            Part of their ‘problem’ is that they have lots and lots of users with long-living code bases. That means that, if they move fast and break things, their users won’t move to newer versions of the language.

            Another part is that they want to be able to generate the fastest code possible. That leads to such things as having all kinds of constructors (‘normal’ ones, copy constructors, move constructors), and giving developers the ability to tweak them for maximum performance.

            In addition, a lot of this was invented after the language was in use for decades. I think that makes the constructor story more complicated than needed.

          • pjmlp 3 days ago

            Until those languages decide to be fully bootstraped, alongside Khronos and OpenGroup being welcoming to them for newer standards, C++ won't go away.

      • falcor84 3 days ago

        How about (C++)-- ?

        • Joel_Mckay 3 days ago

          There was a C--, and it was an Assembly macro C like syntax based compiler.

          https://en.wikipedia.org/wiki/C--

          The GNU gcc/g++ was far more important to standardization than most people like to admit.

          Have a great day, =)

  • jjmarr 3 days ago

    Working as a junior in a C++ codebase is great for my career because the skill floor is so high. Because it's so difficult to do anything there's a lot of room to distinguish myself.

    No other language creates as many problems for programmers as C++.

  • userbinator 3 days ago

    I've come to the conclusion that most if not all the complexity is largely to justify the existence of those who work on "moving the language forward".

  • bun_terminator 3 days ago

    True, however in practice this is rarely an issue. You usually only use a tiny subset of construction rules. And if you ever make a mistake, they are easily caught by static analysis tools.

    • smackeyacky 3 days ago

      It’s quite a big issue. It’s actually a bit worse than the article makes out if you throw static objects into the mix and get race conditions where you don’t know which objects get constructed first. C++ has to be approached with caution even by experienced devs.

      • maccard 3 days ago

        I agree with the parent - global initialisation order is a once-bitten-never-again, and the reality is that working in most codebases doesn't require understanding all of these rules - knowing the quirks is usually only required by one or two people and the rest can work with what they've got.

      • TinkersW 3 days ago

        that is a novice issue, it is easily avoided

      • bun_terminator 3 days ago

        experienced (and even the not so much) devs know the perils of static initialization and avoid it.

  • dathinab 3 days ago

    it being this complicated can be fine (if there isn't too much of it)

    but only if not knowing how the complicated parts work doesn't create any subtle issues and has reasonable compiler time errors and isn't fundamental needed to write any code

    Because then you can use the language without needing to know how exactly that complexity works and if you get it wrong you get a reasonable compiler error. And then decide to either spend some time to learn what you didn't know or you just write the code differently. But in either case you don't have a situation with a unexpected runtime error which should be impossible and where you have no good way to know where to even start looking.

    • dathinab 3 days ago

      its funny how people are down voting this when it's exactly the approach rust uses

pierrebai 3 days ago

The article is just plain wrong about classes: if you have declared any constructor, then the language will not provide a default constructor and default-initialization will fail with a compiler diagnostic.

So their claim that "T t;" will "do nothing" is incorrect.

    class T
    {
    public:
        T(int);
    };
    T t;
Will fail.
  • wavemode 3 days ago

    I'm confused at how your comment is relevant to this article. Here you've written `T(int);`, i.e. a constructor with an argument. None of the classes declared in the article have a constructor that takes any arguments. Nor does the text of the article seem to make any statement which contradicts what you've asserted here. And all of the examples in the article compile successfully.

  • shadowgovt 3 days ago

    I think I missed where in the article they did a `T t;`... Doesn't seem to show up with an eyeball scan?

    • vitus 3 days ago

      In the first few paragraphs:

      > Primarily, there are two kinds of initialization of concern: default-initialization and value-initialization. The rules given in the standard look roughly like this:

      > * For any type T, T t; performs default-initialization on t as follows: ...

      As GP mentions, the article's descriptions of default and value initialization are both incorrect for classes that do not have default constructors, as that code will simply not compile.

      • wavemode 3 days ago

        But... all of the classes in the article -do- have default constructors. And all of the examples in the article do compile. So I'm confused at what point you guys are making.

        • vitus 2 days ago

          Quoting the full section on `T t;`:

          - If T is a class type and there is a default constructor, run it.

          - If T is an array type, default-initialize each element.

          - Otherwise, do nothing.

          That decision tree should read: "If T is a class type: it will invoke the default constructor. It is a compile-time error to write this if T does not have a default constructor." Not "if there is a default constructor, run it; otherwise, fall back to doing nothing."

          The "do nothing" applies to scalar types such as ints, and indirectly to scalar member variables that aren't explicitly initialized one way or another. Not to classes that have deleted the default constructor.

        • shadowgovt 2 days ago

          Yes, this. It appears these commenters are claiming that the author said something they did not say. Rather, there is a corner case that the author has not spoken about in this article.

          ... Which is not surprising, given that we're talking about a language with a spec that dwarfs most works of human literature for sheer mass.

jeffbee 3 days ago

I agree there is a lot of complexity in C++ in the year 2024, however I feel that much of the appearance of complexity around initialization is due to the pedantic, dogmatic use of the word "default" by the committee to mean "not".

hwc 3 days ago

I'm so glad I use Go more than C++ these days. In Go, all values are always zero-initialized if you don't explicitly assign a value. If you need a constructor, you write a regular function that returns an explicitly assigned object.

I like keeping the rules of the language simple enough that there is never any confusion.

  • javierhonduco 3 days ago

    Personally I’m not a fan of Go’s default zero-initialisation. I’ve seen many bugs caused by adding a new field, forgetting to update constructors to intialise these fields to “non-zero” values which caused bugs. I prefer Rust’s approach where one has to be explicit.

    That being said it’s way less complex than C++’s rules and that’s welcomef.

    • maccard 3 days ago

      I spent a year and a half writing go code, and I found that it promised simplicity but there an endless number of these kinds of issues where it boils down to "well don't make that mistake".

      • gizmo686 3 days ago

        It turns out that a lot of the complexity of modern programming languages come from the language designers trying to make misaked harder.

        If you want to simplyfing by synthesising decades of accumulated knowledge into a coherent language, or to remove depreciated ideas (instead of the evolved spaghetti you get by decades of updating a language) then fine. If your approach to simplicity is to just not include the complexity, you will soon disciplinary that the complexity was there for a reason.

    • kccqzy 3 days ago

      The problem you are describing in Go is rarely a problem in C++. In my experience, a mature code base rarely has things with default constructors, so adding a new field will cause the compiler to complain there's no default constructor for what you added, therefore avoiding this bug. Primitive types like `int` usually have a wrapper around them to clarify what kind of integers, and same with standard library containers like vector.

      However I can't help but think that maybe I'm just so fortunate to be able to work in a nice code base optimized for developer productivity like this. C++ is really a nice language for experts.

      • dieortin 3 days ago

        Why would you have a wrapper around every primitive/standard library type?

        • kccqzy 3 days ago

          Type safety.

          Compare `int albumId, songId;` versus `AlbumId albumId; SongId songId;`. The former two variables can be assigned to each other causing potential bug and confusion. The latter two will not. Once you have a basic wrapper for integers, further wrappers are just a one-liner so why not. And also in practice making the type more meaningful leads you to shorter variable names because the information is already expressed in types.

    • catlifeonmars 3 days ago

      FWIW there is a linter that enforces explicit struct field initialization.

    • ErikBjare 3 days ago

      Haven't written Go in a long time, but I do remember being bit by this.

    • zarathustreal 3 days ago

      Yea this can be problematic if you don’t have sum types, it’s hard to enforce correct typing while also having correct default / uninitialized values.

    • dgfitz 3 days ago

      Wouldn’t it just be considered bad practice to add a field and not initialize it? That feels strongly like something a code review is intended to catch.

      • javierhonduco 3 days ago

        It’s easy to miss this in large codebases. Having to check every single struct initalisation whenever a field is added is not practical. Some folks have mentioned that linters exist to catch implicit initialisation but I would argue this shouldn’t require a 3rd party project which is completely opt-in to install and run.

      • dieortin 3 days ago

        All bugs are considered bad practice, yet they keep happening :P

  • thowruasdf 3 days ago

    The downside is now all types must dogmatically have a nullary constructor.

    The Default typeclass (or trait) as seen in Haskell and Rust, is a far better design, as this makes the feature opt-in for data types that truly support them.

    • kccqzy 3 days ago

      Don't even need to go that far. In C++ it is common to delete the default constructor anyways. So that achieves the opt-in.

      • dieortin 3 days ago

        Having to delete the default constructor means it’s opt-out, not opt-in

        • kccqzy 3 days ago

          I don't think you really know what a real-life C++ code base looks like. Deleting the default constructor often happens implicitly with no syntax needed. This often happens when you define a custom constructor or when its member doesn't have a default constructor.

  • jeffbee 3 days ago

    Go makes a completely reasonable tradeoff, giving away performance to gain ease of use. C++ also makes a tradeoff that seems reasonable or necessary to its users, that it makes it possible to not be forced to write to the same class member twice, in exchange for a more complex language. Any language that attempts to offer this property unavoidably adopts the complexity as well. See, for example, Java.

MathMonkeyMan 3 days ago

Show me someone who understands the rules, and I'll show you a compiler engineer... who probably doesn't understand the rules.

nxobject 3 days ago

As an aside, I see that DEC front panel you've got there in your blog header.

sixtram 3 days ago

Great link, I'm going to add this to my list of favorite interview questions. (^_-)

vsgherzi 3 days ago

this has got to be one of my favorite blog names i've seen on this site

gnarlouse 3 days ago

this title makes me want to shout

"Call J. G. Wentworth, 877 Cash Now!"

3l3ktr4 3 days ago

This is the best title, OP.