How do I know if std::map insert succeeded or failed?
Asked Answered
C

8

11

I have a map in a multithreaded app mapping a class called uuid to pointer. What I want to know if an insert operation succeeded for failed.

e.g.

_mymap.insert(hint, MyMap::value_type(entry.uuid, itemptr));

Does it throw an exception or something if it fails?

Christadelphian answered 24/2, 2011 at 14:46 Comment(0)
L
19

In fact the insert method which takes a hint parameter does not return whether the insert succeeded or not. One way to check if insert actually happened would be to check the size of the map before and after insertion. If it is the same, then insert has failed (i.e. the key was already present). I know it sounds ugly, but that's the most efficient way I could come up with. In fact I believe that there is no compelling reason that the insert with a hint should not return a pair (including the bool) just like the regular non-hint insert does. But once it has been specified in an older standard, it is very hard to change, since it is a breaking change, which the C++ community mostly resents.

Original (wrong) answer

See this link

... returns a pair, with its member pair::first set to an iterator pointing to either the newly inserted element or to the element that already had its same value in the map. The pair::second element in the pair is set to true if a new element was inserted or false if an element with the same value existed.

The link also contains an example

For example:

if(mp.insert(make_pair(key, value)).second == false)
{
   cout << "Insertion failed. Key was present"
}
Luncheon answered 24/2, 2011 at 14:52 Comment(6)
In fact, this is incorrect. The insert() method with a hint parameter does not return a pair, but just an iterator.Shote
IMPORTANT: If you just insert an element WITHOUT the hint parameter, use the answer in the See this link. The original answer was wrong because it is for the case where we don't need the hint parameter, but the poster of the question need the hint. The original answer is NOT wrong if we don't need the hint, which is often the case.Unbiased
Concerning the reason for the hint-ed version of insert returning an iterator instead of a pair, Nicolai M. Josuttis, in his "The C++ Standard Library" (both 1st and 2nd versions), justifies it by saying that this way you have one insert function that has the same interface for all container types defined in the STL (vector, deque, list, set, multiset, map, multimap).Adda
Depending on your definition of failure, you might need insert_or_assign (c++17 standard) whose returned boolean (if the version without the hint is used) differentiates between the insertion case (true) and the assignment case (false). See the referenceAurelea
The compelling reason is given in CPP Reference: "The hinted insert (3,4) does not return a boolean in order to be signature-compatible with positional insert on sequential containers, such as std::vector::insert. This makes it possible to create generic inserters such as std::inserter." I second your recommendation to check the size before and after - it's not hard to write a small function for that.Invert
The other reason is that the hint often comes from a call to upper_bound or lower_bound, in which case you already know whether there's a key collision or not.Fluellen
K
7
typedef std::map<std::string, int> map;
map m;
std::pair<map::iterator,bool> result = m.insert(std::make_pair("hi", 42));

result.second contains what you want

Konstanze answered 24/2, 2011 at 14:53 Comment(1)
That's a different insert(), without the hint argument. The version that accepts a hint returns an iterator, not a pair.Invert
M
5

It depends what you mean by failed or succeeded.

std::map::insert succeeds when it inserts the new element, otherwise it returns an iterator to an already existing element.

std::map::insert fails if there is not enough memory to insert a new element and throws std::bad_alloc.

Malarkey answered 24/2, 2011 at 14:58 Comment(0)
S
2

The first insert member function returns a pair whose bool component returns true if an insertion was made and false if the map already contained an element whose key had an equivalent value in the ordering, and whose iterator component returns the address where a new element was inserted or where the element was already located.

To access the iterator component of a pair pr returned by this member function, use pr.first, and to dereference it, use *(pr.first). To access the bool component of a pair pr returned by this member function, use pr.second.

The second insert member function, the hint version, returns an iterator that points to the position where the new element was inserted into the map.

Source: http://msdn.microsoft.com/en-us/library/81ac0zkz(v=vs.80).aspx

Singularize answered 24/2, 2011 at 14:48 Comment(4)
Ok, that's pretty hard to understand. Can you give me an example. MS doesn't actually have one that I can see.Christadelphian
I think your link is wrong. It points to a page with the title "System.Collections.Generic Namespace".Houghton
Yes and I'd like to point out I'm using gcc under linux. Hopefully all STLs are created equal?Christadelphian
@Matt H: Yes, the STL is standardized.Houghton
H
1

Yes, it would throw one of the exceptions used in the STL, e.g. when out of memory. That is in case of failure.

Or were you also interested in knowing whether the element was already contained in the instance?

Houghton answered 24/2, 2011 at 14:52 Comment(0)
I
0

insert() throws std::bad_alloc if it tried to insert but failed; if it chose not to insert because a value with that key already exists, then it does not throw. It just returns an iterator to the existing element.

The version of insert() that takes a hint just returns an iterator, unlike the non-hinted version which also returns an indication of whether the value was inserted or was already present.

CPP Reference suggests using change of size() to determine whether the insert was successful. We could try something like

// untested
template<class K, class V, class C, class A, class Element>
auto insert(std::map<K,V,C,A>& map,
            std::map<K,V,C,A>::const_iterator hint,
            Element&& element)
{
     auto const initial_size = map.size();
     auto const it = map.insert(hint, std::forward<Element>(element));
     auto const was_added = map.size() != initial_size;
     return std::pair{it, was_added};
}

Actually, for the example in the question (which constructs a key-value pair), we probably want to be wrapping the emplace_hint() member function, so we could pass either a pair or separate key and value:

