Harry
Harry

Reputation: 3074

std::unordered_map gives error when inserting using emplace function

I'm getting this weird error when inserting an element into std::unordered_map using emplace function but not if I use operator[] function overload. This is my code:

#include <iostream>
#include <unordered_map>
#include <memory>

class B {
public:
    B() = default;
    ~B() = default; 
};

class A {
public:
    A() = default;
    ~A() = default;
    
    std::unique_ptr<B> b_ptr;
};

int main() {
    std::unordered_map<std::string, A> mp;
    // mp.emplace("abc", A()); // gives compiler here
    auto& a = mp["def"];
}

I'm getting huge error print when compiled. This is a short note of error: template argument deduction/substitution failed

Upvotes: 6

Views: 1297

Answers (2)

mgkrupa
mgkrupa

Reputation: 71

If you are using C++17 or later, replacing

// mp.emplace("abc", A());

with

mp.try_emplace("abc");

compiles fine.

The reason why mp.emplace("abc", A()); produces a compile error is that the sub-expression A() constructs a new object of type A in main()'s scope and so emplace() expects to directly forward this new object as the argument into some A constructor, but there is no such constructor (said differently, somewhere inside the emplace() method's definition, there is something like the code A(A()), which is undefined).

As mentioned elsewhere, adding a move constructor to class A with the code A(A&&) = default; will also fix the compilation error (although this fix might not be an option with other, more complex classes). The reason why this removes the error is because there now exists a constructor that emplace() can forward this A() object into (as an argument). That is, the code A(A()) becomes well-defined.

Upvotes: 7

NathanOliver
NathanOliver

Reputation: 181027

When you use emplace like mp.emplace("abc", A()); what you are doing is creating a temporary A, and that object is then copied/moved into the object that emplace is going to construct. When you did ~A() = default; in the class, that gets rid of the compiler supplied default move constructor, and the copy constructor is implicitly deleted because std::unique_ptr can't be copied so the A() can't be moved or copied into the object emplace is going to create.

You can fix this by using the std::piecewise_construct taged version of emplace to forward the parts of the key value pair to emplace like

mp.emplace(std::piecewise_construct,    // call the piecewise_construct overload
          std::forward_as_tuple("abc"), // forwards "abc" to the key
          std::forward_as_tuple());     // forwards nothing to the value so it can be default constructed
          

or you could just add a move constructor to A using

A(A&&) = default;

so that emplace can move the A() you created in main into mp.

Upvotes: 6

Related Questions