Search code examples
c++c++11moveunordered-mapemplace

emplace unordered_set in unordered_map


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)’

Solution

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