Search code examples
c++c++14copy-constructorstdmap

Cannot use std::map::emplace to insert an object of a class with no copy constructor


I'm trying to compile the following code:

#include <map>
#include <condition_variable>

class MyClass
{
public:
    MyClass(): m_cv() {}
    std::condition_variable m_cv; //just to illustrate that a copy constructor is not obvious to write
};

int main()
{
    std::map<std::string,MyClass> testmap;
    testmap.emplace("key",MyClass());
}

Compilation fails at the last line (emplace method call) with a very long error that starts as follows:

error: no matching function for call to ‘std::pair, MyClass>::pair(const char [4], MyClass)

By trial and error, I have come up with the understanding that the error comes from the absence of copy constructor for MyClass. If I add one, or else if I replace the condition variable attribute with a basic type (int for instance), the error disappears. However I'm not sure of my analysis, and most importantly, I do not understand why a copy constructor for MyClass is needed here.

So my question is twofold:

  1. Why is the copy constructor required?
  2. How can I rewrite my code in order not to need the copy constructor (which is kind of tricky to write due to the complexity of the class, omitted here for brevity)?

Solution

  • In C++17:

    testmap.try_emplace("key");
    

    In C++14 or older:

    testmap.emplace(std::piecewise_construct, std::forward_as_tuple("key"), std::tuple<>{});
    

    You code constructs MyClass twice: one time you do it manually (MyClass()), and the second time std::map does it internally (in your case it tries to call the copy constructor).

    The second constructor call isn't going anywhere, but you can change what arguments it receives, if any. So you have to get rid of the first call, and change the second call to receive no arguments, as opposed to const MyClass &.

    The arguments of .emplace() go straight to the constructor of std::pair. If you examine the constructors, the only one that can default-construct the second element (not counting the pair's own default constructor) seems to be the std::piecewise_construct one.

    And .try_emplace() just uses std::piecewise_construct internally.