std::map::emplace 解析失败,但插入右值有效——为什么?

std::map::emplace fails to resolve, but insert of rvalue works -- why?

考虑以下在 map 中使用数字键放置空 vector 的尝试:

#include <map>
#include <vector>

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

emplace的调用未能解决:

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

尝试用向量的向量做同样的事情按预期工作(编辑:使用 emplace_back 时不是,请参阅 Bo Persson 的回答):

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

我对emplace背后逻辑的粗略理解是对emplaceinsert的调用应该给出相同的结果,不同之处在于emplace既不需要移动也不需要复制。对于这些类型,使用 insert 版本没有太大危害,因为向量的内容会被移动(在这种特定情况下,向量无论如何都是空的)。但总的来说,为什么 std::map::emplace 会失败?使用 GCC 8.1.

编译器无法推断出 {} 的类型,因为 emplace 是可变参数函数。强制执行类型,它将起作用:

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

map::emplace 的签名是

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

{}没有类型,无法推导。

您可能会使用,对于 emplace:

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

对于insert,(其中一个)签名是:

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

所以{42, {}}用来构造std::pair<const int, std::vector<size_t>>.

emplace 的签名不同。

对于地图

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

与矢量

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

map 的情况下,编译器无法从 {} 推导出 Args,因此失败。

对于 vector 来说要容易得多,因为 v.emplace({}); 中的第一个参数已知是 const_iterator。所以{}可以匹配一个默认构造的迭代器。