Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the preferred/idiomatic way to insert into a map?

People also ask

What is an insert on a map?

An inset map is a smaller map featured on the same page as the main map. Traditionally, inset maps are shown at a larger scale (smaller area) than the main map. Often, an inset map is used as a locator map that shows the area of the main map in a broader, more familiar geographical frame of reference.

How do I add something to a map in C++?

map insert() in C++ STL. 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.

How do you access elements on a map?

C++ map at() function is used to access the elements in the map with the given key value. It throws an exception out_of _range, if the accessed key is not present in the map.

How do I search a map in C++?

C++ map find() function is used to find an element with the given key value k. If it finds the element then it returns an iterator pointing to the element. Otherwise, it returns an iterator pointing to the end of the map, i.e., map::end().


As of C++11, you have two major additional options. First, you can use insert() with list initialization syntax:

function.insert({0, 42});

This is functionally equivalent to

function.insert(std::map<int, int>::value_type(0, 42));

but much more concise and readable. As other answers have noted, this has several advantages over the other forms:

  • The operator[] approach requires the mapped type to be assignable, which isn't always the case.
  • The operator[] approach can overwrite existing elements, and gives you no way to tell whether this has happened.
  • The other forms of insert that you list involve an implicit type conversion, which may slow your code down.

The major drawback is that this form used to require the key and value to be copyable, so it wouldn't work with e.g. a map with unique_ptr values. That has been fixed in the standard, but the fix may not have reached your standard library implementation yet.

Second, you can use the emplace() method:

function.emplace(0, 42);

This is more concise than any of the forms of insert(), works fine with move-only types like unique_ptr, and theoretically may be slightly more efficient (although a decent compiler should optimize away the difference). The only major drawback is that it may surprise your readers a little, since emplace methods aren't usually used that way.


First of all, operator[] and insert member functions are not functionally equivalent :

  • The operator[] will search for the key, insert a default constructed value if not found, and return a reference to which you assign a value. Obviously, this can be inefficient if the mapped_type can benefit from being directly initialized instead of default constructed and assigned. This method also makes it impossible to determine if an insertion has indeed taken place or if you have only overwritten the value for an previously inserted key
  • The insert member function will have no effect if the key is already present in the map and, although it is often forgotten, returns an std::pair<iterator, bool> which can be of interest (most notably to determine if insertion has actually been done).

From all the listed possibilities to call insert, all three are almost equivalent. As a reminder, let's have look at insert signature in the standard :

typedef pair<const Key, T> value_type;

  /* ... */

pair<iterator, bool> insert(const value_type& x);

So how are the three calls different ?

  • std::make_pair relies on template argument deduction and could (and in this case will) produce something of a different type than the actual value_type of the map, which will require an additional call to std::pair template constructor in order to convert to value_type (ie : adding const to first_type)
  • std::pair<int, int> will also require an additional call to the template constructor of std::pair in order to convert the parameter to value_type (ie : adding const to first_type)
  • std::map<int, int>::value_type leaves absolutely no place for doubt as it is directly the parameter type expected by the insert member function.

In the end, I would avoid using operator[] when the objective is to insert, unless there is no additional cost in default-constructing and assigning the mapped_type, and that I don't care about determining if a new key has effectively inserted. When using insert, constructing a value_type is probably the way to go.


The first version:

function[0] = 42; // version 1

may or may not insert the value 42 into the map. If the key 0 exists, then it will assign 42 to that key, overwriting whatever value that key had. Otherwise it inserts the key/value pair.

The insert functions:

function.insert(std::map<int, int>::value_type(0, 42));  // version 2
function.insert(std::pair<int, int>(0, 42));             // version 3
function.insert(std::make_pair(0, 42));                  // version 4

on the other hand, don't do anything if the key 0 already exists in the map. If the key doesn't exist, it inserts the key/value pair.

The three insert functions are almost identical. std::map<int, int>::value_type is the typedef for std::pair<const int, int>, and std::make_pair() obviously produces a std::pair<> via template deduction magic. The end result, however, should be the same for versions 2, 3, and 4.

Which one would I use? I personally prefer version 1; it's concise and "natural". Of course, if its overwriting behavior is not desired, then I would prefer version 4, since it requires less typing than versions 2 and 3. I don't know if there is a single de facto way of inserting key/value pairs into a std::map.

Another way to insert values into a map via one of its constructors:

std::map<int, int> quadratic_func;

quadratic_func[0] = 0;
quadratic_func[1] = 1;
quadratic_func[2] = 4;
quadratic_func[3] = 9;

std::map<int, int> my_func(quadratic_func.begin(), quadratic_func.end());

Since C++17 std::map offers two new insertion methods: insert_or_assign() and try_emplace(), as also mentioned in the comment by sp2danny.

insert_or_assign()

Basically, insert_or_assign() is an "improved" version of operator[]. In contrast to operator[], insert_or_assign() doesn't require the map's value type to be default constructible. For example, the following code doesn't compile, because MyClass does not have a default constructor:

class MyClass {
public:
    MyClass(int i) : m_i(i) {};
    int m_i;
};

int main() {
    std::map<int, MyClass> myMap;

    // VS2017: "C2512: 'MyClass::MyClass' : no appropriate default constructor available"
    // Coliru: "error: no matching function for call to 'MyClass::MyClass()"
    myMap[0] = MyClass(1);

    return 0;
}

