Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does std::map emplace need a copy constructor on gcc?

Tags:

c++

gcc

c++11

The following code compiles well on Visual Studio's Visual C++ 19.0 compiler, but gcc 5.4.0 complains about the copy constructor being private.

#include <map>

class CMyClass
{
public:
  CMyClass(int) {};
private:
  CMyClass(const CMyClass&); // I want to avoid copy construction
};

int main()
{
  std::map<int, CMyClass> mymap;
  mymap.emplace(0, 0);

  return 0;
}

Error message:

‘CMyClass::CMyClass(const CMyClass&)’ is private

Isn't avoiding copies exactly what emplace is for? Am I missing something?

like image 747
Bert Avatar asked Jun 22 '17 12:06

Bert


People also ask

Does map have a copy constructor?

Copy constructor is used to copy a whole existing map and the map to be copied is passed as a single param to the constructor.

What does map emplace do in C++?

The map::emplace() is a built-in function in C++ STL which inserts the key and its element in the map container. It effectively increases the container size by one.

Does std :: map insert make copy?

Yes -- when you insert an item into an std::map, you pass it by value, so what it contains is a copy of what you passed.


2 Answers

This is not valid in C++11 as published.

The C++11 standard depicts two pair constructors with two parameters:

pair(const T1& x, const T2& y);
template<class U, class V> pair(U&& x, V&& y);

If the first gets chosen, you are doomed for obvious reasons.

The second overload does not participate in overload resolution if, as relevant here, "V is not implicitly convertible to second_type". Here, V is int, second_type is CMyClass. Is int implicitly convertible to CMyClass? No, because the declaration CMyClass x = some_int; is not well-formed, as in C++11 that declaration notionally constructs a CMyClass temporary from some_int and then moves it into x, but CMyClass cannot be moved.

The extra overloads GCC added - as is allowed - have similar constraints.


C++17 substantially revamped the constraints on pair's constructors, which has the side effect of allowing your code to work. Implementers treated this as a retroactive defect fix, which is why GCC 6 accepts your code.

like image 143
T.C. Avatar answered Oct 10 '22 00:10

T.C.


GCC 5.4.0 is only checking if the types are convertible when enabling the overload in question:

  // DR 811.
  template<class _U1, class = typename
       enable_if<is_convertible<_U1, _T1>::value>::type>
constexpr pair(_U1&& __x, const _T2& __y)
: first(std::forward<_U1>(__x)), second(__y) { }

Whereas more recent versions of GCC checks if it is move constructible:

288       template<typename _U1, typename
289            enable_if<_PCCP::template
290                _MoveCopyPair<true, _U1, _T2>(),
291                          bool>::type=true>
292        constexpr pair(_U1&& __x, const _T2& __y)
293        : first(std::forward<_U1>(__x)), second(__y) { }

In particular _PCCP is an alias for _PCC<true, _T1, _T2>. _PCC is a traits class that contains this function:

138       template <bool __implicit, typename _U1, typename _U2>
139       static constexpr bool _MoveCopyPair()
140       {
141     using __do_converts = __and_<is_convertible<_U1&&, _T1>,
142                   is_convertible<const _U2&, _T2>>;
143     using __converts = typename conditional<__implicit,
144                        __do_converts,
145                        __not_<__do_converts>>::type;
146     return __and_<is_constructible<_T1, _U1&&>,
147               is_constructible<_T2, const _U2&&>,
148               __converts
149               >::value;
150       }
like image 2
user8199961 Avatar answered Oct 09 '22 22:10

user8199961