33

According to cppreference.com, std::map::operator[] for non-existing value does zero-initialization.

However, the same site does not mention zero-initialization for std::unordered_map::operator[], except it does have an example which relies on this.

Of course this is just a reference site, not the standard. So, is the code below ok or not?

#include <unordered_map>
int main() {
    std::unordered_map<int, int> map;
    return map[42];     // is this guaranteed to return 0?
}
Sander De Dycker
  • 16,053
  • 1
  • 35
  • 40
hyde
  • 60,639
  • 21
  • 115
  • 176
  • 13
    @Ælex you cannot reliably test if something is initialized – 463035818_is_not_an_ai Dec 05 '19 at 09:55
  • 2
    @Ælex I dont really understand, how can you have a non-initialized `std::optional`? – 463035818_is_not_an_ai Dec 05 '19 at 10:21
  • 2
    @Ælex there is no way to test if an object is initialized or not because any operation on an uninitialized object other than initialization results in Undefined Behavior. A `std::optional` object that holds no contained value is still an initialized object. – bolov Dec 05 '19 at 22:06
  • 2
    The value object is value-initialized, not zero-initialized. For scalar types these are the same, but for class types they are different. – aschepler Dec 06 '19 at 01:40
  • @bolov I tried to test it yesterday using gnu 17 and std 17, and bizarrely all I got was zero initialisation. I thought `std::optional` `has_value` would test it but it fails, so I guess you are correct. – Ælex Dec 06 '19 at 10:23
  • @Ælex In that test, what did you do to ensure that the memory location where your `std::optional` was placed was in a already used memory page, *and* that memory location was also previously set to something else than zero? Or did you inspect the produced assembly and noticed it explicitly writes zeroes there? – hyde Dec 06 '19 at 12:08
  • @hyde I intentionally left an uninitialised variable and tried wrapping it with `std::optional` and I think the compiler optimised it to zero. I did not go into the debugger, but could give it a try. – Ælex Dec 06 '19 at 12:14

2 Answers2

24

On the site you linked it says:

When the default allocator is used, this results in the key being copy constructed from key and the mapped value being value-initialized.

So the int is value-initialized:

The effects of value initialization are:

[...]

4) otherwise, the object is zero-initialized

This is why the result is 0.

Blaze
  • 16,736
  • 2
  • 25
  • 44
19

Depending on which overload we're talking about, std::unordered_map::operator[] is equivalent to [unord.map.elem]

T& operator[](const key_type& k)
{
    return try_­emplace(k).first->second;
}

(the overload taking an rvalue-reference just moves k into try_emplace and is otherwise identical)

If an element exists under key k in the map, then try_emplace returns an iterator to that element and false. Otherwise, try_emplace inserts a new element under the key k, and returns an iterator to that and true [unord.map.modifiers]:

template <class... Args>
pair<iterator, bool> try_emplace(const key_type& k, Args&&... args);

Interesting for us is the case of there being no element yet [unord.map.modifiers]/6:

Otherwise inserts an object of type value_­type constructed with piecewise_­construct, forward_­as_­tuple(k), forward_­as_­tuple(std​::​forward<Args>(args)...)

(the overload taking an rvalue-reference just moves k into forward_­as_­tuple and, again, is otherwise identical)

Since value_type is a pair<const Key, T> [unord.map.overview]/2, this tells us that the new map element will be constructed as:

pair<const Key, T>(piecewise_­construct, forward_­as_­tuple(k), forward_­as_­tuple(std​::​forward<Args>(args)...));

Since args is empty when coming from operator[], this boils down to our new value being constructed as a member of the pair from no arguments [pairs.pair]/14 which is direct initialization [class.base.init]/7 of a value of type T using () as initializer which boils down to value initialization [dcl.init]/17.4. Value initialization of an int is zero initialization [dcl.init]/8. And zero initialization of an int naturally initializes that int to 0 [dcl.init]/6.

So yes, your code is guaranteed to return 0…

Michael Kenzel
  • 15,508
  • 2
  • 30
  • 39