简体   繁体   中英

Why does move constructor affect is_assignable?

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 aa 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?

Updated

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.

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 to pointer
b) U is not an array type
c) Either Deleter is a reference type and E is the same type as D , or Deleter is not a reference type and E is implicitly convertible to D

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM