Class 似乎可以通过用户定义的析构函数移动

Class seems to be movable with user defined destructor

我想弄清楚为什么这段代码无法编译? 我已经创建了用户定义的析构函数,因此不应创建移动构造函数和移动赋值运算符。那为什么complication失败了,说我的class不满足要求?

#include <iostream>
#include <concepts>
#include <type_traits>

template<class T>  requires (!std::movable<T>)
struct Singleton
{

};

struct Widget 
{
  ~Widget() = default;
};

int main()
{
  auto x = Singleton<Widget>{};
}

编辑。这个版本也是一样。

#include <iostream>
#include <concepts>
#include <type_traits>

extern void fun();

template<class T>  requires (!std::movable<T>)
struct Singleton
{

};

struct Widget 
{
  ~Widget() 
  {
    fun();
  } 
};

int main()
{
  auto x = Singleton<Widget>{};
}

错误信息

:23:28: note: constraints not satisfied : In substitution of 'template requires !(movable) > struct Singleton [with T = Widget]': :23:28: required from here :8:8: required by the constraints of 'template requires !(movable) struct Singleton' :7:30: note: the expression '!(movable) [with T = Widget]' evaluated to 'false' 7 | template requires (!std::movable)

在 godbolt 上使用 gcc (trunk) 编译。我使用的唯一编译标志是 -std=c++20

https://godbolt.org/z/sb535b1qv

两个例子中都没有移动构造函数。但这不是 std::movable 检查的内容。它将您测试的检查推迟到 std::move_constructible。该概念仅检查对象是否可以从右值直接初始化和复制初始化。

从 C++98 开始,复制构造函数就可以从右值进行初始化。它仍然适用于添加移动操作。这就是为什么该特征得到满足的原因。 Widget a; Widget b = std::move(a); 仍然有效,即使没有移动构造函数,因为编译器提供的复制构造函数使这成为可能。

编译器不生成移动构造函数是一项设计决策,目的是在迁移到 C++11 时保持旧代码正常工作。这样的代码会遵守三规则,因此将旧副本换成编译器生成的移动被认为是有风险的。但是旧代码仍然可以从右值初始化对象,所以复制构造函数保留了它的旧功能。


值得注意的是,该特性在未来可能会发挥预期的作用。此处定义的默认复制构造函数是已弃用的功能:

[class.copy.ctor]

6 If the class definition does not explicitly declare a copy constructor, a non-explicit one is declared implicitly. If the class definition declares a move constructor or move assignment operator, the implicitly declared copy constructor is defined as deleted; otherwise, it is defined as defaulted ([dcl.fct.def]). The latter case is deprecated if the class has a user-declared copy assignment operator or a user-declared destructor ([depr.impldec]).