std::map<std::string, std::string> m;
(void) m["nonexistent"];
m.size(); // => 1
Is there a historic reason for this strange specification? Or is it just a concession make the "operator=" sugar work better? std::map<std::string, std::string> m;
(void) m["nonexistent"];
m.size(); // => 1
Is there a historic reason for this strange specification? Or is it just a concession make the "operator=" sugar work better?operator[] returns a reference to a slot in the map which can be read from or assigned to. Since the reference must be valid, you better have allocated a slot in the map that can be written to if the value didn’t exist.
m["nonexistent"] = 1;
This would not work if you return a dummy value..find() is the safe way to do map lookup without side effects.
I mean, left side expressions and right side expressions are different
let salary = salaries["Jim Bob"]; // Note this panics if "Jim Bob" isn't a key in salaries, by analogy to arrays which panic if you do an out-of-bounds array access
However you can't write this, it won't compile:
salaries["Jim Bob"] += 10_000; // No raise for Jim Bob, this does not compile because that's not a mutable reference.
The ability to compute and pass around lvalue references (where "lvalues" are approximately "things that can appear on the lhs of assignment") is a very powerful feature, but also one which causes complexity in all sorts of other places, which is a bit of a theme for C++. One of those complexities is that non-const `operator[]` essentially has to be `operator[]=` even if at this specific call site there's no assignment happening.
C++ could have had const `std::map:::operator[]` throw an exception if the key isn't present, but having const and non-const versions of a function do different things is extremely error-prone and instead you have to use at() if that's what you want.
I tend to use find() when doing a read only lookup, and operator overloading for writing.
- what is the dummy value? does the value type even have one?
- I don't think index-assignment (or attribute-assignment) is overloadable in C++, so you need `a[b]` to return something which can be assigned to, and proxy values tend to be really wonky (see: `vector<bool>`), so assigning to the result of the indexing would replace the dummy value
from collections import defaultdict
m = defaultdict(int)
m["nonexistent"]
len(m) # => 1
There are examples of the opposite too, though. Ruby's Hash does not insert the entry: m = Hash.new(0)
m["nonexistent"]
m.length # => 0It does if you initialise the hash with a block to do so:
m = Hash.new {|h, k| h[k] = 0}
m["nonexistent"]
m.length # => 1
That is, essentially, what defaultdict does. Just through subclassing (because `dict` does not provide such a fallback, but it has a magic method you can override to customise the mapping's behaviour in that case).if norvig isn't smart enough to use defaultdict without introducing bugs then i'm not either
(I am sure I never reported anything about the sudoku solver, since I never studied it.)
AFAIK basically just 3 wide-spread languages which do this, and they're all known for their idiosyncratic behaviour: C++, Perl, and PHP.
A few languages provide this as opt-in via a subtype (Python's defaultdict) or init (Ruby's Hash.new with a block parameter).
Some more will do it via an opt-in callsite API e.g. Python's `setdefault`, Java's `computeIfAbsent`, Rust's `entry` API.
my $x = $h{$v};
this is not safe because it autovivifies $h{$v} as an ARRAY ref: my $x = $h{$v}[$i];
with respect to idiosyncratic behavior, if a language doesn't have idiosyncratic behavior, it has no reason to exist; it's just gratuitously incompatible syntax for already-available semanticsEspecially annoying when looking up something in a const& map.
The alternative approach could have been to make operator[]= and operator[] different.
foo[bar] += baz;
... needs to provide operator+= with a mutable reference to foo[bar], and a maybe immutable reference to baz.
https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...
Which is actually better than C++ in more than one way. As a library author, you overload a+b, and you get a+=b for free. As a user, you know that a+=b always means the same thing as a=a+b (except for side effects) regardless of the library.
Similarly, you can't overload && and || in C# directly like you can in C++, but instead you overload "operator true" and "operator false", and you get the boolean ops consistently derived from that (which means that they can short-circuit properly when overloaded, unlike in C++).
salaries["Jim Bob"] += 10_000;
Calls the getter on salary to look up "Jim Bob", it finds maybe he's currently on 80_000, returns 80_000, we add 10_000 to that, getting 90_000 and then we call the setter on salary asking it to write 90_000 to "Jim Bob". Two lookup operations.It's also deeply unfortunate here that C# thinks "null" is both a plausible value for common types and a good way to distinguish lookup failure. So it's possible either that Jim Bob's salary literally is null for some insane reason or that Jim Bob wasn't on the payroll, and the effects here are identical.
The extra lookup would not be acceptable in C++ or Rust, though that feels much more reasonable in a language like C# or Go -- paying a little performance to buy nicer ergonomics. The needlessly dangerous API shape on the other hand is very C++ and ought not to exist in C#.
But, yes, it is true that you end up needing double lookup when combining compound assignment with indexing.
Lots of C# APIs do, in fact return null from Index (ie the [] operator) for "Not found", you're clearly writing much newer .NET code and so you're using a modern generic container which can't do this (because its return type can't be null)
> nor does it allow it by default even for reference types (assuming that you're compiling with nullability checks on, which has been the default for years)
My emphasis. I believe the default you're talking about starts in .NET 6 in late 2021. I don't think "since late 2021" qualifies as "for years".
C# Code I write from scratch does have nullability checks because I'd prefer to be in a language where "Null reference" isn't a thing, and this is as close as C# gets - but I maintain tonnes of code that was written three, five, even ten years ago.
I stand corrected on the timeline for nullable reference types being the default - for some reason I remembered it as being on by default for new projects since VS 2019.
auto [it, newly_created] = m.try_emplace(key);
Does the same, and gives back more information. For something like `m[key] = value`, you can do `m.insert_or_assign(key, value)`, and save the default construction + assignment when the key was not there.For ordinary lookup you don't want to randomly mutate the map anyway. If your reference to the map is const, you can't use operator[] anyway, fortunately. However `m.at(key)` is not great either when the key not being present is really not exceptional, and the dance with `m.find(key)` is often not too ergonomic. I still suggest to just use `find` here.
operator[] does more than lookup, but there are other functions for the other stuff. It should be pretty clear...?
Comparing to other languages I know, I couldn't think of another where collection[key] denotes insertion.
C++ simply generalized this model to all collections, in line with the overall "better C" design. But then for maps it means that [] has to proactively create an entry if you want []= to work like it does everywhere else.
Similarly, C++ iterators are generalized C pointers, which is also not an ideal abstraction in practice (and why few other languages do it that way).
It is just very convenient (hence the existence of defaultdict in python).
if(auto i = x.find(y); i!=x.end()){
// use i->{first,second}
}
Also ideally you should be able to do this: for(auto [key, value]: x.equal_range(y)) {
}
But infuriatingly, x.equal_range returns a pair instead of some range view so you need some additional infrastructure (either convert the result of equal range into a range or add some begin/end overloads for pair).The equal_range solution looks nice from a genericity perspective (look ma I do multimaps!) but is really unintuitive with regular maps imo. Unless I'm mistaken it also does not allow an else branch for when the value is not there, which is often needed.
Hopefully one day we will get proper patter matching and destructing in if statements (Herb Sutter has made significant progress on this).
cpp2 is both interesting and ambitious, I really hope it gets somewhere.
OTOH, try...else I find myself missing at times.
In practice C++ APIs where you'd naturally write Option<&T> use pointers and are consequently easier to use wrong, this is an example of Rust actually providing improved safety technology (whereas many commonly identified safety differences like bounds checking in Rust are cultural differences)
The reason std::optional doesn't allow references is because someone in the commitee objected that some users might be confused by the assignment semantics (will assignment rebind or assign through the reference?) and the proposal authors dropped it instead of spending too long discussing pro and cons. It can still be easily re-added if the commitee wants to.
No need to drag the rust in the discussion.
You could easily hack std::optional<T&> specifically - and several people have, not just Boost - however C++ has a long history of "clever" hacked specializations that turn out to be disastrous so that isn't attractive. It's called generic programming for a reason.
The fact that it looks like you might be able to rebind or assign through the reference is a design goof. To the extent that std::optional is just the Boost type with a sticker on, that's a goof by the people behind the Boost type. However, they were hemmed in by inadequate language functionality, WG21 has the option to go fix the language, and they didn't do that, so the blame for that goes to them and not Boost. Having now standardised std::optional with the design goof, there is no attractive route forward.
The correct answer to "someone's" question by the way should be neither and that's the goof in std::optional, assignment to a Maybe type is assignment to the Maybe type you can't assign a Foo to a Maybe<Foo>, imagine if C++ also thought this ought to work for std::vector<int> ...
Far from being inadequate, it is pretty trivial to do so: https://gcc.godbolt.org/z/n4vK4jeaG .
You still need to special-case references because they are not actual C++ objects and need special handling.
Here's a question for you, what is the type of a do expression which just returns from the function you're in ? I mean, it's just an expression, we don't have to actually evaluate it we can ask what the type is... can't we ?
As far as I can see, C++ does not have a suitable type, which is why e.g. some C++ functions end up with a special attribute to signify that they won't actually return. I can see why this type would be unsettling, it's like when the ancient Greeks didn't like zero and so they initially refused to treat it as an integer. The type you want here is the additive identity.
Because the type system doesn't really understand additive identity, it's a struggle to write such a guarantee - not as a hack but in the type system.
Even if I did, I have no idea what you are trying to say.
edit: sorry that was a bit harsh. Can you try to explain what property is C++ missing exactly to implement the Niche optimization? I suspect you are trying to say that C++ lacks proper sum types (which is true), but that's really orthogonal to the optimization.
Over in Rust I really want to make BalancedI8, BalancedI16 and so on, types which are exactly the same as the ordinary signed integers of each size, except, their smallest possible value (e.g. i8::MIN ie -128) is gone (so BalancedI8::MIN is -127), leaving them nicely balanced between negative and positive, their abs() method doesn't have a hidden surprise - and of course this offers the smallest possible niche so Option<BalancedI8> fits in a single byte.
I obviously can in some sense do that today and I did -- the types are published, but today I can't use them from stable Rust, these types require a (deliberately permanently unstable) Rust-internal attribute telling the compiler about the niche, and they also can't be safely constructed (obviously I provide a safe function to make one, but you can't just conjure one into existence without calling the function, yes it gets inlined but that's not very ergonomic)
This is not a desirable state of affairs. As usual in Rust, the reason for the perma-unstable feature flag is that there's a germ of an idea of the Right Thing™ and so the eventual removal of the feature flag is predicated on obsoleting the feature by having the Right Thing™ in the language itself, not stabilization of the existing flag. Rust types which provide a niche will some day remove the feature flag and instead be defined using the language in the now-natural way, yet to end users nothing seems to have changed. The backwards incompatible change lives only inside private internals of the standard library so it's fine.
In this case that Right Thing turns out to be Pattern Types. What if we actually just tell the type system what's going on here about which values a type can actually have versus which ones are niches by providing a Pattern for the type. So it knows everywhere in the language that -128 may be a valid i8 but it's not a valid BalancedI8 because the pattern doesn't match.
However, Pattern Types might take quite a while to produce even a basic MVP implementation that can be stabilized, and so for a while I was pushing to get some way to just have what I want stabilized, sooner - without all of Pattern Types, and then replace it later using Pattern Types. But I was persuaded that actually this is poison, if you introduce my hack as a stable feature then it cannot ever actually be translated into a Pattern Type in user's code, so it becomes this weird orphan feature, "tialaramex's stupid niche type hack" forever even though all new types would use Pattern Types to achieve a much superior outcome, there's no known way to fix any of my hacked types.
So in effect you're making the arguments I was making like a month ago, that I now see are hopeless, which is why I'm asserting so vigorously that this is just a hack and what you should aim for instead is to explain to the type system what's going on even though that's harder.
The question about decltype ( do { return; } ) is pertinent because that type is to a type system like zero is to the integers in Z. I hope I spelled that correctly - I can't Godbolt it because do expressions are a speculative design, but hopefully you get the idea. This problem is easier to express with a do expression than with other C++ where the same problem arises but is harder to exhibit.
If rust devs do not want to add a specialization or traits for this feature and wait for the Right Thing it is fine. It is such a niche (get it?) requirement that it is not worth polluting the standard library.
But if you really need it as an user, I don't think the trait solution is an hack at all. I don't see specialization as an hack in general and in fact is important for writing efficient code.
Ideally you would be able to encode every possible property of your program elegantly and generically in the type system while keeping the language lean, fast and easy to understand. In practice at any point in time only a language will only have a finite subset of features and an user can't wait forever for the language to reach perfection. Any 50+ year language will necessarily accumulate cruft. Embrace it. Worse is better.
Yes but the earliest that could happen is, like, 2026 or perhaps 2027. Because you have to wait for both a new std revision to become available.