emplace unordered_set in unordered_map

4.5k views Asked by At

How can I add a (statically defined) unordered_set to an unordered_map, without having to copy the unordered_set?

I tried this:

std::unordered_map<int, std::unordered_set<std::string>> my_map;
for (int i=0; i<100; i++)
  my_map.emplace(i, {"foo", "bar"});

and this:

std::unordered_map<int, std::unordered_set<std::string>> my_map;
for (int i=0; i<100; i++)
  my_map.insert(i, std::move(std::unordered_set<std::string>({"foo", "bar"})));

but none of them compiles, I get these errors (respectively):

error: no matching function for call to ‘std::unordered_map<int, std::unordered_set<std::basic_string<char> > >::emplace(int&, <brace-enclosed initializer list>)’

and

error: no matching function for call to ‘std::unordered_map<int, std::unordered_set<std::basic_string<char> > >::insert(int&, std::remove_reference<std::unordered_set<std::basic_string<char> > >::type)’
4

There are 4 answers

0
TartanLlama On BEST ANSWER

Braced initializers are one of the edge cases that perfect forwarding is not so perfect about.

The issue is that braced initializers passed to function template parameters are in a non-deduced context and compilers are not allowed to deduce a type for them.

Luckily, the fix is pretty easy: just be explicit about the use of std::initializer_list.

my_map.emplace(i, std::initializer_list<std::string>{"foo", "bar"});

The usual way to solve this issue is by doing something like:

auto list = { "foo", "bar" };
my_map.emplace(i, list);

But this doesn't work for std::strings because decltype(list) is deduced as std::initializer_list<const char*>.

0
danielschemmel On

The elements of maps (both map and unordered_map) are of type using value type = std::pair<key_t, mapped_type>. Therefore, emplace does not pass its arguments to the unordered_set<string> constructor!

Once you realize that, the solution is easy:

std::unordered_map<int, std::unordered_set<std::string>> my_map;
for (int i=0; i<100; i++)
    my_map.emplace(i, std::unordered_set<std::string>{"foo", "bar"});
3
emvee On

In order to insert something in a std::map<Key, Value>, you need to insert a std::pair<Key, Value>

Change:

my_map.insert(i, std::move(std::unordered_set<std::string>({"foo", "bar"})));

into:

my_map.insert( std::make_pair(i, std::unordered_set<std::string>({"foo", "bar"})));

and you should be good to go.

0
davidhigh On

You could use the following code:

for (int i=0; i<100; i++)
  my_map.emplace(i, std::unordered_set<std::string>({"foo","bar"}));

It will move the unordered set into the unordered map.