2

Consider the following attempt to emplace an empty vector with a numeric key in a map:

#include <map>
#include <vector>

int main () {
    std::map<size_t, std::vector<size_t>> m;
    m.emplace(42, {});                          // FAILS
    m.insert({42, {}});                         // WORKS
}

The call to emplace fails to resolve:

error: no matching function for call to ‘std::map<long unsigned int, std::vector<long unsigned int> >::emplace(int, <brace-enclosed initializer list>)’
     m.emplace(42, {});
                     ^
In file included from /usr/include/c++/8/map:61,
                 from map_emplace.cpp:1:
/usr/include/c++/8/bits/stl_map.h:574:2: note: candidate: ‘std::pair<typename std::_Rb_tree<_Key, std::pair<const _Key, _Tp>, std::_Select1st<std::pair<const _Key, _Tp> >, _Compare, typename __gnu_cxx::__alloc_traits<_Alloc>::rebind<std::pair<const _Key, _Tp> >::other>::iterator, bool> std::map<_Key, _Tp, _Compare, _Alloc>::emplace(_Args&& ...) [with _Args = {}; _Key = long unsigned int; _Tp = std::vector<long unsigned int>; _Compare = std::less<long unsigned int>; _Alloc = std::allocator<std::pair<const long unsigned int, std::vector<long unsigned int> > >; typename std::_Rb_tree<_Key, std::pair<const _Key, _Tp>, std::_Select1st<std::pair<const _Key, _Tp> >, _Compare, typename __gnu_cxx::__alloc_traits<_Alloc>::rebind<std::pair<const _Key, _Tp> >::other>::iterator = std::_Rb_tree_iterator<std::pair<const long unsigned int, std::vector<long unsigned int> > >]’
  emplace(_Args&&... __args)
  ^~~~~~~
/usr/include/c++/8/bits/stl_map.h:574:2: note:   candidate expects 0 arguments, 2 provided

Attempting to do the same thing with a vector of vectors works as expected (EDIT: not when using emplace_back, cf. Bo Persson's answer):

    std::vector<std::vector<size_t>> v;
    v.emplace({});           // WORKS -- but does the wrong thing!
    v.emplace_back({});      // FAILS
    v.push_back({{}});       // WORKS

My rough understanding of the logic behind emplace is that the calls to emplace and insert should give the same result, the difference being that emplace requires neither move nor copy. For these types, there is not much harm in using the insert version, since the contents of the vector would be moved (and in this specific case, the vector is empty, anyway). In general though, why does this fail for std::map::emplace? Using GCC 8.1.

Jonas Greitemann
  • 1,011
  • 10
  • 25

3 Answers3

3

signature of map::emplace is

template< class... Args >
std::pair<iterator,bool> emplace( Args&&... args );

and {} has no type and cannot be deduced.

You might use, for emplace:

m.emplace(42, std::vector<std::size_t>{});

For insert, (one of its) signature is:

std::pair<iterator,bool> insert( const value_type& value );

so {42, {}} is used to construct std::pair<const int, std::vector<size_t>>.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • I take this is somewhat of a shortcoming on part of C++? Because it is *possible* to infer the type of the brace expression and it works fine in `insert`. `emplace` *knows* the type it is trying to construct, so does the outer set of braces in the argument of `insert`. – Jonas Greitemann May 16 '18 at 11:31
  • @Jonas There's really no way to know, in the general case. The problem here is that [any of the constructors of `std::pair`](http://en.cppreference.com/w/cpp/utility/pair/pair) is a candidate here and there's quite some nasty stuff in there. In particular, some of the signatures need to accept arbitrary types, with the conversion to the final pair type only happening *inside* the constructor. Since `emplace` is supposed to work with everything that would work with one of the constructors, you are out of luck. – ComicSansMS May 16 '18 at 11:41
2

The signatures for emplace are different.

For map

template< class... Args >
std::pair<iterator,bool> emplace( Args&&... args );

vs vector

template< class... Args >
iterator emplace( const_iterator pos, Args&&... args );

In the map case the compiler cannot deduce Args from {}, so it fails.

For the vector it is much easier, as the first parameter in v.emplace({});is known to be a const_iterator. So {} can match a default constructed iterator.

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
  • Good catch. I should have used `emplace_back` for the vector to make it a fair comparison. And sure enough, it fails with a similar error. – Jonas Greitemann May 16 '18 at 11:26
1

Compiler is not able to deduce a type for {} because emplace is a vararg function. Enforce the type and it will work:

m.emplace(42, (std::vector<size_t>){}); 
Jean-Baptiste Yunès
  • 34,548
  • 4
  • 48
  • 69