However, if you replace myMap[0] = MyClass(1); by the following line, then the code compiles and the insertion takes place as intended:

myMap.insert_or_assign(0, MyClass(1));

Moreover, similar to insert(), insert_or_assign() returns a pair<iterator, bool>. The Boolean value is true if an insertion occurred and false if an assignment was done. The iterator points to the element that was inserted or updated.

try_emplace()

Similar to the above, try_emplace() is an "improvement" of emplace(). In contrast to emplace(), try_emplace() doesn't modify its arguments if insertion fails due to a key already existing in the map. For example, the following code attempts to emplace an element with a key that is already stored in the map (see *):

int main() {
    std::map<int, std::unique_ptr<MyClass>> myMap2;
    myMap2.emplace(0, std::make_unique<MyClass>(1));

    auto pMyObj = std::make_unique<MyClass>(2);    
    auto [it, b] = myMap2.emplace(0, std::move(pMyObj));  // *

    if (!b)
        std::cout << "pMyObj was not inserted" << std::endl;

    if (pMyObj == nullptr)
        std::cout << "pMyObj was modified anyway" << std::endl;
    else
        std::cout << "pMyObj.m_i = " << pMyObj->m_i <<  std::endl;

    return 0;
}

Output (at least for VS2017 and Coliru):

pMyObj was not inserted
pMyObj was modified anyway

As you can see, pMyObj no longer points to the original object. However, if you replace auto [it, b] = myMap2.emplace(0, std::move(pMyObj)); by the the following code, then the output looks different, because pMyObj remains unchanged:

auto [it, b] = myMap2.try_emplace(0, std::move(pMyObj));

Output:

pMyObj was not inserted
pMyObj pMyObj.m_i = 2

Code on Coliru

Please note: I tried to keep my explanations as short and simple as possible to fit them into this answer. For a more precise and comprehensive description, I recommend reading this article on Fluent C++.


If you want to overwrite the element with key 0

function[0] = 42;

Otherwise:

function.insert(std::make_pair(0, 42));

I have been running some time comparisons between the abovementioned versions:

function[0] = 42;
function.insert(std::map<int, int>::value_type(0, 42));
function.insert(std::pair<int, int>(0, 42));
function.insert(std::make_pair(0, 42));

Turns out that time differences between the insert versions are tiny.

#include <map>
#include <vector>
#include <boost/date_time/posix_time/posix_time.hpp>
using namespace boost::posix_time;
class Widget {
public:
    Widget() {
        m_vec.resize(100);
        for(unsigned long it = 0; it < 100;it++) {
            m_vec[it] = 1.0;
        }
    }
    Widget(double el)   {
        m_vec.resize(100);
        for(unsigned long it = 0; it < 100;it++) {
            m_vec[it] = el;
        }
    }
private:
    std::vector<double> m_vec;
};


int main(int argc, char* argv[]) {



    std::map<int,Widget> map_W;
    ptime t1 = boost::posix_time::microsec_clock::local_time();    
    for(int it = 0; it < 10000;it++) {
        map_W.insert(std::pair<int,Widget>(it,Widget(2.0)));
    }
    ptime t2 = boost::posix_time::microsec_clock::local_time();
    time_duration diff = t2 - t1;
    std::cout << diff.total_milliseconds() << std::endl;

    std::map<int,Widget> map_W_2;
    ptime t1_2 = boost::posix_time::microsec_clock::local_time();    
    for(int it = 0; it < 10000;it++) {
        map_W_2.insert(std::make_pair(it,Widget(2.0)));
    }
    ptime t2_2 = boost::posix_time::microsec_clock::local_time();
    time_duration diff_2 = t2_2 - t1_2;
    std::cout << diff_2.total_milliseconds() << std::endl;

    std::map<int,Widget> map_W_3;
    ptime t1_3 = boost::posix_time::microsec_clock::local_time();    
    for(int it = 0; it < 10000;it++) {
        map_W_3[it] = Widget(2.0);
    }
    ptime t2_3 = boost::posix_time::microsec_clock::local_time();
    time_duration diff_3 = t2_3 - t1_3;
    std::cout << diff_3.total_milliseconds() << std::endl;

    std::map<int,Widget> map_W_0;
    ptime t1_0 = boost::posix_time::microsec_clock::local_time();    
    for(int it = 0; it < 10000;it++) {
        map_W_0.insert(std::map<int,Widget>::value_type(it,Widget(2.0)));
    }
    ptime t2_0 = boost::posix_time::microsec_clock::local_time();
    time_duration diff_0 = t2_0 - t1_0;
    std::cout << diff_0.total_milliseconds() << std::endl;

    system("pause");
}

This gives respectively for the versions (I ran the file 3 times, hence the 3 consecutive time differences for each):

map_W.insert(std::pair<int,Widget>(it,Widget(2.0)));

2198 ms, 2078 ms, 2072 ms

map_W_2.insert(std::make_pair(it,Widget(2.0)));

2290 ms, 2037 ms, 2046 ms

 map_W_3[it] = Widget(2.0);

2592 ms, 2278 ms, 2296 ms

 map_W_0.insert(std::map<int,Widget>::value_type(it,Widget(2.0)));

2234 ms, 2031 ms, 2027 ms

Hence, results between different insert versions can be neglected (didn't perform a hypothesis test though)!

The map_W_3[it] = Widget(2.0); version takes about 10-15 % more time for this example due to an initialization with the default constructor for Widget.