Reputation: 5260
Just came from is_assignable and std::unique_ptr. @Angew tells me that because std::unique_ptr<int, do_nothing>
and std::unique_ptr<int>
are different types, so static_assert(not std::is_assignable<std::unique_ptr<int>, std::unique_ptr<int, do_nothing>>::value, "");
. So, I tried:
template<typename T, typename D>
struct MoveAssignOnly_V2
{
MoveAssignOnly_V2&
operator=(MoveAssignOnly_V2&)
= delete;
MoveAssignOnly_V2&
operator=(MoveAssignOnly_V2&&) noexcept
{}
};
int main()
{
static_assert(not std::is_assignable_v<MoveAssignOnly_V2<int, float>,
MoveAssignOnly_V2<int, double>>);
}
Yes, because MoveAssignOnly_V2<int, float>
and MoveAssignOnly_V2<int, double>
are two different types, so they are not assignable.
But, when I add a a move ctor:
template<class U, class E>
MoveAssignOnly_V2(MoveAssignOnly_V2<U, E>&& m) noexcept {}
static_assert
fail!(both gcc and clang).
Question here: Why does move constructor affects is_assignable?
The reason why I add this constructor is that I found std::unique_ptr
have a
template< class U, class E >
unique_ptr( unique_ptr<U, E>&& u ) noexcept;
, which confuses me a little: how can it be not assignable now that it has such a ctor? so I tried add such ctor to MoveAssignOnly_V2
and post this question. The two answers are fine, however, still cannot explain why std::unique_ptr
is not assignable when it has both move assignment and this templated constructor.
Upvotes: 3
Views: 122
Reputation: 171117
Take this code:
MoveAssignOnly_V2<int, float> lhs;
MoveAssignOnly_V2<int, double> rhs;
lhs = stdL::move(rhs);
When the converting constructor (note that it's not a move constructor) is not there, there is no way to assign rhs
into lhs
.
However, when you add the constructor template, there is now a way to convert rhs
into the type MoveAssignOnly_V2<int, float>
(which creates a temporary of that type). Then, it's possible to move-assign from that temporary into lhs
.
This is the same principle as:
double lhs = 3.14;
float rhs = 42.f;
lhs = std::move(rhs);
To address the update in the question:
You cannot go with function declarations alone, you have to read the full specification (in the standard or suitable reference). Quoting the linked reference about the converting constructor of std::unique_ptr
:
This constructor only participates in overload resolution if all of the following is true:
a)
unique_ptr<U, E>::pointer
is implicitly convertible topointer
b)U
is not an array type
c) EitherDeleter
is a reference type andE
is the same type asD
, orDeleter
is not a reference type andE
is implicitly convertible toD
So as you can see, the unique_ptr
's converting constructor must be implemented so that it's only active if the source deleter can be converted to the target one. That's basically the same rule as for move assignment in the previous question.
Upvotes: 4
Reputation: 16404
What you added here:
template<class U, class E>
MoveAssignOnly_V2(MoveAssignOnly_V2<U, E>&& m) noexcept {}
is not only a move constructor, but a templated constructor that can construct MoveAssignOnly_V2<T, D>
from any MoveAssignOnly_V2<U, E>
.
Thus constructing MoveAssignOnly_V2<int, float>
from MoveAssignOnly_V2<int, double>>
is fine.
Upvotes: 2