15

Would you teach me why both

std::unordered_map::insert(const value_type&)

and

template<class P> std::unordered_map::insert(P&&)

exist in the standard?

I think that insert(P&&) can serve as insert(const value_type&).

billz
  • 44,644
  • 9
  • 83
  • 100
Mitsuru Kariya
  • 938
  • 4
  • 14
  • Legacy from TR1 possibly. Maybe they wanted to be safe and not remove functions, only add them. Perhaps they weren't certain at the time of the impact it might have on existing code that used TR1. – Benjamin Lindley Feb 06 '13 at 04:58
  • I tried to construct a [program](http://liveworkspace.org/code/4y16As$46) that fails to compile if you remove `insert(const value_type&)`, and failed. Anyone have thoughts? – Yakk - Adam Nevraumont Feb 06 '13 at 05:13
  • std::vector v1; v1.push_back(4); insert(v1.back()); seems to not be compiling for example. I expect whenever the value is constant and not movable the const ref method is used. –  Feb 06 '13 at 08:27
  • @typical *"the const ref method is used"* - Ah, so you mean the method where `P` is deduced as `const value_type&` and `P&&` collapses to `const value_type&`? Oh wait... – Christian Rau Feb 06 '13 at 10:25
  • No, sir. I am saying that inserting vector.back() value call without insert function taking const ref my example did not compile with error: main.cpp:349:22: error: no matching function for call to ‘insert(__gnu_cxx::__alloc_traits >::value_type&, std::vector&)’ –  Feb 07 '13 at 07:46

3 Answers3

7

Both of these overloads

auto std::unordered_map::insert(const value_type&) -> ...

template<class P>
auto std::unordered_map::insert(P&&) -> ...

have their advantages and neither can fully replace the other. The first one seems like a special case of the second one since P might be deduced to be const value_type&. The nice thing about the 2nd overload is that you can avoid unnecessary copies. For example, in this case:

mymap.insert(make_pair(7,"seven"));

Here, the result of make_pair is actually a pair<int, const char*> whereas value_type might be pair<const int, string>. So, instead of creating a temporary value_type object and copying it into the container, we have the chance of directly creating the value_type object into the map by converting the argument and/or moving its members.

On the other hand, it would be nice if this worked as well:

mymap.insert({7,"seven"});

But this list is actually not an expression! The compiler can't deduce P for the second overload because of that. The first overload is still viable since you can copy-initialize a pair<const int,string> parameter with such a list.

sellibitze
  • 27,611
  • 3
  • 75
  • 95
  • Thanks a lot!! Since I cannot understand completely yet why `insert(const value_type&)` is viable for `insert({7,"seven"})`, I read the standard more. Thanks again!! – Mitsuru Kariya Feb 06 '13 at 17:58
  • I posted a [related question](http://stackoverflow.com/questions/14757809/overload-of-stdunordered-mapinsert-reloaded). – Mitsuru Kariya Feb 07 '13 at 18:10
4

The template universal reference overload was added in n1858, with rationale (for map, but the same explicitly applies to multimap):

Two of the insert signatures are new. They have been added to allow moving from rvalue types other than value_type, which are convertible to value_type. When P instantiates as an lvalue, the argument is copied into the map, else it is moved into the map (const qualifiers permitting).

(The other insert signature referred to is insert-with-hint.)

We also refer to the rationale for deque (again, explicitly referenced for other containers):

All member functions which insert (or append, prepend, etc.) a single value_type into the container are overloaded with a member function that accepts that value_type by rvalue reference so that single value_type's can be moved into the container. This not only makes working with heavy weight types much more efficient, it also allows one to insert movable but non-copyable types into the container.

It's apparent that the changes were considered principally as additions; it wasn't considered at the time that the template overload could replace the original (C++03) insert entirely. This can be seen by referring to the earlier n1771, which provides some motivation for the template overload, taking a different approach:

Note below that for map and multimap that there are two new insert overloads, both taking a pair with a non-const key_type. One can not move from a const key_type, and therefore to be able to move a key_type into the (multi)map, a pair must be used. There are overloads for both a const lvalue pair, and a non-const rvalue pair so that lvalue pair's will not be moved from.

pair<iterator, bool> insert(const value_type& x);  // CC
pair<iterator, bool> insert(const pair<key_type,mapped_type>& x);  // CC
pair<iterator, bool> insert(pair<key_type,mapped_type>&& x);

(CC is an abbreviation for CopyConstructible.)

It appears then that the template overloads were added to map and multimap without realising that they made the const value_type & overloads redundant. You might consider submitting a defect report to have the redundant overloads removed.

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • Thank you for the answer. However, I think that the form `insert({...})`(in sellibitze's answer) is useful too, even if a copy occurs. – Mitsuru Kariya Feb 06 '13 at 18:20
2

the difference lies in the type of reference used. The first

std::unordered_map::insert(const value_type&)

uses a Reference (C++03) now called an lvalue Reference in (C++11). This needs to be const. C++11 introduced rvalue References P&& which need not to be const. To allow for both, two insert functions are provided.

Please see this excellent answer on StackOverflow wrt rvalue References in C++11, I hope this helps to answer your question.

What does T&& (double ampersand) mean in C++11?

As you said, it is possible to use the rvalue-overload and just pass a const lvalue ref, but - see this text from http://msdn.microsoft.com/en-us/library/dd293668.aspx

By overloading a function to take a const lvalue reference or an rvalue reference, you can write code that distinguishes between non-modifiable objects (lvalues) and modifiable temporary values (rvalues).

-Hannes

Community
  • 1
  • 1
Hannes M
  • 741
  • 7
  • 20
  • oops, sorry for the repetition of the link. I started to write my answer when there was no answer yet, but got interrupted. – Hannes M Feb 06 '13 at 10:01
  • 1
    Instead of just reading the first part (about move semantics) of both linked posts, you should probably also read the second parts regarding perfect forwarding and the reference collapsing rules. Since `insert` will either copy or move the passed argument there is no reason to distinguish between lvalues and rvalues in insert itself, `value_type`'s constructor will do that for you. – Christian Rau Feb 06 '13 at 10:21