为什么移动构造函数会影响is_assignable?

Why does move constructor affect is_assignable?

刚从过来。 @Angew 告诉我,因为 std::unique_ptr<int, do_nothing>std::unique_ptr<int> 是不同的类型,所以 static_assert(not std::is_assignable<std::unique_ptr<int>, std::unique_ptr<int, do_nothing>>::value, "");。所以,我尝试了:

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>>);
}

是的,因为MoveAssignOnly_V2<int, float>MoveAssignOnly_V2<int, double>是两种不同的类型,所以不可赋值。

但是,当我添加一个移动构造函数时:

template<class U, class E>
MoveAssignOnly_V2(MoveAssignOnly_V2<U, E>&& m) noexcept {}

static_assert fail!(gcc 和 clang)。

问题在这里:为什么移动构造函数会影响is_assignable?

已更新

我添加这个构造函数的原因是我发现 std::unique_ptr 有一个

template< class U, class E >
unique_ptr( unique_ptr<U, E>&& u ) noexcept;

,这让我有点困惑:既然它有这样一个ctor,它怎么不能赋值呢?所以我尝试将这样的构造函数添加到 MoveAssignOnly_V2 和 post 这个问题。这两个答案都很好,但是,仍然无法解释为什么 std::unique_ptr 在同时具有移动赋值和此模板化构造函数时不可赋值。

取此代码:

MoveAssignOnly_V2<int, float> lhs;
MoveAssignOnly_V2<int, double> rhs;
lhs = stdL::move(rhs);

当转换构造函数(注意它不是移动构造函数)不存在时,无法将rhs赋值给lhs

但是,当您添加构造函数模板时,现在有一种方法可以将 rhs 转换为 MoveAssignOnly_V2<int, float> 类型(创建该类型的临时对象)。然后,可以从那个临时移动分配到 lhs.

这与以下原理相同:

double lhs = 3.14;
float rhs = 42.f;
lhs = std::move(rhs);

解决问题中的更新:

你不能单独使用函数声明,你必须阅读完整的规范(在标准或 suitable reference 中)。引用有关 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

因此,如您所见,必须实现 unique_ptr 的转换构造函数,以便它仅在源删除器可以转换为目标删除器时才处于活动状态。这与上一个问题中移动分配的规则基本相同。

您在此处添加的内容:

template<class U, class E>
MoveAssignOnly_V2(MoveAssignOnly_V2<U, E>&& m) noexcept {}

不仅是一个移动构造函数,而且是一个模板构造函数,可以从任何MoveAssignOnly_V2<U, E>.

构造MoveAssignOnly_V2<T, D>

因此从 MoveAssignOnly_V2<int, double>> 构建 MoveAssignOnly_V2<int, float> 就可以了。