Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing a pair after moving it into a map

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?

like image 740
roger.james Avatar asked Jul 09 '13 15:07

roger.james


People also ask

How do you access a pair on a map?

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.

How to add element to a map?

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.

How is std::map sorted?

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.


3 Answers

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 object t constructed with std::forward<Args>(args)... 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.

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:

  1. First move-construct a pair tmp from your argument (which would mean you can't make assumptions on the state of your argument after the function returns);
  2. Check if the key is already present in the map;
  3. If not, do the necessary housekeeping to insert tmp into the container.

Or even:

  1. Check if the key is present in the map;
  2. If so, insert a new element move-constructed from your argument;
  3. If not, move-construct a new object from your argument and do nothing with it.

Point 3 above is absolutely meaningless, but it is not formally forbidden. Mind the phrasing:

Inserts a value_type object t constructed with std::forward<Args>(args)... if and only if there is no element in the container with key equivalent to the key of t.

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.

like image 84
Andy Prowl Avatar answered Sep 21 '22 11:09

Andy Prowl


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.

like image 37
zennehoy Avatar answered Sep 21 '22 11:09

zennehoy


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.

like image 29
hmjd Avatar answered Sep 18 '22 11:09

hmjd