How to Know If Std::Map Insert Succeeded or Failed

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

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"
}

Why the std::map::insert failed in the following example?

As stated on the cppreference.com the std::map::insert:

Inserts element(s) into the container, if the container doesn't
already contain an element with an equivalent key.

In // Overload 1:

The iterator it_hinata is pointing to the lastly inserted entry which is {"Hinata"s, 162.8} and if you try to enter the same key-value pair, the insertion fails and hence success2 == false.

In // Overload 4:

The iterator it_hinata is still pointing to the same (firstly) inserted key-value pair(i.e. same {"Hinata"s, 162.8}). Therefore, the same reason in the above case, insertion fails. That means, the size of the map(i.e. karasunoPlayerHeights) remains the same after the insertion call, and the condition std::size(karasunoPlayerHeights) != n evaluated to false.

Following is the minimal, complete reproducible example from what OP posted:

#include <iomanip>
#include <iostream>
#include <map>
#include <string>
using namespace std::literals;

template<typename It> void printInsertionStatus(It it, bool success)
{
std::cout << "Insertion of " << it->first << (success ? " succeeded\n" : " failed\n");
}

int main()
{
std::map<std::string, float> karasunoPlayerHeights;

// Overload 3: insert from rvalue reference
const auto [it_hinata, success] = karasunoPlayerHeights.insert({ "Hinata"s, 162.8f });
printInsertionStatus(it_hinata, success);
{
// Overload 1: insert from lvalue reference
const auto [it, success2] = karasunoPlayerHeights.insert(*it_hinata);
printInsertionStatus(it, success2);
}
{
// Overload 4: insert from lvalue reference with positional hint
const std::size_t n = std::size(karasunoPlayerHeights);
const auto it = karasunoPlayerHeights.insert(it_hinata, *it_hinata);
printInsertionStatus(it, std::size(karasunoPlayerHeights) != n);
}
// Print resulting map
std::cout << std::left << '\n';
for (const auto& [name, height] : karasunoPlayerHeights)
std::cout << std::setw(10) << name << " | " << height << "cm\n";
}

which outputs:

Insertion of Hinata succeeded
Insertion of Hinata failed
Insertion of Hinata failed

Hinata | 162.8cm

how to check if a std::map insert fails in c++?

map::insert returns a pair< iterator, bool > where the bool indicates if the item has been inserted or if the key is already present in the list. If the key is already present, the iterator will point to this item.

Recover moved element after failed move insertion/emplace in std::unordered_map

If using C++17 features is an option, try_emplace will only move the arguments if the key doesn't already exist in the map.

Otherwise, you can have your own version to get (functionally) the same effect, by combining find and emplace (or insert).

