Initialization in C++ is bonkers (2017)

(blog.tartanllama.xyz)

191 points | by todsacerdoti 61 days ago

25 comments

  • shadowdev1 61 days ago
    Heh, low comments on C++ posts now. A sign of the times. My two cents anyway.

    I've been using C++ for a decade. Of all the warts, they all pale in comparison to the default initialization behavior. After seeing thousands of bugs, the worst have essentially been caused by cascading surprises from initialization UB from newbies. The easiest, simplest fix is simply to default initialize with a value. That's what everyone expects anyway. Use Python mentality here. Make UB initialization an EXPLICIT choice with a keyword. If you want garbage in your variable and you think that's okay for a tiny performance improvement, then you should have to say it with a keyword. Don't just leave it up to some tiny invisible visual detail no one looks at when they skim code (the missing parens). It really is that easy for the language designers. When thinking about backward compatibility... keep in mind that the old code was arguably already broken. There's not a good reason to keep letting it compile. Add a flag for --unsafe-initialization-i-cause-trouble if you really want to keep it.

    C++, I still love you. We're still friends.

    • juliangmp 61 days ago
      > When thinking about backward compatibility... keep in mind that the old code was arguably already broken. There's not a good reason to keep letting it compile.

      Oh how I wish the C++ committee and compiler authors would adopt this way of thinking... Sadly we're dealing with an ecosystem where you have to curate your compiler options and also use clang-tidy to avoid even the simplest mistakes :/

      Like its insane to me how Wconversion is not the default behavior.

      • motorest 61 days ago
        > Oh how I wish the C++ committee and compiler authors would adopt this way of thinking...

        I disagree. If you expect anyone to adopt your new standard revision, the very least you need to do is ensure their code won't break just by flipping s flag. You're talking about production software, many of which has decades worth of commit history, which you simply cannot spend time going through each and every single line of code of your >1M LoC codebase. That's the difference between managing production-grade infrastructure and hobbyist projects.

        • dwattttt 60 days ago
          > If you expect anyone to adopt your new standard revision, the very least you need to do is ensure their code won't break just by flipping s flag.

          Why would you expect that a new revision can't cause existing code to compile? It means that "new" revisions can't fix old problems, and one thing you always get more of over time is perspective.

          If you don't want your code "broken", don't migrate to a new standard. That's the point of supporting old standards. Don't hobble new standards because you both want new things, but don't want old things to change.

          • motorest 60 days ago
            > Why would you expect that a new revision can't cause existing code to compile?

            For staters, because that would violate the goals and prioritites of the C++ as established by the C++ standardization committee.

            I could go on and on, but it's you who should provide any semblance of rationale: why do you believe existing software should break? What value do you see in it? Does it have any value at all?

            • dwattttt 59 days ago
              > why do you believe existing software should break? What value do you see in it? Does it have any value at all?

              How exactly can a _new_ standard cause existing software compiling on an old standard to break?

              EDIT: As to the value, I've mentioned elsewhere in this thread; we know a lot more about the practice of programming than when C++ was created and standardised. Why would we say those design decisions can never be questioned or changed? It locks into place choices we never knew were bad, until they caused decades of problems.

              > that would violate the goals and prioritites of the C++ as established by the C++ standardization committee.

              Correct. It's these goals and priorities that are being criticised.

        • nickysielicki 60 days ago
          > You're talking about production software, many of which has decades worth of commit history, which you simply cannot spend time going through each and every single line of code of your >1M LoC codebase.

          They can keep using the old standard.

          • motorest 60 days ago
            > They can keep using the old standard.

            But they cannot upgrade. Ever. At least without requiring major maintenance work. Which means never. Do you understand what that means?

            • nickysielicki 60 days ago
              Every compiler continues to support old standards. What risk am I missing? This feels like a perfectly acceptable outcome for icky legacy code that is not essential enough to maintain.
              • SideQuark 59 days ago
                No they don't, not even close.

                Name a single long running compiler suite that can compile every version it once did.

                • nickysielicki 58 days ago
                  • SideQuark 58 days ago
                    https://gcc.gnu.org/projects/cxx-status.html

                    GCC rarely, like never, supports the complete standards. Every release improves conformance, fixes errors, sometimes drops support for targets, perhaps changes how poorly defined areas work.

                    As a result, likely no version compiles code the same as a previous version across all possible code.

                    You’ll also note GCC doesn’t likely compile the variants of C++ it did before the 1998 version.

                    And this type of page is just the tip of the iceberg https://gcc.gnu.org/gcc-13/porting_to.html. You’ll note changes made that can render previously compilable code no longer compilable.

                    So no, you cannot naively think a new compiler version will simply run all your old code. On big projects upgrading is a huge endeavor to minimize problems.

                    We could do every supported language the same way.

        • johannes1234321 60 days ago
          The option there is better tooling, for which the foundation exists which can do such maintenance somewhat automatically, in the simplest case by just adding the Keywords to request old behavior.

          But the annoyance comes when dealing with multiple compilers and versions. Then you have to add more compatibility macros all over. Say, when being a library vendor trying to support broad range of customers.

          • motorest 60 days ago
            > The option there is better tooling (...)

            The tooling already exists. The bulk of the criticism in this thread is clearly made from a position of ignorance. For example, all major compilers already provide flags to enable checks for uninitialized variables being used. Onboarding a static code analysis tool nowadays requires setting a flag in CMake.

            These discussions would be constructive if those engaging in them had any experience at all with the language and tooling. But no, it seems the goal is to parrot cliches out of ignorance. Complaining that they don't know what a reserved word means and using that as an argument to rewrite software in other languages is somehow something worth stating.

        • mystified5016 60 days ago
          Python seems to still be pretty popular despite breaking most extant code with language updates
      • monkeyelite 61 days ago
        And the cost of this is that every time I open a project in another language it’s broken and I have to make changes to fix all their little breaking changes.
      • zahlman 61 days ago
        >Oh how I wish the C++ committee and compiler authors would adopt this way of thinking

        Many different committees, organizations etc. could benefit, IMO.

    • josefx 61 days ago
      > keep in mind that the old code was arguably already broken

      The code is only broken if the data is used before anything is written to it. A lot of uninitialized data is wrapped by APIs that prevent reading before something was written to it, for example the capacity of a standard vector, buffers for IO should only access bytes that where already stored in them. I have also worked with a significant number of APIs that expect a large array of POD types and then tell you how many entries they filled.

      > for a tiny performance improvement

      Given how Linux allocates memory pages only if they are touched and many containers intentionally grow faster then they are used? It reduces the amount of page faults and memory use significantly if only the used objects get touched at all.

      • riehwvfbk 61 days ago
        You are very very unlikely to trigger Linux overcommit behavior by not initializing a member variable. It's even more unlikely for this to be a good thing.

        In effect, you are assuming that your uninitialized and initialized variables straddle a page boundary. This is obviously not going to be a common occurrence. In the common case you are allocating something on the heap. That heap chunk descriptor before your block has to be written, triggering a page fault.

        Besides: taking a page fault, entering the kernel, modifying the page table page (possibly merging some VMAs in the process) and exiting back to userspace is going to be A LOT slower than writing that variable.

        OK you say, but what if I have a giant array of these things that spans many pages. In that case your performance and memory usage are going to be highly unpredictable (after all, initializing a single thing in a page would materialize that whole page).

        OK, but vectors. They double in size, right? Well, the default allocator for vectors will actually zero-initialize the new elements. You could write a non-initializing allocator and use it for your vectors - and this is in line with "you have to say it explicitly to get dangerous behavior".

        • josefx 61 days ago
          > In effect, you are assuming that your uninitialized and initialized variables straddle a page boundary

          You are assuming that I am working with small data structures, don't use arrays of data, don't have large amounts of POD members, ... .

          > That heap chunk descriptor before your block has to be written, triggering a page fault.

          So you allocate one out of hundreds of pages? The cost is significantly less than the alternative.

          > In that case your performance and memory usage are going to be highly unpredictable (after all, initializing a single thing in a page would materialize that whole page).

          As opposed to initializing thousands of pages you will never use at once? Or allocating single pages when they are needed?

          > Well, the default allocator for vectors will actually zero-initialize the new elements.

          I reliably get garbage data after the first reserve/shrink_to_fit calls. Not sure why the first one returns all zero, I wouldn't rely on it.

          • jchw 60 days ago
            > You are assuming that I am working with small data structures, don't use arrays of data, don't have large amounts of POD members, ... .

            Sounds like a great set of use cases for explicit syntax to opt out of automatic initialization.

          • riehwvfbk 60 days ago
            Reserve vs resize, yes.

            Reserve will not initialize, but then you have to keep track of the real vector size on the side, inevitably leading to bugs. Alternatively, something like this https://stackoverflow.com/questions/15967293/how-to-make-my-... will make resize() leave the elements uninitialized.

            • josefx 60 days ago
              > Reserve will not initialize, but then you have to keep track of the real vector size on the side

              You already do that when you use push_back. It tracks the size for you, overallocates to amortize the cost of growing and most importantly does not initialize the overallocated memory before it is used, meaning pages will not be touched / mapped by the OS unless you actually end up using it. Giving you the benefit of amortizing vector growth without paying for the uninitialized memory it allocates behind the scenes for future use.

              Directly accessing reserved memory instead of using resize was to check if the allocator zero initialized that overallocated memory. That the parts that are used end up initialized at a later point is entirely irrelevant to my point.

              So your previous point:

              > They double in size, right? Well, the default allocator for vectors will actually zero-initialize the new elements.

              They double in capacity, not size when used with push_back. Which means exactly one new element will be initialized no matter how much uninitialized/unused/unmapped capacity the vector allocates for future use.

        • motorest 61 days ago
          > You are very very unlikely to trigger Linux overcommit behavior by not initializing a member variable.

          The problem with your assumption is that you're just arguing that it's ok for code to be needlessly buggy if you believe the odds this bug is triggered are low. OP points out a known failure mode and explains how a feature eliminates it. You intentionally ignore it for no reason.

          This assumption is baffling when, in the exact same thread, you see people whining about C++ for allowing memory-related bugs to exist.

          • yorwba 60 days ago
            Linux overcommit is not a bug, it's a feature. The argument isn't that it's okay for code to be buggy if the odds of triggering the bug are low, it's that it's okay for code to not make use of a feature if the odds of benefiting from that feature are low.
            • motorest 60 days ago
              > Linux overcommit is not a bug, it's a feature.

              You failed to read what I wrote. I referred to why clients would choose to not initialize early to avoid scenarios such as Linux over committing, not that Linux had a bug.

              • yorwba 60 days ago
                Overcommit is an optimization where virtual memory that is allocated but unused is not mapped to physical memory. If you want to avoid this (for some reason), choosing not to initialize early is not going to have the intended effect.
                • motorest 60 days ago
                  > Overcommit is an optimization where virtual memory that is allocated but unused is not mapped to physical memory.

                  Either you're replying without bothering to read the messages you're replying to, or you're failing to understand what is being written.

                  > If you want to avoid this (for some reason), choosing not to initialize early is not going to have the intended effect.

                  Read PP's comment.

    • fooker 60 days ago
      > keep in mind that the old code was arguably already broken.

      Reminder than compiler devs are usually paid by trillion dollar companies that make billions with 'old code'.

    • tails4e 60 days ago
      Especially when doing the right/safe thing by default is at worst a minor performance hit. They could change the default to be sane and provide a backwards compatible switch to pragma to revert to the less safe version. They could, but for some reason never seem to make such positive changes
    • redandblack 60 days ago
      stupid question as I have not tpuched C++ since the 90s - can the IDEs not do this with all these now almost universal linters and AI assists. Maybe something that prompts before a commit and autoprompts before/after fixes to only the inititaization. Maybe simple as a choice in the refactoring menu? Rust - where are you for proposing this fix to C++ or, is it javascript?
    • vrighter 60 days ago
      that's the undefined keyword in zig. I love it. It makes UB opt-in and explicit
    • loeg 61 days ago
      Compilers should add this as a non-standard extension, right? -ftrivial-auto-var-init=zero is a partial solution to a related problem, but it seems like they could just... not have UB here. It can't be that helpful for optimization.
      • Matheus28 61 days ago
        Yes but it’s not portable. If zero initialization were the default and you had to opt-in with [[uninitialized]] for each declaration it’d be a lot safer. Unfortunately I don’t think that will happen any time soon.
        • tialaramex 61 days ago
          You probably don't want zero initialization if you can help it.

          Ideally, what you want is what Rust and many modern languages do: programs which don't explain what they wanted don't compile, so, when you forget to initialize that won't compile. A Rust programmer can write "Don't initialize this 1024 byte buffer" and get the same (absence of) code but it's a hell of a mouthful - so they won't do it by mistake.

          The next best option, which is what C++ 26 will ship, is what they called "Erroneous Behaviour". Under EB it's defined as an error not to initialize something you use but it is also defined what happens so you can't have awful UB problems, typically it's something like the vendor specifies which bit pattern is written to an "unintialized" object and that's the pattern you will observe.

          Why not zero? Unfortunately zero is too often a "magic" value in C and C++. It's the Unix root user, it's often an invalid or reserved state for things. So while zero may be faster in some cases, it's usually a bad choice and should be avoided.

          • motorest 61 days ago
            > Ideally, what you want is what Rust and many modern languages do: programs which don't explain what they wanted don't compile, so, when you forget to initialize that won't compile.

            I think you're confusing things. You're arguing about static code analysis being able to identify uninitialized var reads. All C++ compilers already provide support for flags such as -Wuninitiaized.

            • bluGill 60 days ago
              Uninitialized variables reads can only sometimes be detected statically. -Wuninitialized is still good, but it will miss a lot of cases when the read is in a different translation unit. Whole program analysis could get more cases, but with large programs (multi-million lines of code) it is unlikely we can analyze everything (everything being more than just variables) before the universe ends - see the halting problem.
              • motorest 60 days ago
                > Uninitialized variables reads can only sometimes be detected statically. -Wuninitialized is still good, but it will miss a lot of cases when the read is in a different translation unit.

                From my experience, you're exaggerating the number of false negatives, which is more a factor of how you write your code than what static code analyzers do.

                Also, your comment reads like an attempt at moving the goal post. We start this discussion being very adamant in accusing C++ of being impossible to detect uninitialized var reads. Once that assertion is thoroughly proven to be false and resulting from clueless ignorance, now we try to reframe it as being... Imperfect in some hypothetical scenarios? So what's supposed to be the actual complain?

                The main problem with C++ is that some people somehow are personally invested in criticizing it from a position of complete ignorance. The problem is not technical, it's social.

                • bluGill 59 days ago
                  I agree false negatives are rare - I was intending to temper expectations because perfection is impossible.
            • dwattttt 60 days ago
              > You're arguing about static code analysis being able to identify uninitialized var reads.

              (Safe) Rust does guarantee to identify uninitialised variable reads, but I believe the point is that you can get the optimisation of not forcing early initialisation in Rust, you just have to be explicit that that's what you want (you use the MaybeUninit type); you're forced to be clear that that's what you meant, not just by forgetting parens.

              • tialaramex 60 days ago
                You can even write e.g. this:

                  let mut jim: Goat;
                  // Potentially much later ...
                  if some_reason {
                    jim = make_a_new_goat();
                  } else {
                    jim = get_existing_goat();
                  }
                  use(jim); // In some way we use that goat now
                
                The compiler can see OK, we eventually initialized this variable before we used it, there's no way we didn't initialize it so that's fine, this compiles.

                But, if we screw up and make it unclear whether jim is initialized, probably because in some cases it wouldn't be - that doesn't compile.

                This is the usual "avoid early initialization" C++ programmers are often thinking of and it doesn't need MaybeUninit, since it's definitely fine if you're correct, it's just that the C++ compiler is happy (before C++ 26) with just having Undefined Behaviour if you make any mistakes and the Rust compiler will reject that.

                [Idiomatically this isn't good Rust, Rust is an expression language so we can just write all that conditional if-else block in the initializer itself and that's nicer, but if you're new to this the above works fine.]

              • motorest 60 days ago
                > (Safe) Rust does guarantee to identify uninitialised variable reads (...)

                That's great. You can get that check on C++ projects by flipping a compiler flag.

                Aren't we discussing C++?

                • loeg 60 days ago
                  What flag do you have in mind? (To the best of my knowledge, no such flag exists -- unless you're talking about one of the heavily penalized sanitizer modes.)
                  • motorest 60 days ago
                    > To the best of my knowledge, no such flag exists

                    Your knowledge doesn't seem to even reach the point of having googled the topic. If you googled it once, you'd not be commenting it doesn't exist. Hell, you don't even seem to have read the thread, let alone the discussion.

                    • loeg 59 days ago
                      Again with the personal attacks.

                      I'll note that you have failed to name this "obvious" flag that I'm missing.

        • leni536 61 days ago
          Something like that is heading into C++26 actually. Except the initialization is not to zero, but to some unspecified value (with explicit intention of not allowing leaking garbage) and allowing to trap. It's called "erroneous values".
        • loeg 61 days ago
          I don't really care if it isn't portable. I only have to work with Clang, personally.

          > If zero initialization were the default and you had to opt-in with [[uninitialized]] for each declaration it’d be a lot safer.

          I support that, too. Just seems harder than getting a flag into Clang or GCC.

          • motorest 60 days ago
            > I don't really care if it isn't portable.

            You don't care because your job is not to ensure that a new release of C++ doesn't break production code. You gaze at your navel and pretend that's the universe everyone is bound to. But there are others using C++, and using it in production software. Some of them care, and your subjective opinions don't have an impact in everyone else's requirements.

            > I only have to work with Clang, personally.

            Read Clang's manual and check what compiler flags you need to flip to get that behavior. It's already there.

            • loeg 60 days ago
              Lmao. You've misread both of my upthread comments and have somehow arrived at the conclusion that this justifies personal attacks. There's just no discussion to be had here.
          • ryandrake 61 days ago
            Portability is always for the other guy’s sake, not your own. That’s why so many people don’t care about it.
            • loeg 61 days ago
              Again, I'm not opposed to the idea, it just seems more challenging logistically.
          • TuxSH 60 days ago
            Gcc already has [[gnu::uninitialized]] (clang doesn't, AFAIK), as well as -ftrivial-auto-var-init=pattern which exactly matches the new C++26 semantics, if I'm not mistaken
            • loeg 60 days ago
              > -ftrivial-auto-var-init=pattern

              I believe this only helps for trivial automatic variables; not non-trivial automatic variables (structs/classes) that contain uninitialized trivial members.

              • TuxSH 58 days ago
                Ah, you're right, thanks for correcting me! This also doesn't apply to heap-allocated variables, though I think p2795r2 should cover all these cases.

                I wonder if (for stack variables) this is due to an implementation detail in the compiler. After all, non-trivial classes have 'actual' constructors that run and that is supposed to initialize their respective class instances...

    • MichaelRo 61 days ago
      >> Of all the warts, they all pale in comparison to the default initialization behavior.

      Come on. That's nothing compared to the horrors that lay in manual memory management. Like I've never worked with a C++ based application that doesn't have crashes lurking all around, so bad that even a core dump leaves you clueless as to what's happening. Couple OOP involving hundreds of classes and 50 levels deep calls with 100s of threads and you're hating your life when trying to find the cause for yet another crash.

      • bluGill 60 days ago
        I can write bad code in rust too. Rust makes it more difficult, but if you try hard you can abuse it to get the same hundreds of classes and 50 level deep calls, and 100s of threads. You can even do manual memory management in Rust - it isn't built into the language but you can call system APIs to allocate memory if you really want to be stupid. Don't do that is the answer.

        Good programmers have long ago written best practices guides based on hard learned experience. Newer languages (like Rust) were designed by people who read those guides and made a language that made using those features hard.

      • kaashif 61 days ago
        50 levels deep? With some of the template metaprogramming I've seen, looking at just the types for just one level will not only fill your screen, but take up megabytes on disk...
      • motorest 60 days ago
        > Come on. That's nothing compared to the horrors that lay in manual memory management. Like I've never worked with a C++ based application that doesn't have crashes lurking all around, so bad that even a core dump leaves you clueless as to what's happening.

        Have you tried fixing the bugs in your code?

        That strategy has been followed by people writing code in every single language, and when used (even with C++) you do drive down the number of these crashes to a residual/purely theoretical frequency.

        Scenarios such as those you've described are rare. There should be more to them than the tool you're using to do your job. So why blame the tool?

  • nlehuen 61 days ago
    Not to worry, there is a 278 page book about initialization in C++!

    https://leanpub.com/cppinitbook

    (I don't know whether it's good or not, I just find it fascinating that it exists)

    • bhk 61 days ago
      Wow! Exhibit 1 for the prosecution.
    • kazinator 61 days ago
      C++ doesn't have initiation hazing rituals, but initialization hazing rituals. (One of which is that book.)
    • codr7 60 days ago
      That's what I've been saying, every line of C++ is a book waiting to be written.
    • nitrogen99 61 days ago
      Well, authors are incentivized into writing long books. Having said that it obviously doesn't take away the fact that C++ init is indeed bonkers.
      • harry8 61 days ago
        What would be the incentive for making this a long book? Couldn't be money.
        • jcelerier 61 days ago
          It is actually. It's been shown that longer books make more sales as they are considered more trustworthy, so authors are incentivized to artificially drag them longer than they actually require
          • harry8 60 days ago
            Ever written one? How much did you make?
            • bluGill 60 days ago
              The money isn't from book sales. The money is you can charge higher consultant fees because "you wrote the book". If you don't play the game of course you won't make money, but writing a book is one step. (the full game has lots of different paths, there are other ways to make a lot of money without writing a book)
              • harry8 59 days ago
                right so no money in increased sales, which was obviously the point of the comment.
        • Analemma_ 61 days ago
          I imagine if I'd managed to actually memorize all of C++'s initialization rules, I'd probably have to write a book too just to get it all out, or I'd lose my sanity.
          • sph 60 days ago
            Then you can proudly put “C++ initialization consultant” on your resumé and get paid $1000 a day fixing class constructors at Fortune 500 companies.
          • codr7 60 days ago
            There are no limits to how much of an specialized expert you can become in C++.

            Knowing all of it just isn't possible from my experience.

        • nitwit005 60 days ago
          Imagine you're in a world where magazines are dead, but books are still a thing, and stores won't stock a thin book.
  • agent327 61 days ago
    The answer to this is to replace default-init by zero-init. This removes all special cases and all surprise, at a cost that is minimal (demonstrated experimentally by its implementation in things like Windows and Chrome) or even negative. Doing so would make software safer, and more reproducible, and it would make the object model more sound by removing the strange zombie state that exists only for primitive types.

    Of course we should provide a mechanism to allow large arrays to remain uninitialized, but this should be an explicit choice, rather than the default behaviour.

    However, will it happen? It's arguably the easiest thing C++ could do to make software safer, but there appears to be no interest in the committee to do anything with safety other than talk about it.

    • shultays 60 days ago

        Of course we should provide a mechanism to allow large arrays to remain uninitialized, but this should be an explicit choice, rather than the default behaviour.
      
      First you are saying "cost is minimal even negative" and then already arguing against it on the next paragraph.
      • ddulaney 60 days ago
        The general cost over a several large codebases has been observed to be minimal. Yet, there are specific scenarios where the costs are real and observable. For those rare cases, an explicit opt-in to risky behavior makes sense.
        • shultays 60 days ago

            The general cost over a several large codebases has been observed to be minimal
          
          Is this unexpected? A large code base has a lot of other things and it is normal that such changes will be a rounding error. There are lots of other bottlenecks that will just overwhelm such a such change. I don't think "it is not affecting large code bases as much", you can use that argument for pretty much anything that adds an overhead

          Not to mention if you change every int a to int a=0 right now, in those code bases, a=0 part will likely to be optimized away since that value is not being (shouldn't be) used at all and likely will be overwritten in all code paths

    • monkeyelite 60 days ago
      We all agree, poor defaults were chosen in C++ across the board. we have learned a lot about languages since then.

      The question is what to do about it - balancing the cost of change to code and to engineers who learned it.

      > but there appears to be no interest in the committee to do anything with safety other than talk about it.

      There is plenty of interest in improving C++ safety. It’s a regular topic of discussion.

      Part of that discussion is how it will help actual code bases that exist.

      Should the committee do some breaking changes to make HN commenters happier, who don’t even use the language?

      • 112233 60 days ago
        There is no hope for committee. In C++33 we will probably have variables defined as

            int const<const> auto(decltype(int)) x requires(static) = {{{}}};
        
        And when asked how on earth did this happen and why, there will be the same "we must think about the existing code, the defaults were very poor"

        Meanwhile they absolutely could make sane defaults when you plonk "#pragma 2033" in the source (or something, see e.g. Baxter's Circle compiler), but where would be the fun of that.

        They still use single pass compiling (and order of definitions) as the main guiding principle...

        • intelVISA 60 days ago
          The root issue is that the committe has no incentive to improve the language when the current situation enriches its key members, C++ is just the vehicle they co-opted to sell books, or consulting, on solving problems that they perpetuate.
        • monkeyelite 60 days ago
          I’m with you - the features they add are baffling.

          > we must think about the existing code, the defaults were very poor"

          What does adding bad features have to do with maintaining defaults?

          • 112233 60 days ago
            A lot of totally deranged contortions in C++, if you are lucky to find someone sharing the "secret" committee discussions, end up being caused by interaction of ODR, ADL, decay and other equally disease-sounding features. Like, I'd like to know how many people that can use C++ comfortably could write implementation of std::move and std::forward without looking. Or even tell which is which. Actually, let's try:

                template<typename T> T&& function_1(remove_reference_t<T> &x) {
                    return static_cast<T &&>(x);
                }
                template<typename T> remove_reference_t<T> && function_2(T &&x) {
                    return static_cast<remove_reference_t<T &&> >(x);
                }
            
            Which one is move and which is forward?

            Now, since all this jenga must remain functional, the only option they have to try and fix stuff that is a complete misfeature (like "this" being a pointer) is either requiring you to write make_this_code_not_stupid keywords everywhere ("explicit", "override", ...), or introduce magic rewriting rules (closures, range for, ...)

            some fixes go much lower, "decltype(auto)" being a pinnacle of language design that will unlikely be surpassed.

            • int_19h 60 days ago
              Why do you believe that `override` belongs in that category?
              • 112233 60 days ago
                Because the more desirable fix was requiring explicit annotation for "new" virtual functions, and make silent hiding an error. Instead, we have situation where you have to write "override" everywhere to get the desired sane behaviour.
                • int_19h 59 days ago
                  I agree that explicit annotation for overrides is desirable, but disagree that it alleviates the need for `override`. The fact that a method is introduced for the purpose of overriding is IMO important enough to justify an explicit annotation regardless, much like C# does it with `new` and `override`.

                  It's also not clear to me what this has to do with ODR, ADL etc. That is to say, it's obviously a problem with a historical design decision (implicit overrides) that now has to be kept for backwards compatibility reasons, making it impossible to require a `new` annotation above and beyond opt-in compiler warnings. But that particular problem is not a uniquely C++ one, seeing how implicit overrides are actually more common than explicit ones in mainstream OOP languages, largely because they have all inherited this choice from either Simula (as C++ did) or Smalltalk. C# is more of an exception in that regard.

      • agent327 60 days ago
        I was not proposing sweeping changes to all the defaults in C++, I was proposing to adopt a single, specific change. That change does not break any existing code, removes pitfalls from the language, and has already been tried by industry and found to be beneficial. Why is it not in C++26?

        https://open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2754r0... provides what appears to be the answer to this question: "No tools will be able to detect existing logical errors since they will become indistinguishable from intentional zero initialization. The declarations int i; and int i = 0; would have precisely the same meaning." ...yes, they would. _That's the point_. The paper has it exactly the wrong way around: currently tools cannot distinguish between logical error and intentional deferred initialization, but having explicit syntax for the latter would make the intention clear 100% of the time. Leaving a landmine in the language just because it gives you more warnings is madness. The warning wouldn't be needed to begin with, if there were no landmine.

        I'm not sure what you mean with "who don't even use the language". Are you implying that only people that program professionally in C++ have any stake in reliable software?

        • monkeyelite 60 days ago
          > Are you implying that only people that program professionally in C++ have any stake in reliable software?

          No. I’m saying people who don’t understand C++ aren’t going to have good ideas about how to make initialization better.

          And yes - comments which simply name call can be safely discard.

          • agent327 60 days ago
            I'm confused. Who's name calling? Are you trying to imply stuff about me (and if so, what gave you the idea I don't understand C++?)?

            Please read this thread, in particular the comments by James20k (who, incidentally, is a C++ standards committee member): https://www.reddit.com/r/cpp/comments/yzhh73/p2723r0_zeroini...

            This was three years ago. He enumerates numerous reasons for introducing zero-initialisation as I described, and yet somehow here we are, with C++26 just around the corner, and safety on everyone's lips. Is it part of C++26 now? Nope...

      • BlackFly 60 days ago
        > Should the committee do some breaking changes to make HN commenters happier, who don’t even use the language?

        As phrased, you clearly want the answer to this question to be no, but the irony there is that that is how you kill a language. This is simply survivor bias, like inspecting the bullet damage only on the fighter planes that survive. You should also be listening to people who don't want to use your language to understand why they don't want that, especially people that stopped using the language. Otherwise you risk becoming more and more irrelevant. It won't all be valuable evidence, but they are clearly the people that cannot live with the problems. When other languages listen, better alternatives arise.

        • monkeyelite 60 days ago
          > you clearly want the answer to this question to be no

          Uh yes. It’s phrased that way because it’s absurd. About half the comments in this section are a form of name calling by people who don’t understand constructors/destructors.

          Those people who have no insight into how to make initialization better.

          > Otherwise you risk becoming more and more irrelevant

          Relevancy is relative to an audience. You want to listen to people who care and have your interests in mind.

          C++ and Java are the most relevant languages in terms of professional software engineering.

          • BlackFly 57 days ago
            You missed the forest for the trees. Since my claim is that

            > people who care and have your interests in mind

            that have valuable insights about the problems of the language and so understand constructors and destructors but don't make you susceptible to survivor bias are

            >> especially people that stopped using the language.

            People that don't want to learn the language in the first place offer a different kind of insight, insight into a world where constructors and destructors aren't needed.

    • BlackFly 60 days ago
      I would say the answer is to replace both with explicit init unless you explicitly say some equivalent of "Trust me, bro," to the compiler. Some structs/data (especially RAII structs backing real resources) have no sensible default or zero.

      But yeah, most structs have a good zero value so a shorthand to create that can be ergonomic over forced explicitness.

      • agent327 60 days ago
        That would be a breaking change though. Having default zero-init would apply to existing source and convey it's benefits simply by recompiling, without any engineering hours being required.
  • vitaut 61 days ago
  • gnabgib 61 days ago
    Small discussion at the time (42 points, 6 comments) https://news.ycombinator.com/item?id=14532478

    Related: Initialization in C++ is Seriously Bonkers (166 points, 2019, 126 points) https://news.ycombinator.com/item?id=18832311

  • ts4z 61 days ago
    This is a specialization of the general statement that C++ is bonkers.
    • MichaelMoser123 60 days ago
      and putting structure instances into an array so that you can refer to them via indexes of the array entries (as the only escape from being maimed by the borrow checker) is normal?
      • ts4z 60 days ago
        C++ would be bonkers even if Rust did not exist.
        • MichaelMoser123 60 days ago
          you have a point. The usual approach was to choose a subset of C++ features, so it becomes appropriate for a given project. But yes, the language is huge - as it tries to suite everyone but no one in particular (which is insane)
    • lblume 61 days ago
      Unlike Rust, C++ at least has specialization...
  • kazinator 61 days ago
    > This rule makes sense when you think about it

    No, it is bonkers; stick to your consistent point, please.

    These two should have exactly the same effect:

      bar() = default;       // inside class declaration
    
      bar::bar() = default;  // outside class declaration
    
    The only difference between them should be analogous to the difference between an inline and non-inline function.

    For instance, it might be that the latter one is slower than the former, because the compiler doesn't know from the class declaration that the default constructor is actually not user-defined but default. How it would work is that a non-inline definition is emitted, which dutifully performs the initialization, and that definition is actually called.

    That's what non-bonkers might look like, in any case.

    I.e. both examples are rewritten by the compiler into

      bar() { __default_init; }
    
      bar::bar() { __default_init; }
    
    where __default_init is a fictitious place holder for the implementation's code generation strategy for doing that default initialization. It would behave the same way, other than being inlined in the one case and not in the other.

    Another way that it could be non-bonkers is if default were simply not allowed outside of the class declaration.

      bar::bar() default;  // error, too late; class declared already!
    
    Something that has no hope of working right and is easily detectable by syntax alone should be diagnosed. If default only works right when it is present at class declaration time, then ban it elsewhere.
  • ValtteriL 61 days ago
    The book Beautiful C++: 30 Core Guidelines for Writing Clean, Safe, and Fast Code recommends initializing/providing default values for member variables in default member initializers intead of the initializer list used here.

    """ Default member initializers define a default value at the point of declaration. If there is a member that cannot be defined in such a way, it suggests that there may be no legal mechanism by which a default constructor can be defined. """

  • markhahn 61 days ago
    Most of that actually just makes sense if you approach it from the historic,low-level, minimalist direction. But maybe if you're coming from some other, higher-comfort language...
    • frollogaston 61 days ago
      Coming from C, none of this made sense to me. Wut is `foo() = default;`? If you want a default value of 0, why isn't it just

        struct foo {
          int a = 0;
        };
      
      In Python, which is higher-level ofc, I still have to do `foo = 0`, nice and clear.
      • Maxatar 61 days ago
        `foo() = default;` is an explicit way to generate a default constructor for `foo`. The default constructor works by recursively calling the default constructors for all class instance fields. In C++ there are a bunch of rules about when a class has a default constructor or not, but by explicitly declaring one you are guaranteed to have it so long as all your class instance fields have default constructors.

        Your example of having a field called `a` that is initialized to 0 is perfectly valid C++ as well but it's not the same as an explicitly declared default constructor.

        • AlienRobot 60 days ago
          Yeah, this is obviously nonsensical.

          If the constructor is "default" then why do you need to explicit set it? Yeah, I know some objects don't have constructors, but it would make more sense if you had to explicit delete the default constructor, or the keyword "trivial" was used instead of default.

          • frollogaston 60 days ago
            Idk what other language has this "default" keyword. Seems unnecessary.
      • motorest 61 days ago
        > Coming from C, none of this made sense to me. Wut is `foo() = default;`?

        C does not have member functions, let alone special member functions such as constructors. It's understandable that someone with a C background who never had any experience using a language besides C would struggle with this sort of info.

        C++ improved upon C's developer experience by introducing the concept of special member functions. These are functions which the compiler conveniently generates for you when you write a simple class. This covers constructors (copy constructors and move constructors too). This is extremely convenient and eliminates the need for a ton of boilerplate code.

        C++ is also smart enough to know when not to write something it might surprise you. Thus, if you add anything to a basic class that would violate assumptions on how to generate default implementations for any of these special member functions, C++ simply backs off and doesn't define them.

        Now, just because you prevented C++ from automatically defining your constructors, that does not mean you don't want them without having to add your boilerplate code. Thus, C++ allows developers to define these special member functions using default implementations. That's what the default keyword is used for.

        Now, to me this sort of complaining just sounds like nitpicking. The whole purpose of special member functions and default implementations is to help developers avoid writing boilerplate code to have basic implementations of member functions you probably need anyway. For basic, predictable cases, C++ steps in and helps you out. If you prevent C++ from stepping in, it won't. Is this hard to understand?

        More baffling, you do not have to deal with these scenarios if you just declare and define the special member functions you actually want. This was exactly how this feature was designed to work. Is this too hard to follow or understand?

        I think the problem with C++ is that some people who are clearly talking out of ignorance feel the need to fabricate arguments about problems you will experience if you a) don't know what you are doing at all and aren't even interested in learning, b) you want to go way out of your way to nitpick about a tool you don't even use. Here we are, complaining about a keyword. If we go through the comments, most of the people doing the bulk of the whining don't even know what it means or how it's used. They seem to be invested in complaining about things they never learned about. Wild.

        • frollogaston 60 days ago
          Yes it's too hard to follow or understand. Of all the langs I've used, C++ is the only one I can't trust to default-init things the expected way, and I use it every day.
          • motorest 60 days ago
            > Yes it's too hard to follow or understand.

            I don't think so. There might be tough topics, but special member functions ain't it. I mean, think about what you are saying.

            * Is it hard to write a constructor?

            * Is it hard to understand that a C++ compiler can help you out and write one for you when you write a basic class?

            * Is it hard to understand if you step in and start defining your own constructors and destructors, C++ compilers step out of your way and let you do the work?

            Let's now frame it the other way around: why don't you write all the constructors and destructors you need? Would it be nice if the compiler did at least some of that work? I mean, not all of it, but at least just the basic stuff. It could safely fill in constructors and destructors at least for the most basic cases without stepping on your toes. When you need something fancy or specialized, you could just do the work yourself. Does this sound good? Or is this too hard to understand?

      • zabzonk 61 days ago
        > If you want a default value of 0, why isn't it ...

        It is.

        • frollogaston 61 days ago
          I know that works too, but there are also other unclear ways to do it.
      • codr7 60 days ago
        I agree in the example given.

        But add templates to the mix and a generic default becomes quite useful.

    • 112233 60 days ago
      how does

          int x = x;
      
      has ever made sense? In hostoric minimalist direction?

      (and if you have to ask, x is initialized — with the uninitialized value of x)

      • int_19h 60 days ago
        It is very occasionally useful when you need to define a self-referential data structure, i.e. something like:

          struct foo { foo& p; ... };
          
          foo x{x, ...};
        
        Still, this is hardly a good justification for it to be the default behavior. There's a reason why ML has `let rec`.
        • 112233 60 days ago
          Thank you for this, it illustrates an important distinction. (Your example would be clearer if you used a pointer instead of a reference)

          Address of variable does not depend on it's value, and can be known and used before the variable is defined. At no point in your example value of "x" is used before the end of initialization.

          However, in order to allow that, the language goes and allows the use of uninitialized value too. That is just plain horrible design.

          • int_19h 59 days ago
            > Your example would be clearer if you used a pointer instead of a reference

            I actually used the reference deliberately here because, unlike a pointer, a reference cannot be rebound.

            But yes, I agree, this isn't good design. Off the top of my head I can think of at least one way to enable the same thing with a tiny bit of special (and rather obvious) syntax without introducing a footgun. It wouldn't even need any new keywords!

              foo x{this x, ...};
  • e-dant 61 days ago
    Let the language die, hope it goes quicker than cobol.
    • trealira 61 days ago
      C++ is not going anywhere. It's even still used in gamedev to make new games. It's used in HPC and scientific computing. Windows applications often use it. And so on.
    • compiler-guy 61 days ago
      https://www.phoronix.com/news/GCC-15-Merges-COBOL

      COBOL Language Frontend Merged For GCC 15 Compiler Written by Michael Larabel in GNU on 11 March 2025 at 06:22 AM EDT. 33 Comments

    • jandrewrogers 61 days ago
      For better or worse, modern C++ is still the most capable and expressive systems language. To replace it, we need (at a minimum) a language with similar capability and expressiveness in the low-level systems domain. The options are really thin; Zig probably comes the closest but it is a bit austere coming from recent C++ versions.

      I think we can do significantly better than C++ as a systems language. We just haven’t landed on a language that really nails the design.

      • johnnyjeans 60 days ago
        > For better or worse, modern C++ is still the most capable and expressive systems language.

        Not really. Rust, ATS, D, and some implementations of Lisp and even Haskell if you slay the dragon of actually learning GHC. Modern C++ is honestly overrated in my opinion as an extensive user of the language with 300k lines in a modern dialect alone. It's a pain in the ass to step through in a visual debugger and core dumps may as well be useless no matter the platform. It's extremely grating to read and to write. Compilers have a tendency to crash like crazy if you get too cute. There's still no compile-time (or runtime) reflection. I would literally rather be writing proofs for a dependent type system than deal with heavy template metaprogramming.

        • jandrewrogers 60 days ago
          C++20 metaprogramming is pretty clean and straightforward. It became usable around C++17, though the learning curve is a bit steep. I can’t remember the last time I saw a compiler crash, and I’ve used many compilers on code bases that use a lot of dark corners of C++. The occasional compiler bug is a thing though.

          I didn’t say C++ was amazing, just that recent dialects are better than the alternatives in practice, many of which I have also used for similar code.

          Rust is not a substitute for C++ unless your code isn’t all that low-level, it lives closer to the abstraction level of Java. There are a number of odd gaps in the Rust feature set for low-level systems programming (database kernels in my case), the rigid ownership/lifetime model doesn’t play nicely with fairly standard systems-y things like DMA, and the code is always runs slower for some inexplicable reason.

          I’d love something to replace C++ but to be a candidate it can’t be less expressive, slower, and so rigid that it is difficult to do some ordinary systems-y things. A nerfed programming language is not the answer to this question.

          • johnnyjeans 60 days ago
            > C++20 metaprogramming is pretty clean and straightforward.

            I disagree. Having to juggle two type systems and two languages simultaneously is inherently unclean, to say nothing of how noisy and unergonomic templates end up being. For anything non-trivial they're a mess. Imagine I handed you a codebase with a few hundred templated structures, averaging about 50 parameters each, many of which are variadic, many of which are nesting as parameters within each other. As you climb through this pile, you end up in a totally different layer, where these more complicated templated structures are passing around and working on a much larger collection of simpler templated structures and constexpr. You're not going to have a fun time, no matter how comfortable you are with templates.

            > I can’t remember the last time I saw a compiler crash

            How long of a parameter pack do you think it will take to cause a crash? Of course eventually the compiler has no more memory to spare, and that'll be a crash. Clang will helpfully complain:

              warning: stack nearly exhausted; compilation time may suffer, and crashes due to stack overflow are likely
            
            But I assure you, the compiler will crash long before it runs out of memory. If you're in the domain of non-trivial meta-templates, these packs can explode to ludicrous sizes from recursive concatenation, and especially if you have a logic error somewhere that generates and concatenates packs you didn't want. And that's just the obviously intuitive example. Now contextualize this in the codebase I describe above.

            The more you push templates as a turing-complete language to their maximum potential, the more compiler issues you run into. Templates are probably the most rigid and unstable metaprogramming facility I've experienced, honestly. GCC and cl.exe are the worst for it.

            > it lives closer to the abstraction level of Java.

            That's interesting, because Java isn't very abstracted over the JVM. It's that extra layer of the JVM being an abstraction over another CPU architecture that makes Java abstract. C and C++ are arguably more abstracted, because they have to be. But just like Rust, they provide good general semantics for any register machine that the abstractions can mostly be compiled away.

            > the rigid ownership/lifetime model doesn’t play nicely with fairly standard systems-y things like DMA

            Choosing references as your default is insane, honestly. Safe Rust is akin to writing formally verified software in the mental and ergonomic overhead it incurs. That's not coincidental. I've come to the conclusion one of the biggest mistakes of the community is not pushing pointer-only Rust harder, given they want it to be taken seriously as a systems language.

            Rust's safety is irrelevant as far as I'm concerned. It's nice that it has it, but I (and everybody else working in C++) is used to working without it and don't really miss it when it's gone.

            > and the code is always runs slower for some inexplicable reason.

            As you come to understand Rust as a set of assembler macros and rewrite rules applied over them, I've found they're neither more or less granular than the C/C++ ones. They're just a different family, and it stems from a different style of assembly programming (of which there are many). If you've ever translated a lisp with manual memory management onto a register machine, it's similar to that way of thinking about how to compose LOAD/STORE/JUMP + arithmetic, which it got from the ISWIM/ML heritage. Just like with C/C++, everything else is syntax sugar and metaprogramming. At the core of it, you should still be able to translate your Rust code into your target's machine code in your head relatively trivially.

            • trealira 60 days ago
              > Choosing references as your default is insane, honestly. Safe Rust is akin to writing formally verified software in the mental and ergonomic overhead it incurs. That's not coincidental. I've come to the conclusion one of the biggest mistakes of the community is not pushing pointer-only Rust harder, given they want it to be taken seriously as a systems language.

              That's an interesting perspective I hadn't heard before, but I think there are a couple problems. One, the culture is really against using unsafe unless it's impossible to write the code using safe Rust. This happened with the Actix web drama in 2020 [1]. And, there is the opinion that unsafe Rust is harder than C [2]. Not just that, but unsafe Rust is really ugly and annoying to use compared to safe Rust. I think that hinders the potential adoption of your idea, too.

              [1]: https://steveklabnik.com/writing/a-sad-day-for-rust/

              [2]: https://chadaustin.me/2024/10/intrusive-linked-list-in-rust/

    • bdangubic 61 days ago
      “quicker than cobol” means it will die in the next 100 years (maybe) :)
    • greesil 61 days ago
      I don't think it's going anywhere, too much existing code that's still useful. People STILL use Fortran 77 for goodness sake.
      • lblume 61 days ago
        Fortran may still be used but is considered functionally dead nonetheless. Nobody is hiring Fortran devs anymore (and those who do put themselves in a really hard market position). Yet, learning C++ might still be a more valuable skill than learning Rust.
        • pjmlp 60 days ago
          Yet, Fortran 2023 is the latest standard, and Fortran support is one of the reasons why CUDA won over OpenCL.
        • kergonath 61 days ago
          Fortran 77 is dead. Fortran is not, and yes, people still get hired to use it. Just maybe not in your field.
    • gosub100 61 days ago
      COBOL is alive and well. Why would a company rewrite a codebase that has decades of error free functionality? What do they get?
      • cheema33 61 days ago
        > Why would a company rewrite a codebase that has decades of error free functionality? What do they get?

        All well and good if it is something you do not have to modify/maintain on a regular basis. But, if you do, then the ROI on replacing it might be high, depending on how much pain it is to keep maintaining it.

        We have an old web app written in asp.net web forms. It mostly works. But we have to maintain it and add functionality to it. And that is where the pain is. We've been doing it for a few years but the amount of pain it is to work on it is quite high. So we are slowly replacing it. One page at a time.

        • gosub100 61 days ago
          the insurance companies running COBOL don't care. it's cheaper to pay a cowboy $X00,000/yr to keep the gravy dispenser running than trying to modify it. by definition, this is code that's been in use for decades. Why change it?
    • pjmlp 60 days ago
      First someone needs to rewrite famous open source compiler development tools like GCC and LLVM into something else.
    • indigoabstract 61 days ago
      I think this saying applies here pretty well: Horses don't die when the dogs want them to.
    • jimbob45 61 days ago
      I suspect the committee agrees with you. I think they’ve anticipated a competitor coming to kill C++ for two decades now and see themselves as keeping C++ on life support for those who need it.

      It’s shameful that there’s no good successor to C++ outside of C# and Java (and those really aren’t successors). Carbon was the closest we came and Google seems to have preemptively dropped it.

      • wffurr 61 days ago
        The latest Carbon newsletter is here, from March: https://github.com/carbon-language/carbon-lang/discussions/5...
      • jhasse 60 days ago
        Carbon doesn't have exceptions which makes it DOA for some.

        I think Cpp2 / cppfront will become the successor language instead.

      • compiler-guy 61 days ago
        Carbon is still quite active.
        • jimbob45 61 days ago
          The addition of a safety design is a shift in our milestones for v0.1, and you can see the difference here. Both of these are fundamental parts of v0.1, and will take long enough that the earliest date for v0.1 is pushed out to the end of 2026

          Look, no one is more excited than me for this, but this is reaching Star Citizen levels of delays.

  • monkeyelite 60 days ago
    Initialization does look insane. But as with most C++ complexity this is inherent.

    Lists of the “good parts” of C++ over C usually include RAII. But f we imagine starting with C and adding C++ features to see when complexity explodes. I think the worst offender is constructor/destructor.

    They require the language to perfectly track the lifetime of each member of every structure. If you resize a vector, every entry must call a constructor. If exceptions are possible, but insert little cleanup calls into all possible code paths.

    Want to make a copy of something? Who is responsible for calling constructor/destructor. Want to make a struct? What if one member requires construction? How do you handle a union?

    The result is micromanaging and turning most operations into O(n) init/cleanup calls.

    The modern C approach avoids all of this and allows you to manage pieces of memory - rather than values. Zero initialize or leave uninitialized.

    So what do we lose? Well classes own resources. If you have a vector<MyObject> and MyObject has a member vector<Items> then we should be able to cleanup without looking inside each member of each element.

    I think we should separate resource allocation from use. Allocators are the things that care about cleanup, move, etc. This should be the exception - rather than the default way to think about structs.

    • AlienRobot 60 days ago
      I'm not very good with C++, so one time I tried to use RAII to do all sorts of neat GUI things, like freeze property events and ref count uses of files to know when to freed them from memory.

      Essentially instead of doing

          object.freeze();
          object.setProperties(...);
          object.thaw();
      
      I tried to do

          {
              Freezer freezer(object);
              object.setProperties(...);
          }
      
      EVERY time I need a start/end matching function I used RAII instead because it looked neat. I tried to make "with" from Python in C++.

      The problem is that exceptions thrown in a destructor can't be caught, which made this coding style practically impossible to use. Thawing the properties meant dispatching property change events, which meant that event handlers all around the app would be executed inside a destructor unaware that they were being executed inside a destructor. If any of these threw an exception, the whole thing crashed. In hindsight dispatching these events from a setter instead of from the main event loop also creates all sorts of trouble.

      • int_19h 60 days ago
        The closest equivalent to `with` from Python would be a higher-order function defined such that it can accept a lambda (i.e. it needs to be a template). Then you'd write something like:

           object.batchEvents([&]{
             object.setProperties(...);
           });
        
        (for bonus points, pass object reference as argument to the lambda)
    • gpderetta 60 days ago
      > Want to make a copy of something? Who is responsible for calling constructor/destructor.

      What do you mean? The compiler will do it for you.

      > This should be the exception - rather than the default way to think about structs.

      the way that RAII in C++ recursively construct and destroys arbitrary object graphs is extremely powerful. It is something that very few other languages have (Rust, any other?). It should definitely be the default.

      > I think we should separate resource allocation from use. Allocators are the things that care about cleanup, move, etc.

      I'm not sure what you mean by use. If you mean we should separate allocation from construction, I agree! But then so does C++. They are tied by default, but it is easy to separate them if you need it.

      • monkeyelite 60 days ago
        > What do you mean? The compiler will do it for you.

        It will. And the only cost to you is you have to understand initializer lists, pod types, copy constructors, move constructors, launder, trivially_destructible, default initialization, uninitialized_storage, etc.

        > the way that RAII in C++ recursively construct and destroys arbitrary object graphs is extremely powerful

        And extremely complex.

        The big fallacy here is that you would want to manage resources at the individual node level - rather than for a batch.

        > I'm not sure what you mean by use

        It’s similar to the idea of arenas (also made difficult by constructors btw). You can make sophisticated systems for managing allocations of individual nodes in a graph - like reference counted smart pointers. Or you can avoid the problem entirely by deallocating the whole group at once.

        Imagine if C++ structs were required to be pod and classes were not. Then you could always know that a struct can be trivially allocated/deallocated etc.

        Then you could design data structures for pod types only that didn’t have to worry about O(n) cleanup and init.

        • bluGill 60 days ago
          There is often value in putting a class in a struct. Your proposed rule of struct is POD means there will be many less structs, and force people to think about POD or not when the vast majority of the time that doesn't matter to them.
          • monkeyelite 60 days ago
            > There is often value in putting a class in a struct.

            And what’s the cost?

            Note that a reference to a class is still POD. It just doesn’t have ownership.

            Also I’m not making a specific policy proposal. Im identifying constructors and destructors as the source of complexity. What should we do about it?

            > and force people to think about POD or not

            No such thing as feature you don’t have to understand. The reason this article exists is that C++ programmers must deal with complex initialization behavior.

            Can a C++ programmer not understand move semantics? Or copy constructors?

            • bluGill 60 days ago
              Most of the time the cost is not worth worrying about.

              Most of the time you don't need to think about move or copy - the compiler does the right thing for you. When there are exceptions it is generally when you need to disable them which is simple enough. When you think about constructors you only need to think about the local class in general, and not how they interact with the larger world.

              constructors and destructors eliminate a lot more complexity than they solve. They enable RAII and thus clear ownership of everything (not just memory!).

              • monkeyelite 60 days ago
                Sounds like C++ has everything you want and is just the right amount of complexity for you.
                • bluGill 59 days ago
                  Every version of c++ has added something I want and overall makes the language better. backward compatibility means I can start using the new stuff without rewritting the old and so I can take advantage of it now. (At least somewhat - calling old APIs is weird)
        • gpderetta 60 days ago
          > Imagine if C++ structs were required to be pod and classes were not. Then you could always know that a struct can be trivially allocated/deallocated etc.

          static_assert(is_trivial_v<T>)

          • monkeyelite 60 days ago
            Unfortunately other people aren’t writing simple C structs they are using the full feature set of C++ and I have to use their code.

            So no this doesn’t solve C++ complexity including the initialization problem.

  • rnikander 60 days ago
    Been trying Rust for some months now. The IDE experience is so much nicer I'm probably sticking with it, but the restrictions on how I write code are still grating on me. I need to learn the language better, but now I still feel like I wanted something more like C++ 2.0 without as much paradigm shift.
  • nyarlathotep_ 61 days ago
    Aside, but the author of this blog is the author of https://nostarch.com/building-a-debugger

    A wonderful exploration of an underexplored topic--I've pre-ordered the hard copy and have been following along with the e-book in the interim.

  • timewizard 61 days ago
    > Explicitly initialize your variables, and if you ever fall in to the trap of thinking C++ is a sane language, remember this

    It's a systems language. Systems are not sane. They are dominated by nuance. In any case the language gives you a choice in what you pay for. It's nice to be able to allocate something like a copy or network buffer without having to pay for initialization that I don't need.

    • creata 61 days ago
      C and Rust both tend to be more sane than C++, though, so you can't just pin it on C++ being a systems programming language.
      • pjmlp 60 days ago
        Agree with Rust, with C, only when people think they know C, but never opened a page of ISO C, or spent afternoons reading compiler manuals about language extensions and implementation specific behaviors.
        • tialaramex 60 days ago
          I spent about two decades getting paid to write C before I learned Rust so I feel confident describing myself as an expert. It's true that C's abstract machine is a much stranger thing than many of its proponents believe - and that it's not very like any computer built this century so that the "portable assembler" claims are plain delusional, but I will say it's definitely less crazy than C++, more sane if you will.

          This has become a bit less true in C17 and C23, but a lot of that is driven by the urge from WG21 (the C++ committee) to have WG14 (C language) do their work for them, hopefully some WG14 members will push back against that.

          • pjmlp 60 days ago
            Agreed, now if WG14 actually cared about sensible improvements regarding strings and arrays.

            As for latest ISO C revisions, not sure if it is doing any WG21 work other that the whole #embed drama, rather it looks to me pushing for a C++ without Classes, with a much worse design, e.g. _Generic.

    • vacuity 61 days ago
      I think in this case it's not amiss to mention Rust. Rust gives a compile error if it's not certain a variable is initialized. Option is the standard dynamic representation of this, and works nicely in the context of all Rust code. MaybeUninint is the `unsafe` variant that is offered for performance-critical situations.
      • tialaramex 60 days ago
        To clarify for anybody else following along (I assume you knew), the type MaybeUninit isn't unsafe (Rust doesn't have unsafe types), only its method named assume_init and related APIs are unsafe.

        That's because only this feature introduces the potential for safety problem, which is amusing because it doesn't actually do anything per se, it will often emit zero CPU instructions.

        It's unsafe because before we called this function we had a MaybeUninit<T> and, well as it said, maybe it isn't initialized, so nothing will assume it is. But once we assume_init, we've got a T, all the code working with the T, including safe Rust code, is entitled to ignore any scenarios that could arise if it were not, in fact, initialized properly. Despite not in some sense "doing" anything, the unsafe call was critical.

    • wffurr 61 days ago
      >> Systems are not sane.

      “The systems programmer has seen the terrors of the world and understood the intrinsic horror of existence.”

      https://www.usenix.org/system/files/1311_05-08_mickens.pdf

    • int_19h 60 days ago
      Zig is also a systems language, yet it doesn't have this problem because every local variable must have an explicit initializer. So this simply won't compile:

        pub fn main() void {
           var x: i32;
           ...
        }
      
      If you do actually want an uninitialized variable, you say so:

        var x: i32 = undefined;
      
      This rule also takes care of structs by applying recursively.
    • gosub100 61 days ago
      That may have made sense in the days of < 100 MHz CPUs but today I wish they would amend the standard to reduce UB by default and only add risky optimizations with specific flags, after the programmer has analyzed them for each file.
      • jcelerier 61 days ago
        > That may have made sense in the days of < 100 MHz CPUs

        you don't know how much C++ code is being written for 100-200MHz CPUs everyday

        https://github.com/search?q=esp8266+language%3AC%2B%2B&type=...

        I have a codebase that is right now C++23 and soon I hope C++26 targeting from Teensy 3.2 (72 MHz) to ESP32 (240 MHz). Let me tell you, I'm fighting for microseconds every time I work with this.

        • gosub100 61 days ago
          "how much code" =/= how many developers.

          the people who care about clock ticks should be the ones inconvenienced, not ordinary joes who are maintaining a FOSS package that is ultimately stuck by a 0-day. It still takes a swiss-cheese lineup to get there, for sure. but one of the holes in the cheese is C++'s default behavior, trying to optimize like it's 1994.

          • jcelerier 61 days ago
            > the people who care about clock ticks

            I mean that's pretty much the main reason for using c++ isn't it? Video games, real-time media processing, CPU ai inference, network middleware, embedded, desktop apps where you don't want startup time to take more than a few milliseconds...

            • gosub100 61 days ago
              No, it's not a dichotomy of having uninitialized data and fast startup or wait several milliseconds for a jvm or interpreter to load a gigabyte of heap allocated crap.
            • PaulDavisThe1st 61 days ago
              it's not about startup time. it's about computational bandwidth and latency once running.
        • vjvjvjvjghv 61 days ago
          I bet even there you have only a few spots where it really makes a difference. It’s good to have the option but I think the default behavior should be safer.
          • jcelerier 61 days ago
            I don't know, way too often often my perf traces are evenly distributed across a few hundred functions (at best), without any clear outlier.
      • bluGill 60 days ago
        They are doing what you want. It is a long difficult process to figure out what is UB - most of it is cases where there is nothing written down and so it UB by default - it wasn't defined. Once UB is found and documented then they get to figure out what to be done about it. In some cases nothing as realistically nobody does that, in the case in question they have defined what happens, but the article is 8 years old.
      • timewizard 61 days ago
        CPU speed is not memory bandwidth. Latency and contention always exist. Long lived processes are not always the norm.

        In another era we would have just called this optimal. https://x.com/ID_AA_Carmack/status/1922100771392520710

  • beached_whale 61 days ago
    And for the most part it does what you expect.
  • waynecochran 61 days ago
    This idea that everything must be initialized (i.e. no undefined or non-deterministic behavior) should never be forced upon a language like C++ which rightly assumes the programmer should have the final say. I don't want training wheels put on C++ -- I want C++ do exactly and only what the programmer specifies and no more. If the programmer wants to have uninitialized memory -- that is her business.
    • Maxatar 61 days ago
      It's so ironic hearing a comment like this. If what you really want is for C++ to do only what you strictly specified, then you'd always release your software with all optimizations disabled.

      But I'm going to go out on a limb here and guess you don't do that. You actually do allow the C++ compiler to make assumptions that are not explicitly in your code, like reorder instructions, hoist invariants, eliminate redundant loads and stores, vectorize loops, inline functions, etc...

      All of these things I listed are based on the compiler not doing strictly what you specified but rather reinterpreting the source code in service of speed... but when it comes to the compiler reinterpreting the source code in service of safety.... oh no... that's not allowed, those are training wheels that real programmers don't want...

      Here's the deal... if you want uninitialized variables, then explicitly have a way to declare a variable to be uninitialized, like:

          int x = void;
      
      This way for the very very rare cases where it makes a performance difference, you can explicitly specify that you want this behavior... and for the overwhelming majority of cases where it makes no performance impact, we get the safe and well specified behavior.
      • sixthDot 60 days ago
        > int x = void;

        this is what the D programming language does. Every var declaration has a well know value, unless it is initialized with void. This is nice, optimizing compilers are able to drop useless assignments anyway.

      • trealira 61 days ago
        Nah, they'd never add new syntax like that, given it's inconsistent with the rest of C++.

        If they added an explicit uninitialized value representation to the language, I bet it would look something like this:

          int x {std::uninitialized<int>::value};
        • Maxatar 60 days ago
          You're not far off. In C++26 the syntax will be:

             int x [[indeterminate]];
          
          I'm not kidding here;
        • gpderetta 60 days ago
          C++ hasn't done it this way for nullptr or nullopt, why would it do it for an explicit uninitialized?
          • trealira 60 days ago
            I guess nullptr was put in there because because because "#define NULL 0" had some bad consequences for C++ and they needed a replacement.

            std::nullopt doesn't seem so different to what I was talking about; I guess it's just less verbose. When I wrote that, I was thinking of things like "std::is_same<T1, T2>::value" being there.

        • tlb 60 days ago
          That's about the right level of ceremony to request an uninitialized variable.
      • frollogaston 61 days ago
        How about int x = 0 if you want 0. Just `int x;` doesn't make it clear that you want 0.
        • kstrauser 61 days ago
          Safe defaults matter. If you're using x to index into a array, and it's randomly initialized as +-2,000,000,000 because that's what happened to be in that RAM location when the program launched, and you use it before explicitly setting it, you're gonna have a bad time.

          And if you used it with a default value of 0, you're going to end up operating on the 0th item in the array. That's probably a bug and it may even be a crasher if the array has length 0 and you end up corrupting something important, but the odds of it being disastrous are much lower.

      • waynecochran 61 days ago
        The whole advantage of UB is that this places less restraints on what the optimizer can do. If I say something does not need to be initialized I am giving the optimizer the freedom to do more!
        • TheBicPen 61 days ago
          So what's the issue with introducing explicit syntax to do exactly that if you want to? A safe default does not preclude you from opting out of safety with a bit of syntax or perhaps a compiler flag.
          • monkeyelite 60 days ago
            The issue is that the language was already designed with the old behavior.
      • monkeyelite 60 days ago
        > It's so ironic hearing a comment like this. If what you really want is for C++ to do only what you strictly specified, then you'd always release your software with all optimizations disabled

        The whole idea of optimizations is producing code that’s equivalent to the naiive version you wrote. There is no inconsistency here.

        • RUnconcerned 60 days ago
          Optimizations are not "exactly and only what the programmer specifies and no more". They actually fall into the "more" category, believe it or not.
          • monkeyelite 60 days ago
            Show me some O2 optimizations that will act contrary to code I wrote - meaning they violate the “as if” rule.
            • Maxatar 60 days ago
              Two points... the first is you want an optimization that violates the "as if" rule, sure... copy constructors are allowed to violate the "as if" rule, so here you go:

                  https://godbolt.org/z/jzWWTW85j
              
              Compile that without optimizations and you get one set of output, compile it with optimizations and you get another. There are actually an entire suite of exceptions to the "as-if" rule.

              The second point is that the whole reason for having an "as if" rule in the first place is to give permission for the compiler to discard the literal interpretation of the source code and instead only consider semantics that are defined to be observable, which the language standard defines not you the developer.

              There would be no need for an "as if" rule if the compiler strictly did exactly what it was told. Its very existence should be a clue that the compiler is allowed to reinterpret the source code in ways that do not reflect its literal interpretation.

              • William_BB 59 days ago
                The standard says that "Copy elision is <...> one of the two allowed forms of optimization, alongside allocation elision and extension,(since C++14) that can change observable side-effects"

                I agree you have a valid point though. I'd be interested to know the committee's reasoning.

              • monkeyelite 60 days ago
                > if the compiler strictly did exactly what it was told.

                What does this mean since I am not writing assembly and there is no specified correspondence between assembly and C++.

                • Maxatar 59 days ago
                  Nothing about my post has anything to do with assembly. You asked for, and I quote "Show me some O2 optimizations that will act contrary to code I wrote - meaning they violate the “as if” rule." and I provided just that. You can go on the link I provided, switch back and forth between O2 and O0 and see different observable behavior which violates the as-if rule.

                  I'm not sure why you're bringing up assembly but it suggests that you might not correctly understand the example I provided for you, which I reiterate has absolutely nothing to do with assembly.

            • RUnconcerned 60 days ago
              I dunno about what you wrote, but here is one that clearly violates what OP said!

              https://godbolt.org/z/Y4Yjb7z9c

              clang notices that in the second loop I'm multiplying by 0, and thus the result is just 0, so it just returns that. Critically, this is not "exactly and only what the programmer specifies", since I very much told it to do all those additions and multiplications and it decided to optimize them away.

              • monkeyelite 60 days ago
                What? But the result is the same!
    • yxhuvud 61 days ago
      The discussion about what should be the default behavior and of what should be the opt-in behavior is very different from what should be possible. It is definitely clear that in c++, it must be possible to not initialize variables.

      Would it really be that unreasonable to have initialisation be opt-out instead of opt-in? You'd still have just as much control, but it would be harder to shoot yourself in the foot by mistake. Instead it would be slightly more easy to get programs that can be optimised.

      • frollogaston 61 days ago
        C++ is supposed to be an extension of C, so I wouldn't expect things to be initialized by default, even though personally I'm using C++ for things where it'd be nice.

        I'm more annoyed that C++ has some way to default-zero-init but it's so confusing that you can accidentally do it wrong. There should be only one very clear way to do this, like you have to put "= 0" if you want an int member to init to 0. If you're still concerned about safety, enable warnings for uninitialized members.

        • dwattttt 60 days ago
          C++ is supposed to be an extension of <thing with bad default>, so I wouldn't expect <a good default>.

          Things can change & grow, that's why we make new standards in the first place.

          • frollogaston 60 days ago
            It'd be confusing for C++ to differ from C in how primitives work. If they want to evolve C too then sure.
            • frollogaston 60 days ago
              also there are compiler warnings for uninitialized variables
        • gpderetta 60 days ago
          my_type my_var = {}; almost always does the right thing.

          The almost is unfortunate.

    • loeg 61 days ago
      As someone who has to work in C++ day in and day out: please, give me the fucking training wheels. I don't want UB if I declare an object `A a;` instead of `A a{};`. At least make it a compiler error I can enable!
      • ryandrake 61 days ago
        Ideally, there would be a keyword for it. So ‘A a;’ would not compile. You’d need to do ‘A a{};’ or something like ‘noinit A a;’ to tell the compiler you’re sure you know what you are doing!
      • bregma 60 days ago
        Can you identify a compiler released in the last, say, 20 years that does not give a warning (or error, if the compiler is instructed to turn warnings into errors) for uninitialized variables when warnings are enabled?
        • int_19h 60 days ago
          Most of them. They have to, because declaring an uninitialized variable that is later initialized by passing a reference or pointer to it to some initialization function is a rather common pattern in low-level C++.

          In a sane language that would be distinguishable by having the direction explicit (i.e. things like in/out/ref in C#), and then compiler could complain for in/ref but not for out. But this is C++, so...

        • loeg 60 days ago
          Does Clang 20 (March 2025) meet your criteria?

          https://godbolt.org/z/W9Tzhfn4s

      • waynecochran 61 days ago
        Not me. I want to give the optimizer the freedom to do its thing. If I say something does not need to be initialized, then the optimizer has one less constraint to worry about.
        • wiseowise 61 days ago
          We’ve already understood you don’t want sane language design, you don’t need to repeat it ten times.
    • 90s_dev 61 days ago
      That's the inherent tension, though, isn't it?

      A programmer wants the compiler to accept code that looks like a stupid mistake when he knows it's not.

      But he also wants to have the compiler make sure he isn't making stupid mistakes by accident.

      How can it do both? They're at odds.

    • GrantMoyer 61 days ago
      The problem is that the initialization semantics are so complex in C++ that almost no programmer is actually empowered to exercise their final say, and no programmer without significant effort.

      And that's not just said out of unfamiliarity. I'm a professional C++ developer, and I often find I'm more familiar with C++'s more arcane semantics than many of my professional C++ developer co-workers.

    • marsten 60 days ago
      Unfortunately C++ ended up with a set of defaults (i.e., the most ergonomic ways of doing things) that are almost always the least safe. During most of C++'s development, performance was king and so safety became opt-in.

      Many of these can't be blamed on C holdover. For example Vector.at(i) versus Vector[i] – most people default to the latter and don't think twice about the safety implications. The irony is that most of the time when people use std::vector, performance is irrelevant and they'd be much better off with a safe default.

      Alas, we made our bed and now we have to lie in it.

      • int_19h 60 days ago
        vector::at() is an interesting example. Most of the time, you don't use it because you don't index vectors in the first place - you use iterators, and there's no equivalent of at() for them that is guaranteed to throw when out of bounds.
    • charlotte-fyi 61 days ago
      The entire problem is that what the programmer wants to do and what the program actually does isn't always clear to the programmer.
    • kstrauser 61 days ago
      By that logic, you'd have to dislike the situations where C++ does already initialize variables to defined values, like `int i;`, because they're removing your control and forcing training wheels upon you.

      So, do you?

      • jcelerier 61 days ago

            int i;
        
        does not initialize the value.
        • kstrauser 61 days ago
          It's a gotcha to be sure. Sometimes it does, sometimes it doesn't. From a reference[0]:

            #include <string>
            
            struct T1 { int mem; };
            
            struct T2
            {
                int mem;
                T2() {} // “mem” is not in the initializer list
            };
            
            int n; // static non-class, a two-phase initialization is done:
            // 1) zero-initialization initializes n to zero
            // 2) default-initialization does nothing, leaving n being zero
            
            int main()
            {
                [[maybe_unused]]
                int n;            // non-class, the value is indeterminate
                std::string s;    // class, calls default constructor, the value is ""
                std::string a[2]; // array, default-initializes the elements, the value is {"", ""}
                //  int& r;           // Error: a reference
                //  const int n;      // Error: a const non-class
                //  const T1 t1;      // Error: const class with implicit default constructor
                [[maybe_unused]]
                T1 t1;            // class, calls implicit default constructor
                const T2 t2;      // const class, calls the user-provided default constructor
                // t2.mem is default-initialized
            }
          
          That `int n;` on the 11th line is initialized to 0 per standard. `int n;` on line 18, inside a function, is not. And `struct T1 { int mem; };` on line 3 will have `mem` initialized to 0 if `T1` is instantiated like `T1 t1{};`, but not if it's instantiated like `T1 t1;`. There's no way to tell from looking at `struct T1{...}` how the members will be initialized without knowing how they'll be called.

          C++ is fun!

          [0]https://en.cppreference.com/w/cpp/language/default_initializ...

          • 90s_dev 61 days ago
            Stroustrup once said

            > "There's a great language somewhere deep inside of C++"

            or something to that effect.

            • int_19h 60 days ago
              Yes, it's called Simula-67.

              (the original pre-ISO C++ is basically C with Simula bolted onto it.)

        • portaltonowhere 61 days ago
          Unless `i` is global…
      • waynecochran 61 days ago
        Most cases, e.g. local var declaration. `int i` does not initialize i.
    • anon-3988 61 days ago
      If they want the program to do exactly what is told they won't get to have optimization.
      • dminik 60 days ago
        It doesn't really seem worth it. https://web.ist.utl.pt/nuno.lopes/pubs/ub-pldi25.pdf

        > The results show that, in the cases we evaluated, the performance gains from exploiting UB are minimal. Furthermore, in the cases where performance regresses, it can often be recovered by either small to moderate changes to the compiler or by using link-time optimizations.

      • waynecochran 61 days ago
        That's the whole point of UB -- it leaves open more possibilities for optimization. If everything is nailed down, then the options are more restricted.
    • titzer 61 days ago
      > I want C++ do exactly and only what the programmer specifies and no more.

      Most programmers aren't that good and you're mostly running other people's code. Bad defaults that lead to exploitable security bugs is...bad defaults. If you want something to be uninitialized because you know it then you should be forced to scream it at the compiler.

    • vjvjvjvjghv 61 days ago
      The dev should have the option to turn it off but I think that removing a lot of undefined and non deterministic behavior would be a good thing. When I did C++ I initialized everything and when there was a bug it could usually be reproduced. There are a few cases where it makes sense performance wise to not initialize but those cases are very small compared to most other code where undefined behavior causes a ton of intermittent bugs.
    • tonyhart7 61 days ago
      "If the programmer wants to have uninitialized memory -- that is her business."

      idk, seems like years of academic effort and research wasted if we do the way C++ do it

    • flavio81 60 days ago
      >I want C++ do exactly and only what the programmer specifies

      Good luck with that.

  • adityamwagh 61 days ago
  • alexvitkov 61 days ago
    This is not even worth thinking about, just type " = {}" on every struct/class member and every variable declaration, and forget about all this nonsense.
    • dataflow 61 days ago
      That's a bad idea. It defeats tools (warnings, sanitizers, etc.) that try to tell you you have forgotten to place the semantically correct value in your variables.

      If you want indiscriminate initialization, a compiler flag is the way, not forcing it in the source code.

  • bluGill 60 days ago
    This is obsolete and wrong! Variables are now - well as of C++26 which is next year - required to be initialized in C++. C++ is not standing still, it is getting better (well mostly better). You can do stupid things in C++, but I've seen stupid things in every non-toy language (and even most toys)
  • jandrewrogers 61 days ago
    I largely prefer modern C++ as systems languages go but there is no getting around the fact that the initialization story in C++ is a hot mess. Fortunately, it mostly does what you need it to even if you don't understand it.
    • vjvjvjvjghv 61 days ago
      And sometimes it doesn’t do what you think it does.
  • dschuetz 60 days ago
    Are AI chatbots aware of this?
  • gitroom 61 days ago
    [dead]
  • jeffbee 61 days ago
    It's fun to cross the streams of HN catnip.

    C++ sucks, it's too hard to use, the compiler should generate stores all over the place to preemptively initialize everything!

    Software is too bloated, if we optimized more we could use old hardware!

    • Maxatar 61 days ago
      I'm not familiar with programming languages that generate redundant stores in order to initialize anything.

      Usually what happens is the language requires you to initialize the variable before it's read for the first time, but this doesn't have to be at the point of declaration. Like in Java you can declare a variable, do other stuff, and then initialize it later... so long as you initialize it before reading from it.

      Note that in C++, reading from a variable before writing to it is undefined behavior, so it's not particularly clear what benefit you're getting from this.

      • josefx 61 days ago
        > Note that in C++, reading from a variable before writing to it is undefined behavior, so it's not particularly clear what benefit you're getting from this.

        The compiler cannot always tell if a variable will be written to before it is accessed. if you have a 100kb network buffer and you call int read = opaque_read(buffer); the compiler cannot tell how much or if anything at all was written to buffer and how size relates to it, it would be forced to initialize every byte in it to zero. A programmer can read the API docs, see that only the first read bytes are valid and use the buffer without ever touching anything uninitialized. Now add in that you can pass mutable pointers and references to nearly anything in C++ and the compiler has a much harder time to tell if it has to initialize arguments passed to functions or if the function is doing the initialization for it.

        • tialaramex 60 days ago
          In C++ 26 you will be able to specifically mark that you want the initialization not to happen, whereupon of course (a) your reviewers can see this and critique it, are you sure it doesn't need initializing? Isn't this the code we run once per month that doesn't need to be fast or small? (b) the maintenance programmer coming later can identify that you did it on purpose and consider whether it's still correct

          The problem was never "This is always a bad idea" and instead only "This is usually a bad idea so you should need to explicitly ask for it when you want it, so that when somebody writes it by mistake they can be told about that".

          Rust chooses to need a lot of ceremony to make a MaybeUninit, and especially to then assume_init with no justification because it's not actually initialized - but that's because almost nobody who wants to write that understands what will actually happen, they typically have the C programmer "All the world's a PDP-11" understanding and in that world this operation has well understood and maybe even desirable behaviour. We do not live in that world, this is not a PDP-11 and it isn't shy about that. The thing they want is called a "freeze semantic" and it's difficult to ensure on modern systems.

          But if you hate the ceremony, regardless of the fact there's a good reason for it - many languages have less ceremony, often just a handful of keystrokes to say "Don't initialize this" will be enough to achieve what you intended. What they don't do, which C++ did, was just assume the fact you forgot to write an initializer means you want the consequences - when in reality it usually means you're just human and make mistakes.

      • zahlman 61 days ago
        > Note that in C++, reading from a variable before writing to it is undefined behavior, so it's not particularly clear what benefit you're getting from this.

        You gain the benefit that the compiler can assume the code path in question is impossible to reach, even if there's an obvious way to reach it. To my understanding, this can theoretically back-propagate all the way to `main()` and make the entire program a no-op.

        • tialaramex 60 days ago
          There are real (in the sense they compile in actual C++ compilers people use and the resulting executable will run) toy (ie not intended for production use) examples where you can cause the compiler to conclude that executing this never-called function named format_hard_disk or begin_world_war_iii or whatever is the only remaining possible meaning of the program so that's what happens when it's run.

          Nobody has written an actual format-hard-disk or start-world-war-III routine, because we're not crazy, but the alarming function naming is intended to signal what a terrible idea it is to have this programming language at all.

      • jorhannn 60 days ago
        >Note that in C++, reading from a variable before writing to it is undefined behavior

        They are finally fixing that in C++26 where it's no longer undefined behavior, it's "erroneous behavior" which will require a diagnostic and it has to have some value and compilers aren't allowed to break your code anymore.

        • tialaramex 60 days ago
          But the price for that is indeed those "redundant stores" which it appears today C++ programmers are convinced explain the slowdown.

          I mean sure, the real cause is more likely their incompetent use of the wrong algorithms and data structures or the fact that it's too hard to rely on external dependencies so they're still using a stdlib feature that's known to be significantly slower than the best efforts but eh, it's just a single #include away.

          • jeffbee 60 days ago
            Redundant stores are not some kind of myth. People spent a long time reducing redundant stores in the constructors of the protobuf library, for example, and this has measurable efficiency benefits.
            • tialaramex 60 days ago
              They aren't a myth, but they're very easy to overestimate as a cause of problems.
              • jeffbee 60 days ago
                Granted. Be guided by profiles, always.
    • int_19h 60 days ago
      Software is too bloated because there are a dozen layers of wrappers that don't really do anything useful, not because variables aren't default-initialized.

      We had languages with default initialization 30 years ago used to write DOS software running on 80386. It was plenty fast.