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 delete
d(!) 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 = default
ed?
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