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…