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?
CodePudding user response:
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[]
.
CodePudding user response:
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.
CodePudding user response:
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
}