5

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)’
Valentin Lorentz
  • 9,556
  • 6
  • 47
  • 69
  • This looks more like you want an `unordered_map` of `unordered_set`s (which is not what you say in the question). Please clarify. – Walter Jun 24 '15 at 07:53

4 Answers4

9

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*>.

TartanLlama
  • 63,752
  • 13
  • 157
  • 193
2

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"});
danielschemmel
  • 10,885
  • 1
  • 36
  • 58
2

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.

davidhigh
  • 14,652
  • 2
  • 44
  • 75
1

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.

emvee
  • 4,371
  • 23
  • 23