The standard "map" containers in C++ allow you to insert an rvalue:
T x;
std::map<int, T> m;
// m[1]; // populate "1"
auto it = m.insert(std::make_pair(1, std::move(x)));
The question is what happens when the element already exists, i.e. it->second == false
. Has the element x
already been "moved-from"? For instance, if it is a unique pointer, will x
have been reset to null?
Patently the answer in the above case is "yes", because the moving-from happens already at the point of creating the pair. But suppose now I want to update the existing value, but still retain the information of whether the value already existed or not (so I can't just say m[1] = std::move(x);
). Is it possible to "not move from" the object in that case?
I discovered in GCC that the following works [Update: works in GCC 4.6, does not work in GCC 4.8]:
auto it = m.insert(std::pair<const int, T &&>(1, std::move(x)));
But is this guaranteed to not move?
The map::insert() is a built-in function in C++ STL which is used to insert elements with a particular key in the map container. Parameters: The function accepts a pair that consists of a key and element which is to be inserted into the map container.
C++ hash map and hash set which preserves the order of insertion. The ordered-map library provides a hash map and a hash set which preserve the order of insertion in a way similar to Python's OrderedDict. When iterating over the map, the values will be returned in the same order as they were inserted.
std::move is used to indicate that an object t may be "moved from", i.e. allowing the efficient transfer of resources from t to another object. In particular, std::move produces an xvalue expression that identifies its argument t . It is exactly equivalent to a static_cast to an rvalue reference type.
The C++ function std::map::at() returns a reference to the mapped value associated with key k.
Though std::move
does not actually perform any move, and neither does std::make_pair
, std::make_pair
forwards its arguments to the std::pair
constructor, which initialises its two value members from those arguments.
As such, the move is performed at that point, before the std::map
has a chance to do anything. So, yes, you end up with a "broken" move for no good reason.
You should be able to take advantage of emplace
(in order to skip the pair construction). From Table 102:
Effects: Inserts a
T
objectt
constructed withstd::forward<Args>(args)...
if and only if there is no element in the container with key equivalent to the key oft
.
Clearly, the library is still "forwarding" at this point so it's pre-move, and in your case no emplace shall take place so the entire expression ought to be an effective no-op.
However, libstdc++ from GCC 4.8.0 appears to have a bug in this regard: emplace
invokes _M_emplace_unique
on the internal tree, which forwards the arguments to _M_create_node
, which forwards the arguments to allocator_traits<_Node_allocator>::construct
, which forwards the arguments to _S_construct
, which forwards the arguments to __a.construct
which, with the default allocator, is std::allocator<std::pair<const _Key, _Tp> >::construct
, which is the pair constructor you were trying to avoid... all before the collision check in _M_emplace_unique
.
It could be claimed that the standard is ambiguous in this regard, but I'd call it a violation of intent. Then again, clang v3.4 with libc++ exhibits this behaviour too, as does Visual Studio 2012. So if my standard interpretation is correct, this fails on all three of the mainstream toolchains.
I guess they all decided that the "if and only if" applied to the insertion, rather than the insertion and the construction.
I have posted a question on std-discussion aiming to provoke an improvement to the passage from Table 102 to authoritatively answer this once and for all.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With