6

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

5
  • whats yous compiler vendor/version? Clang and GCC are ok with this code Commented Sep 29, 2021 at 14:21
  • @EduardRostomyan GCC 9.2 Commented Sep 29, 2021 at 14:22
  • 1
    Simplified problem: std::pair<const std::string, A> p { "abc", A{} };. Adding move constructor into A can resolve it: A(A&&) = default;. Live demo: godbolt.org/z/5rEYcdro6. Commented Sep 29, 2021 at 14:24
  • @NathanOliver doesn't emplace create class A object in place when inserting? Commented Sep 29, 2021 at 14:27
  • 1
    @Harry No, it creates map's value type in place, which is std::pair<const std::string,A> in your case. Commented Sep 29, 2021 at 14:28

2 Answers 2

7

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.

Sign up to request clarification or add additional context in comments.

Comments

6

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.

5 Comments

What you say is a work around but the issue is that A is not movable... one just needs to add move ctor/assignment to A, or rather to declare them defaulted.
@ALX23z IIRC, mapped type doesn't need to be copyable/movable with unordered maps. Or, does it?
@DanielLangr unordered_map can work with such types. But it doesn't look like OP was looking for the non-movable and non-copyable case.
@ALX23z While adding a move constructor will get the original code to work, and I've added that in, doing so might incur a performance penalty. It has to do a default construction and then a move, where with the piecewise_construct we forward all the parameters into emplace and only have a single default constructor call. In this case it probably wont matter, but I could see where it might if the class had a lot of members that need to be moved.
@NathanOliver in most cases compiler should be able to optimize out the unnecessary construction calls. And frankly writing the extra-long construction is not good for the code clarity and it's not worth ruining it even if there is some additional calls that compiler failed to optimize. That's being said, I agree that knowledge of such construction is important but it should be accompanied with clarification when it's worthwhile and important to use. Not just "here, use it".

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.