I'm busy testing an implementation of various generic algorithms and I'm using types with minimal support of provided functions. I came across this weird setup when using a std::pair<T, movable> with some type T (e.g., int) and a movable type defined like this:
struct movable
{
movable() {}
movable(movable&&) = default;
// movable(movable const&) = delete;
movable(movable&) = delete;
};
The idea is have a type which is movable but not copyable. That works great, e.g., with expressions like this:
movable m1 = movable();
movable m2 = std::move(m1);
However, when trying to use this type as a member of std::pair<...> it fails! To make get the code to compile it is necessary to add the deleted(!) copy constructor taking a movable const& (or have only that version). The copy constructor taking a non-const reference is insufficient:
#include <utility>
auto f() -> std::pair<int, movable> {
return std::pair<int, movable>(int(), movable());
}
What is going on here? Is std::pair<...> overspecified by mandating that std::pair(std::pair const&) is = defaulted?
The problem seems to be down to the specification of std::pair's copy constructor (in 20.3.2 [pairs.pair] synopsis):
namespace std { template <class T1, class T2> struct pair { ... pair(const pair&) = default; ... }; }
A quick check with my implementation implies that the obvious implementation copying the two members does not require the const& version of the movable copy constructor. That is, the offensive part is the = default on pair's copy constructor!
std::pair copy constructor is declared as follows:
pair(const pair&) = default;
By declaring this copy constructor for movable:
movable(movable&) = delete;
you inhibit implicit creation of movable(const movable&) (so it's not even deleted, there's just no such constructor), thus this is the only copy constructor you have. But std::pair copy constructor requires a copy constructor of its members to take const reference, so you get compile error.
If you add this:
movable(movable const&) = delete;
or (better) just remove movable(movable&) = delete; declaration, you now have the movable(movable const&) constructor, and because it's deleted, the std::pair copy constructor also becomes deleted.
Update: Let's consider a simpler example demonstrating the same issue. This doesn't compile:
template <typename T>
struct holder {
T t;
// will compile if you comment the next line
holder(holder const&) = default;
// adding or removing move constructor changes nothing WRT compile errors
// holder(holder&&) = default;
};
struct movable {
movable() {}
movable(movable&&) = default;
// will also compile if you uncomment the next line
//movable(movable const&) = delete;
movable(movable&) = delete;
};
holder<movable> h{movable()};
It will compile if you comment the copy constructor of holder, because this is how implicit copy constructor generation works ([class.copy]/8:
The implicitly-declared copy constructor for a class X will have the form
X::X(const X&)if each potentially constructed subobject of a class type
M(or array thereof) has a copy constructor whose first parameter is of typeconst M&orconst volatile M&. Otherwise, the implicitly-declared copy constructor will have the form
X::X(X&)
That is, when you comment out the declaration holder(holder const&) = default; the implicitly declared copy constructor of holder will have the form holder(holder&). But if you don't, T's copy constructor has take const T& (or const volatile T&) because this is what will be called in memberwise copy procedure described in [class.copy]/15.
And if holder has a move constructor, it's even easier - if you comment out holder(holder const&) = default;, the implicitly declared copy constructor of holder will be just deleted.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With