// untested
template<class K, class V, class C, class A, class... Args>
auto emplace(std::map<K,V,C,A>& map,
             std::map<K,V,C,A>::const_iterator hint,
             Args&&... args)
{
     auto const initial_size = map.size();
     auto const it = map.emplace_hint(hint, std::forward<Args>(args)...);
     auto const was_added = map.size() != initial_size;
     return std::pair{it, was_added};
}
Invert answered 3/8 at 11:25 Comment(0)
D
-1

Comparing the size of the map before insertion and after insertion to know if an element was inserted is slow (map::size is slow with Debian 8: browsing complete map)

It is possible to know whether a pair was inserted or found in a map after insertion with hint without recalculating size, using hint insertion method.

Insert with hint method another pair of the same first, and of a second that you are sure is not in the map ( like -1 for a map of positive second int). If the iterator returned has this impossible value, it has been newly inserted, if not it was found in the map. The iterator returned may then be changed. Example : to insert pairs p (2,4) and p (6, 5) in the map<int, int> m ((0, 1), (2, 3), (4, 5)).

int main (int argc, char* argv []) {
  std::pair<int, int> tmp [3] = {
    std::pair<int, int> (0, 1),
    std::pair<int, int> (2, 3),
    std::pair<int, int> (4, 5)
  };
  std::map<int, int> m ((std::pair<int, int>*) tmp, (std::pair<int, int>*) &tmp [3]);

  std::cout << "initial map == ";
  std::for_each (m.begin (), m.end (), [] (const std::pair<int, int>& p) {
    std::cout << p.first << "->" << p.second << "   ";
  });
  std::cout << std::endl;
  std::cout << std::endl;

  {
    //insertion of a pair of first already in map
    std::cout << "insertion of pair 1 == std::pair<int, int> (2, 4) from second iterator" << std::endl;
    std::map<int, int>::iterator ihint (m.begin ()), k (ihint); ++ihint;
    std::pair<int, int> pfoo (2, -1);
    k = m.insert (ihint, pfoo);
    if (k->second == -1) {
      std::cout << "\tthe pair was inserted" << std::endl;
      k->second = 4;
    }
    else {
      std::cout << "\ta pair with such a first was in the map" << std::endl;
    }
  }
  std::cout << "m after insertion of pair 1 == ";
  std::for_each (m.begin (), m.end (), [] (const std::pair<int, int>& p) {
    std::cout << p.first << "->" << p.second << "   ";
  });
  std::cout << std::endl;
  std::cout << std::endl;

  {
    //insertion of a pair of first not in map
    std::cout << "insertion of pair 2 == std::pair<int, int> (6, 5) from third iterator" << std::endl;
    std::map<int, int>::iterator ihint (m.begin ()), k (ihint); ++ihint; ++ihint;
    std::pair<int, int> pfoo (6, -1);
    k = m.insert (ihint, pfoo);
    if (k->second == -1) {
      std::cout << "\tthe pair was inserted" << std::endl;
      k->second = 5;
    }
    else {
      std::cout << "\ta pair with such a first in the map" << std::endl;
    }
  }
  std::cout << "m after insertion of pair 2 == ";
  std::for_each (m.begin (), m.end (), [] (const std::pair<int, int>& p) {
    std::cout << p.first << "->" << p.second << "   ";
  });
  std::cout << std::endl;
}

outputs : initial map == 0->1 2->3 4->5

insertion of pair 1 == std::pair<int, int> (2, 4) from second iterator

a pair with such a first was in the map

m after insertion of pair 1 == 0->1 2->3 4->5

insertion of pair 2 == std::pair<int, int> (6, 5) from third iterator

the pair was inserted

m after insertion of pair 2 == 0->1 2->3 4->5 6->5

Desmund answered 26/6, 2016 at 11:59 Comment(4)
As my answer was downvoted with no explanation, I added initial map construction, inserted two pairs (one in the map, one not) and print hint insertion results. Using preincrementation, faster than post incrementation. The map I am using is not anymore called "map", but "m", so it wont confuse people "using namespace std", but just "m". Modification of foo iterator is just performed in case of insertionDesmund
I've not downvoted you, but your answer is for sure not easly readable (I'm talking about the text, not the code).Adda
my example explains how to kow wether a pair was inserted or retrieved in a map using insertion with hintDesmund
CPP Reference suggests a much simpler test: "One way to check success of a hinted insert is to compare size() before and after."Invert
H
-2

According to the documentation, std::map insert returns a pair of two values: "an iterator to the inserted element (or to the element that prevented the insertion) and a bool value set to true if and only if the insertion took place."

Depending on the types you are using for mapping, you can potentially do something like what Tristram suggested above, where you create a result pair. However, if you only want one of the values (e.g. the bool that states whether insertion was successful or not), something like this might be simpler:

If you only want the bool, you can do

bool insert_completed = (mymap.insert({key,value})).second;

or if you only want the iterator, you can do

auto inserted_iterator = (mymap.insert({key,value})).first;

This basically says "hey, I just got a pair as a result from insert(), I only care about one piece of that pair, so let me set some variable equal to that piece".

However, note that this doesn't work with every version of insert(). Some return values from insert are only the iterator, some return nothing, one even returns an insert_return_type, so please check the documentation to see which particular case is relevant to the code you're writing.

Helle answered 31/7 at 19:7 Comment(1)
That's a different insert(), without the hint argument. The version that accepts a hint returns an iterator, not a pair.Invert

© 2022 - 2024 — McMap. All rights reserved.