Note that this will likely be less efficient than the try_emplace implementation, if it exists (as you have 2 searches through the container if the key isn't in the map).

map inserting keys but not value

Certainly would help if you wrote

currentFullState[context] = data;

instead of

currentFullState[context] == data;

Also

auto result = currentFullState.insert(std::make_pair(context, data));

should be preferred to

auto result = currentFullState.insert(std::make_pair(context, data.c_str()));

Slightly surprised that the second one compiles.

=========================================================================

The real reason the insert fails is that you are adding that key for the second time. This is the first time

if (currentFullState[context] == sDEFAULT_STRING)

operator[] on a map always adds the key to the map. This is why your second attempt to add with

auto result = currentFullState.insert(std::make_pair(context, data.c_str()));

fails, the key is already present. If you had written

currentFullState[context] = data;

Then it would work.

best way to insert std::map

cache.insert(std::make_pair(id,ps));

This will copy id and ps. If they have "heavy" copy constructors this will waste some time and memory.

cache.insert(std::pair<std::string,A>(id,ps));

Its indeed analog for make_pair

if(cache.find(id) == cache.end()){
cache[id] = ps;
}

Its better to use without check: cache[id] = ps;. But, at first element insertion the "default" object of type decltype(ps) will be constructed. Then, its assigned (using operator=) to ps. If its heavy, this can be problem too. But, i think its preferred way if swap method is not available.

cache.emplace(id, ps);

This can look like zero-copy emplace, but its not. emplace receives constructor parameters, so default copy constructor will be called. Also its C++11.

I think most efficient way is

cache[id].swap(ps);

This should make minimum amount of copies, but drawback is your ps will be reset (or contain old value).

Problem understanding the behaviour of std::map try_emplace for a composite key

I think you may be misunderstanding the cppreference documentation. In your code, you are always trying to add a key that is not in the map. And you are passing that key as an lvalue reference. The only difference with your two cases is that you are using a hint in the second call. So the two try_emplace versions you are using, according to cppreference, are 1 and 3.

template< class... Args > pair<iterator, bool> try_emplace( const Key& k, Args&&... args ); (1)   (since C++17)

template< class... Args > iterator try_emplace( const_iterator hint, const Key& k, Args&&... args ); (3) (since C++17)

Notice:

  • Both versions receive the key as an lvalue reference (Key&).
  • First version returns a pair<iterator, bool, but second version just returns an iterator.

Now, the text below doesn't tell how you have to build your try_emplace arguments; instead, it says what try_emplace is doing internally.

1) If a key equivalent to k already exists in the container, does nothing.
Otherwise, behaves like emplace except that the element is constructed as

value_type(std::piecewise_construct,
std::forward_as_tuple(k),
std::forward_as_tuple(std::forward<Args>(args)...))

In short, just call try_emplace passing a CompositeKey object and a string as arguments (and, optionally, an iterator as a hint). The code below calls try_emplace with an lvalue and an rvalue (the second call with a hint), so it would use the versions 1 and 4.

[Demo]

std::map<CompositeKey, std::string> my_map;

std::cout << "Case 1:\n";
auto key1{CompositeKey{10, 100, 1000}};
auto value1{std::string{"foo"}};
auto [insertedIt, success] = my_map.try_emplace(key1, value1);

std::cout << "Case 2:\n";
insertedIt = my_map.try_emplace(insertedIt, {5, 50, 500}, "bar");

// Outputs:
//
// Case 1:
// custom ctor
// copy ctor
// Case 2:
// custom ctor
// move ctor

  1. Is it correct to assume that try_emplace "move" or "copy" the CompositeKey inside the map?

Whatever you pass is forwarded to try_emplace implementation. In the example above, both CompositeKey arguments are first "custom constructed" ; then, they are passed to try_emplace, where the lvalue is copy constructed while the rvalue is move constructed.

Why is unordered_map find + insert faster than insert + check for success?

The problem is that the specification of emplace for associative containers in effect requires an allocation even in the failure case; the cost of this allocation and reallocation dominates the cost of a failed probe in the find-then-insert strategy.

This is because emplace is specified to emplace-construct value_type (i.e. pair<Key const, T>) from its forwarded arguments; only once it has constructed the pair can it hash the key to check whether it is already present. (It can't just take the first argument, because that could be std::piecewise_construct.) It also can't construct the pair in automatic storage and then move it into a node, because emplace is not specified to require a copyable or even movable value_type, so it has to perform a potentially expensive node allocation on every call. (Note that the ordered associative containers have the same problem, but there the O(log n) cost of a probe is more significant compared to the cost of allocation.)

Unless your inserts are expected to succeed in the majority of cases, you are better off using find-then-emplace over emplace-then-test. You could also use insert, as long as you make sure you're calling the value_type overload and not the template that forwards to emplace.

This is (probably) fixed in C++17, which (should) have try_emplace, with similar semantics to emplace but improved performance in the failure case. (The semantic difference is that the mapped type is not emplace-constructed in the failure case; this makes it possible to e.g. store unique_ptr as the mapped type.)

Problem compiling stl map insert

The version of insert that takes a hint returns only an iterator, not a pair.



Related Topics



Leave a reply



Submit