#include <iostream>
#include <unordered_map>
#include <utility>
using namespace std;
struct Foo {
Foo(const int value) : val(value) {
cout << "Foo(int), val: " << val << '\n';
}
Foo(Foo & foo) {
val = foo.val;
cout << "Foo(Foo &)" << '\n';
}
Foo(const Foo & foo) {
val = foo.val;
cout << "Foo(const Foo &)" << '\n';
}
Foo(Foo && foo) {
val = foo.val;
cout << "Foo(Foo &&)" << '\n';
}
~Foo() { cout << "~Foo(), val: " << val << '\n'; }
Foo& operator=(const Foo& rhs)
{
cout << "Foo& operator=(const Foo& rhs), rhs.val: " << rhs.val;
val = rhs.val;
return *this;
}
bool operator==(const Foo& rhs) const { return val == rhs.val; }
bool operator<(const Foo& rhs) const { return val < rhs.val; }
int val;
};
template<> struct std::hash<Foo> {
size_t operator()(const Foo& f) const { return hash<int>{}(f.val); }
};
int main()
{
std::unordered_map<Foo, int> mp;
mp.insert(std::pair<Foo, int>{1, 50});
std::cout << '\n';
mp.insert(std::pair<const Foo, int>{2, 60});
std::cout << '\n';
std::cout << "exiting main()\n";
}
Foo(int), val: 1
Foo(Foo &&)
~Foo(), val: 1
Foo(int), val: 2
Foo(const Foo &)
~Foo(), val: 2
exiting main()
~Foo(), val: 1
~Foo(), val: 2
Why does mp.insert(std::pair<Foo, int>{1, 50})
compile? Does an implicit conversion occur?
Let's break it down as I see it.
Foo(int)
with val 1 is called when temporary std::pair
is created, val 1 is provided to Foo ctor.
Foo(int), val: 1 is printed.std::pair<iterator, bool> insert(value_type && value)
is called with temporary pair provided as argument.
How? The std::unordered_map<Foo, int>::value_type
is std::pair<const Foo, int>
. Does an implicit conversion from std::unordered_map<Foo, int>
to std::unordered_map<const Foo, int>
occur here? If so, should the copy ctor be invoked here?std::pair{std::move(value)};
ctor.
Foo(Foo &&)
is printed.Why Foo's move ctor is called on the first insert and Foo's copy ctor is called on the second insert?
An implicit conversion is performed, but not in the way you expect it. A different overload for the insert()
method is used that itself performs the conversion. To be more precise:
template<class P> iterator insert(P && value);
is called, since the pair is not of the type const value_type &
and not value_type &&
, or of node_type &&
, which are the other possible single-argument overloads here. See the C++ reference for unordered_map::insert()
for all overloads.The problem is that a move of std::pair<const Foo, int>
is only possible if Foo
defines a const rvalue move constructor, i.e. Foo(const Foo && other)
. Since that specific constructor does not exist, the compiler must resort back to the copy constructor of the pair, that's why that one is called.
(Not to say you should define const rvalue move constructors. Unless you absolutely need them in very specific circumstances, I'd avoid them because it makes reasoning about your class more difficult by adding additional complexity.)
You might also want to take a look at unordered_map::emplace
if you want to avoid needless constructor calls.