What is the preferred/idiomatic way to insert into a map?
I have identified four different ways of inserting elements into a std::map
:
std::map<int, int> function;
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));
Which of those is the preferred/idiomatic way? (And is there another way I have not thought of?)
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 themapped_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 anstd::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 actualvalue_type
of the map, which will require an additional call tostd::pair
template constructor in order to convert tovalue_type
(ie : addingconst
tofirst_type
) -
std::pair<int, int>
will also require an additional call to the template constructor ofstd::pair
in order to convert the parameter tovalue_type
(ie : addingconst
tofirst_type
) -
std::map<int, int>::value_type
leaves absolutely no place for doubt as it is directly the parameter type expected by theinsert
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());