If I move a pair into a map, but the insert failed because the key already exists, can I safely use the pair afterwards?
//objects available: map, pair
auto insert_pair = map.insert(std::move(pair));
if (!insert_pair.second)
{
//can I safely access pair here?
}
Has this been documented in the standard?
Use -> to access the element pointed to by a pointer and a . to access a member variable. In this case, map is a container and pair a struct so you have to access the elements of both with . . Save this answer.
The standard solution to insert new elements into a map is using the std::map::insert function. It inserts the specified key-value pair into the map only if the key already doesn't exist. If the key already exists in the map, the element is not inserted.
std::map is a sorted associative container that contains key-value pairs with unique keys. Keys are sorted by using the comparison function Compare . Search, removal, and insertion operations have logarithmic complexity. Maps are usually implemented as red-black trees.
For how non-sense this may seem (read below), given the current state of the specification you cannot make any assumption on the state of the argument after the function call returns.
To see why, let's first point out that the insert()
member function is defined in terms of emplace()
(see 23.4.4.4/1):
The first form is equivalent to
return emplace(std::forward<P>(x))
. [...]
The post-conditions of emplace()
are in turn specified as (see § 23.2.4, Table 102):
Inserts a
value_type
objectt
constructed withstd::forward<Args>(args)...
if and only if there is no element in the container with key equivalent to the key oft
. Thebool
component of the returned pair istrue
if and only if the insertion takes place, and the iterator component of the pair points to the element with key equivalent to the key oft
.
The sentence in bold from the above quote (emphasis is mine) says that the pair that will become an element of the map if the key is not already present will be move-constructed directly from the rvalue pair you supply.
This would make it very very reasonable to deduce that implementations will first have to check if the key is present and, only if not, move-construct the new map's element from the your pair.
However, "very very reasonable" is not a valid argument when dealing with the specification of a formal language. In this case, from the formal viewpoint there is nothing that prevents an implementation from doing the following:
tmp
from your argument (which would mean you can't make assumptions on the state of your argument after the function returns);tmp
into the container.Or even:
Point 3 above is absolutely meaningless, but it is not formally forbidden. Mind the phrasing:
Inserts a
value_type
objectt
constructed withstd::forward<Args>(args)...
if and only if there is no element in the container with key equivalent to the key oft
.
This only says that if there is no element in the container with key equivalent to the key of t
, no object move-constructed from t
will be inserted into the map - but, for how stupid this may sound, it does not say that no object at all shall be move-constructed from t
: as long as it does not get inserted into the map, this is allowed.
This said, since the Standard does not explicitly constrain implementations in this respect, you cannot make assumptions on whether or not your argument was moved from. Consequently, you cannot make assumptions on the state your pair will be in when the function call returns (per paragraph 17.6.5.15).
If I can let a personal opinion sneak in, though, I believe this is a defect.
I couldn't find anything concrete, but as far as the standard goes regarding types defined by the STL (e.g., pair):
17.6.5.15: Unless otherwise specified, such moved-from objects shall be placed in a valid but unspecified state.
Without satisfying that "otherwise specified", which I haven't been able to find for a "failed" map::insert, this would mean that by the standard you could not use the pair. In reality, based on sensical implementations, I imagine that the pair would remain unchanged, but relying on this would fall into the realm of undefined compiler/stl-specific behavior.
From Table 102 in section 23.2.4 Associative containers of the c++11 standard (draft n3337) the following is stated (for expression a_uniq.insert(t)
which applies in this case):
Requires: If t is a non-const rvalue expression, value_type shall be MoveInsertable into X; otherwise, value_type shall be CopyInsertable into X. Effects: Inserts t if and only if there is no element in the container with key equivalent to the key of t. The bool component of the returned pair is true if and only if the insertion takes place, and the iterator component of the pair points to the element with key equivalent to the key of t.
It makes no statement on any effect on t
if an insertion does not occur (I am unsure whether this falls under unspecified or implementation-defined behaviour). I was unable to locate any other clause that provided further clarification so it appears the standard does not supply a definitive answer. It would be possible to rewrite the posted code to only call insert()
if the pair is not present or just use the returned iterator.
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