5 ms·
map::operator[] should be nodiscard
- reactordev 10mo agoMy 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 10mo agoC++ 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 10mo agoI 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 10mo 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 10mo agoGo 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.
- pjmlp 10mo agoGo 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.
- throwaway17_17 10mo agoCan you expand on Oberon-07 minimalism in the context of bootstrapping or working at the lowest level of abstractions? Your posts about the Wirth tradition languages and their implementations are typically well founded and I haven’t read much on this aspect. If you just have a reference you’d suggest that would be more than enough (if you don’t want to take time explaining what has been written elsewhere).
- pjmlp 10mo agoHanspeter Mössenböck created Oberon-2 with feedback from Niklaus Wirth, and later went on to focus on .NET and Java instead, several of his studends have contributed to MaximeVM and later GraalVM research. Meanwhile the Oberon community diverged into Active Oberon, Zonnon, Component Pascal. However for Niklaus Wirth those were distractions, and he looked into how to take further features away from Oberon, which was already less feature rich than Modula-2. There were a few revisions from Oberon-07 between 2008 and 2016, each removing even more language features, before his retirement from computing world. "Differences between Revised Oberon and Oberon" https://people.inf.ethz.ch/wirth/Oberon/Oberon07.pdf https://people.inf.ethz.ch/wirth/Oberon/Oberon07.pdf "Porting the Oberon Compiler from Oberon to Oberon-07" The latest revision being, https://people.inf.ethz.ch/wirth/Oberon/Oberon07.Report.pdf https://people.inf.ethz.ch/wirth/Oberon/Oberon07.Report.pdf Niklaus Wirth goal became how to remove "needless fluff" out of Oberon, and still get enough features to have a systems programming language with a GC.
- feelamee 10mo ago> 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. I have never seen better documentation for programming languages than cppreference. Can you list such docs?
- fn-mote 10mo agoTitle 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 10mo agohttps://en.cppreference.com/w/cpp/language/attributes/nodiscard 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 10mo agoThanks. 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.
- compiler-guy 10mo agoGoogle could easily change these lines. The question is, should it? One thing about Google living so close to head with its libc++ is that it encounters the issues downstream users will encounter, just long before everyone else. It saw this development within a day or two of the or getting merged. The idiom is unfortunately common in C++ codebases around the world so this was a good predictor that many other users will be broken. It isn’t necessarily erroneous, unlike many of the other no-discard additions made in this patch series. So the question becomes, “Are the false positives worth the true positives?” Not just for Google, but for the entire user base. It is reasonable to disagree on this, and often library writers up to date on the latest and greatest miss the issues this sort of change will cause.
- junon 10mo agoFor 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 10mo agoIt’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 10mo agoThere is in c++, too (std::ignore). Not sure why the author decided to go with the ancient void cast
- aw1621107 10mo agoI believe C++26 now allows _ as a placeholder name [0]: > We propose that when_ is used as the identifier for the declaration of a variable, non static class member variable, lambda capture or structured binding. the introduced name is implicitly given the [[maybe_unused]] attribute. > In contexts where the grammar expects a pattern matching pattern,_ represents the wildcard pattern. Some of the finer details (e.g., effect on lifetime and whether a _ variable can be used) differ, though. [0]: https://github.com/cplusplus/papers/issues/878 https://github.com/cplusplus/papers/issues/878
- tialaramex 10mo ago
- dooglius 10mo 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
- deleted 10mo ago[deleted]
- themafia 10mo agoAh, 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.
- ahartmetz 10mo agoThis "create a default-constructed value just so you can return a reference" logic is pretty terrible tbh. For insert: first create a default-constructed value, then assign to it. For retrieval: in the not found case, (permanently) insert a default-constructed value into the map. Need to return a valid reference! Qt containers do it better: upsert with insert() and retrieve with value(), which, in the not found case, will return a default-constructed value (or a caller-supplied value) but without inserting it into the map.
- j1elo 10mo agoC++ 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.
- ivanjermakov 10mo agoAnother default that Zig got right: every non-void result must be handled. https://github.com/ziglang/zig/issues/219 https://github.com/ziglang/zig/issues/219
- kyralis 10mo agoI 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.
- m-schuetz 10mo agoTerrible for atomic functions where you are often only interested in atomically writing something, but not interested in the return value they also provide.
- leecommamichael 10mo agoThis is one of those preferences that will never fail to split the room. I appreciate both routes depending on the domain, but I do have a preference. As a games and UI app developer, I find required-handling-by-default adds too much friction and disrupts my flow. Rust and Zig (through different means) create friction like this in an effort to make low-level code "easier", but only if the code is "correct" according to the language. As a dev that spent a lot of time with Swift, and loving the language's ability to express APIs, I came to appreciate compilers with the quality that "if the code runs, it's probably correct," and yet my preference did not land on Rust or Zig, but with Odin. I sat down with Odin 5 or so years ago and it felt like the friction was exactly where I wanted it to be for the software I write.
- drnick1 10mo agoIt’s a terrible idea, what if you aren’t interested in the return value and can’t rewrite the function because it is in a third party library? Assigning that to a variable that won’t be used is terribly inefficient and inelegant.
- 10mo ago
- dundarious 10mo agoThere is no need to compromise in order to support pre-C++17, you don't need `try_emplace` when the value is like bool and hence doesn't benefit from move semantics -- plain old `insert` is exactly equivalent, and has existed since std::map's inception.
- nialv7 10mo agoC++ operator [] is poorly designed: index, and index+assignment should be two different operators, and indexing alone should never insert new entries into the map. Languages like D [0] or Rust [1] get this right. [0]: https://dlang.org/spec/operatoroverloading.html#index_assignment_operator https://dlang.org/spec/operatoroverloading.html#index_assign... [1]: https://doc.rust-lang.org/std/ops/trait.IndexMut.html https://doc.rust-lang.org/std/ops/trait.IndexMut.html
- gpderetta 10mo agoYou could (and I would) make the opposite statement: upsert should be the default operator and if you want lookup only or insert only you call different operators. I find it annoying that I often have to reach to defaultdict in Python to get this behavior.
- hmry 10mo agoI certainly use defaultdict often in Python too, but not more often than the regular dict. Maybe 90% dict and 10% defaultdict. So from my POV lookup only should definitely be the default.
- tialaramex 10mo agoC++ could offer the entry API here, so you can get back a type representing the result of finding where this key would go, and then either it has a key+value pair you can mutate if you want, or it has a blank state allowing you to write a new key+value pair if that's what you want, without redoing the potentially expensive find operation to figure out where to put the new/updated pair
- Tempest1981 10mo agoiirc, there are 5 ways to put something into a std::map operator[], insert(), emplace(), try_emplace(), insert_or_assign() And 2 of them don't overwrite an existing value. Lots of people are surprised that insert() can fail. And even more surprised that a RHS [] inserts a default value. I'm not a fan of APIs that surprise.
- jesse__ 10mo agolol .. add one more reason to the overflowing fountain of reasons to not use anything in std
- feelamee 10mo ago> sometimes that’s actually what you meant — you’re calling it only for its side effect. Back in 2022, Stephan T. Lavavej estimated of unique_ptr::release that “90% of discards are a bug, but 10% are maybe valid… What are these 10% valid cases? Does someone have an example
- quuxplusone 10mo agoHere's a made-up example, not from any particular codebase: try { f(p.get()); (void)p.release(); } catch (...) { /* f threw; don't release p after all */ }
- juliangmp 10mo agoThat is an actually terrible pattern, the standard should mandate nodiscard on operator[]. Or maybe finally cast that cursed operator implementation into hell where it belongs. But alas, existing code is considered sacred in the C++ cult so we must never ever inconvenience anyone maintaining legacy code, no matter how broken it is.