简体   繁体   中英

How to use emplace/insert_or_assign in nested map with c++17?

If I have

map<int, map<int, int>> m;

And when I need to insert, I could use

m[1] = { {1, 1} };

But now I want to use emplace/insert_or_assign to replace it, I may use

m.emplace(1, map<int, int>{ {1, 1} });

But I think it is a bit complex to write, expecially I should write map<int, int> again, even more if the original map change into such as map<int, map<int, map<int, map<int, int>>>> , then I should repeat map<int, int> as much as it need. So is there any more simple and readable method?

The question should be why do you want to use emplace / insert_or_assign at the first point.


The point of emplace is so that you don't do unnecessary creation until it is needed. With a map<int, map<int, int>> , you might want to create the inner map only if you are going to insert the new map. To properly utilize such behavior, you should only supply the arguments that construct the map, not the map itself.

However, the only constructor map has that can also fill the container during construction is the one taking an initializer_list . So ideally you would want to write something like:

m.emplace(1, {{1, 1}}) // this won't work

However, this wouldn't work because there's no way for emplace to deduce {{1, 1}} as a initializer_list . Which means you have to specify that manually like:

m.emplace(1, std::initializer_list<std::pair<const int, int>>{{1, 1}})

That's quite some writing, which will only be worse if you have more nested layers. At this point it's probably better to just write:

if (m.find(1) == m.end()) {
// or `!m.contains(1)` for C++20 or later
    m[1] = {{1, 1}};
}

On the other hand, if you didn't really care about the unnecessary construction, you should just use insert instead:

m.insert({1, {{1, 1}}})

The point of insert_or_assign is to make map works on types that is not default constructible.

When you just call m[1] , this will default construct an object if m[1] does not exist. However, that also means operator[] does not work if the type is not default constructible.

However, since a map is default constructible, there isn't much difference between using operator[] directly and using insert_or_assign . So instead, just go use operator[] .

You could do

typedef map<int, int> mp;

and then do

m.emplace(1, mp{ {1, 1} });

That's less verbose and also clearly indicates what is map constructor and what isn't.

Hope this helps.

Edit : You can also try doing

template <typename T>
using nest_mp = std::map<int, T>;

typedef std::map<int, int> mp;

and do

m.emplace(1, nest_mp<nest_mp<mp>>>{ {1, { {1, { {1, 1} } } } } };

There are two { braces for each map construction - one to invoke the std::map initializer list constructor, the other to invoke the std::pair initializer list constructor.

It'd be better not to use map<int, map<int, int>> at all. Instead, use map<std::pair<int, int>, int . This means you have a composite key, so instead of storing a value at [A][B] you store it at [pair(A, B)] . This way when you store a value it's always a single memory allocation for its node (because there's just one map).

Then you write code like this:

m.emplace(std::make_pair(1, 1), 1);
m.insert_or_assign({1, 1}, 1);

And if you want to iterate the values with a specific "first" integer in the key:

int subkey = 1;
for (auto it = m.lower_bound({subkey, 0}),
    end = m.upper_bound({subkey, INT_MAX});
    it != end; ++it)
{
    // do something with elements whose key.first == subkey
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM