Map: Operator[] Should Be Nodiscard

(quuxplusone.github.io)

42 points | by jandeboevrie 5 days ago ago

29 comments

  • junon 3 hours ago

    For the Rust inclined, [[nodiscard]] is #[must_use], if you were confused.

    Anyway, this article illustrates a great reason why C++ is a beautiful mess. You can do almost anything with it, and that comes at a cost. It's the polar opposite ethos of "there should be one clear way to do something" and this sort of thing reminds me why I have replaced all of my systems language needs with Rust at this point, despite having a very long love/hate relationship with both C and C++.

    Totally agree it should be marked as nodiscard, and the reasoning for not doing so is a good example of why other languages are taking over.

    • bayesnet 2 hours ago

      It’s also worth noting that in rust you don’t need to be as worried about marking a function #[must_use] if there is a valid reason some of the time to discard the value. One can just assign like so `let _ = must_use_fn()` which discards the value and silences the warning. I think this makes the intent more clear than casting to void as TFA discusses.

      • on_the_train 2 hours ago

        There is in c++, too (std::ignore). Not sure why the author decided to go with the ancient void cast

    • pjmlp 2 hours ago

      I disagree with the conclusion, other languages are taking over because they have the advantage of not having 40 years of production code history, and those adopting them don't care about existing code.

      You will find similar examples in Python, Java, C#,... and why not everyone is so keen into jumping new language versions.

    • the_mitsuhiko 2 hours ago

      Interestingly Index::index is also usually not marked as `#[must_use]` in Rust either.

      • junon an hour ago

        I don't believe you can mark trait methods with #[must_use] - it has to be on the implementation. Not near a compiler to check at the moment.

        In the case of e.g. Vec, it returns a reference, which by itself is side-effect free, so the compiler will always optimize it. I do agree that it should still be marked as such though. I'd be curious the reasons why it's not.

        • steveklabnik 4 minutes ago

          This is just my take, but I think historically the Rust team was hesitant to over-mark things #[must_use] because they didn't want to introduce warning fatigue.

          I think there's a reasonable position to take that it was/is too conservative, and also one that it's fine.

        • the_mitsuhiko 12 minutes ago

          But it's also not marked at the implementation for HashMap's Index impl for instance.

  • reactordev 3 hours ago

    My pet peeve with c++ is exactly this. Either it’s not wise to call release, or it is (under circumstances) and yet the developer has no idea whether their scenario applies (tip: it doesn’t, 90% of the time).

    The stdlib is so bloated with these “Looks good, but wait” logic bombs.

    I wish someone would just draw a line in the sand and say “No, from here on out, this is how this works and there are no other scenarios in which there needs a work around”. This is why other systems languages are taking off (besides the expressiveness or memory safety bandwagon) is because there are clear instructions in the docs on what this does with examples of how to use it properly.

    Most c++ codebases I’ve seen the last 10 years are decent (a few are superb) and I get that there’s old code out there but at what point do we let old dogs die?

    • GuB-42 2 hours ago

      C++ has always been a "kitchen sink" language, it is used in many different ways and drawing any line may alienate an entire industry.

      > This is why other systems languages are taking off

      Great! It is not a competition. If you think that Rust is a better choice, use Rust, don't make C++ into Rust. Or maybe try Carbon, it looks like it is the language you want. But if you have some old dogs you want to keep alive, then use C++, that's what it is for.

      • reactordev an hour ago

        I get it, I do. There’s a lot of old code out there. My point wasn’t that old dogs are bad. My point was about changing how we care for them.

        If you have old code that you want to compile, use -c98 or whatever to peg it to that. Leave the rest of us alone to introduce more modern ways of things. I’d even be happy to see removal of things.

    • pjmlp an hour ago

      > This is why other systems languages are taking off

      For the time being that are still being written with C++ infrastructure though.

      It would be great if those wannabe C++ replacements were fully bootstraped.

      • reactordev an hour ago

        Go compiles go, not sure what you mean by wannabe c++.

        There’s a frontend to gcc for go and working on rust. Is it the use of gcc you dislike? You’re going to have to explain some more.

        We’re stuck on ASM/ELF. We’re stuck on C of some kind. Maybe in the future LLMs can help us write low-level / high expressiveness code but until we get rid of 1970s “personal computer” decisions in silicon, we’re stuck with it.

        • throwaway17_17 33 minutes ago

          What is the proposed replacement for ASM (in particular) and C in the context of the bootstrapping process? Then why lump ELF (unless you don’t mean the executable format) in with the low level language?

          Historically, pjmlp has pushed very strongly for languages attempting to take the place of C and C++ at the infrastructure layer can not claim to have supplanted those two until their own compiler and related infrastructure is not dependent on C++ (LLVM in particular). I tend to sympathize with this view, it is really hard to take a language’s claim to have supplanted C++ and be the only fit for use language going forward, but then is dependent on millions of lines of the languages they disparage.

          As a counter however, it’s rather difficult to expect a language to overcome thousands of person-years of work on a compiler like LLVM and tens of thousands of person-years on Linux. The newer languages should be able to make an articulate case that throwing away so much work is not a viable approach and just use what exists now but keep the new languages on all greenfield projects.

          • steveklabnik 2 minutes ago

            As another counter, Rust has never claimed to have "supplanted" C++. So holding it to that standard is holding it to a non-goal for itself in the first place.

        • pjmlp 40 minutes ago

          Go is not a wannabe C++ replacement.

          It could be, but its designers aren't keen in modern language design.

          First it needs to fulfill more use cases than Docker and Kubernetes ecosystem.

          And while TinyGO and TamaGo exist, they require custom runtimes, and Assembly tricks that C++ supports at the language level, or even Rust does better than Go.

          It is better than using Oberon-07 minimalist design, though.

      • SJC_Hacker an hour ago

        > t would be great if those wannabe C++ replacements were fully bootstraped

        This would require (re)writing the OS in the replacement language

        Also need assembler to be taken seriously, which Rust can’t do last I checked

        • pjmlp 35 minutes ago

          Nah, they could start by not depending on LLVM/GCC and do their whole compiler back to back.

        • throwaway17_17 an hour ago

          Can you explain the assembler bit? Are you talking about the handling of inline assembly? I though Rust allowed that in unsafe code.

          • steveklabnik 5 minutes ago

            It does! And it's in the language proper, unlike being a compiler extension like it is in C.

  • fn-mote 3 hours ago

    Title could be “ugly C++ idioms prevent map::operator[] from being [[nodiscard]]”.

    Many of the uses are in Google’s codebase.

    Overall very technical- interesting if you are a library writer or maybe if you care about long term improvements in your C++’legacy codebase.

  • rwmj 3 hours ago

    https://en.cppreference.com/w/cpp/language/attributes/nodisc... .. in case anyone else was wondering. It seems to mean the compiler should warn if you ignore the result except by an explicit cast to void.

    • larusso 3 hours ago

      Thanks. I was wondering what this means practically. So they rolled this back because Google who compile with warnings as errors can’t fix these lines? Must be great to be Google and on all these boards. I on the other hand have to deal constantly with breaking changes left and right because someone decided, among them Google (16KB page tables anyone), that going forward stuff works differently.

  • j1elo 2 hours ago

    C++ could try to approach the "stability without stagnation" model.

    Add an opt-in compiler flag --edition='26' which, when used, applies the breaking changes defined for C++26. Then users like Google or others who have been (ab)using some features for their side effects can decide to stay on the older versions.

    • pjmlp an hour ago

      It already exists, --std=c++26.

  • themafia 2 hours ago

    Ah, and because this is C++, the standard map having typed template parameters, which could be a non pointer, they're forced to make operator[] have this semantic:

    Returns a reference to the value that is mapped to a key equivalent to key or x respectively, performing an insertion if such key does not already exist.

    Which is a bit of a surprise coming from mostly C and Go.

  • ivanjermakov an hour ago

    Another default that Zig got right: every non-void result must be handled.

    https://github.com/ziglang/zig/issues/219

    • kyralis 11 minutes ago

      I would not agree. There are plenty of times I've written and used functions that have informational return values that are beneficial in certain cases and unnecessary in most. This is why most languages have chosen annotations to allow for the function author to indicate intent.

  • dooglius 2 hours ago

    `try_emplace` is not a huge improvement since it overloads the existing keyword "try" to mean something pretty different. Should be `emplace_if_absent`/`insert_if_absent` but changing the API of stdlib would require going through a huge formal process