Std :: unordered_map :: operator [] - why are there two signatures?

In C ++ 11, there are two versions of std :: unordered_map :: operator [] , namely:

mapped_type& operator[] ( const key_type& k ); //1 mapped_type& operator[] ( key_type&& k ); //2 

There are two questions:

1) Why the second is needed - the first allows you to pass a constant to the function, since the first contains the const keyword

2) For example, which version, 1 or 2, will be called in this case:

 std::unordered_map<std::string, int> testmap; testmap["test"] = 1; 
+5
source share
2 answers

Usually, the key is used only for comparison purposes, so you may wonder why rvalue semantics are needed: a reference to const should already cover this case.

But it is worth noting that the operator [] can indeed create a new key / value pair: if the key did not exist on the map yet.

In this case, if the second overload was used, the card can safely move the provided key value on the card (when initializing the default value). In my opinion, this is a rather rare and insignificant optimization, but when you are a standard C ++ library, you should not make any efforts to save someone else's cycle, even if this happens only once!

Regarding the second question, I might be wrong, but it should consider the second overload as the best overload.

Edit: There is also a valid point that it can allow you to use motion objects only as key values, even if this is a moot decision

+6
source

This is there for performance reasons. For example, if the key is an rvalue, the key is moved rather than copied when a new element is inserted.

This way you avoid an extra copy of the object / key. You can see this in the following example:

 #include <iostream> #include <unordered_map> struct Foo { Foo() { std::cout << "Foo() called" << std::endl; } Foo(Foo const &other) { std::cout << "Foo(Foo const &other) called" << std::endl; } Foo(Foo &&other) { std::cout << "Foo(Foo &&other) called" << std::endl; } int i = 0; }; bool operator==(Foo const &lhs, Foo const &rhs) { return lhs.i == rhs.i; } void hash_combine(std::size_t& seed, const Foo& v) { std::hash<int> hasher; seed ^= hasher(vi) + 0x9e3779b9 + (seed<<6) + (seed>>2); } struct CustomHash { std::size_t operator()(Foo const& v) const { std::size_t res = 0; hash_combine(res, v); return res; } }; int main() { std::unordered_map<Foo, int, CustomHash> fmap; Foo a; ai = 100; fmap[a] = 100; fmap[Foo()] = 1; } 

Live demo

Conclusion:

 Foo() called Foo(Foo const &other) called Foo() called Foo(Foo &&other) called 

As can be seen in the case of fmap[Foo()] = 1; the rvalue object moves unlike the operator fmap[a] = 100; where the copy constructor is called.

+1
source

All Articles