当存在用户定义的移动分配运算符时,已删除模板移动分配运算符

Templated move assignment operator deleted when there is a user defined move assignment operator

本文关键字:运算符 移动 分配 删除 用户 存在 定义      更新时间:2023-10-16

我有一个带有复制构造函数的类,它只有在满足条件时才启用,比如在本例中,当类型参数不是引用时。以及一个既不可移动也不可复制的成员(比如互斥体(。例如(https://wandbox.org/permlink/hRx51Ht1klYjN7v5)

#include <iostream>
#include <tuple>
#include <mutex>
using std::cout;
using std::endl;    
template <typename T>
class Something {
public:
Something() {}
template <typename Type = T, 
std::enable_if_t<!std::is_reference<Type>{}>* = nullptr>
Something(const Something&) {}
Something& operator=(Something&&) {
return *this;
}
std::mutex mutex_;
};
int main() {
auto&& one = Something<int>{};
auto two = one;
std::ignore = two;
}

当我编译这个代码时,我得到一个错误,说

copy constructor is implicitly deleted because 'Something<int>' has a user-declared move assignment operator
Something& operator=(Something&&) {
^
1 error generated.

好的,所以我试着对移动分配操作符应用同样的约束

template <typename T>
class Something {
public:
Something() {}
template <typename Type = T, 
std::enable_if_t<!std::is_reference<Type>{}>* = nullptr>
Something(const Something&) {}
template <typename Type = T, 
std::enable_if_t<!std::is_reference<Type>{}>* = nullptr>
Something& operator=(Something&&) {
return *this;
}
std::mutex mutex_;
};

错误更改为该

copy constructor of 'Something<int>' is implicitly deleted because field 'mutex_' has an inaccessible copy constructor
std::mutex mutex_;
^
1 error generated.

关于我该怎么做,有什么想法吗?当我显然想默认构造不可移动的不可复制成员时,编译器为什么会抱怨?当我删除约束时,这会编译得很好https://wandbox.org/permlink/daqWAbF40MyfDJcN

特殊成员函数(复制/移动(由其签名标识。当您尝试对它们进行模板化时,您正在使用另一个签名来定义模板,因为模板参数是模板签名的一部分,并且该功能与您试图禁止的复制/移动c'tors无关。

为了完整性,引用标准,强调我的:

[class.copy]/2

类X的非模板构造函数是复制构造函数,如果它的第一个参数是类型X&,常量X&,易失性X&或常数易失性X&,并且要么没有其他参数,要么所有其他参数都有默认参数([dcl.fct.default](

在描述其他特殊成员时也会出现类似的措辞。它们必须是非模板。

因此,SFINAE不会影响这些功能。而且你不能用SFINAE直接影响它们,因为编译器会确保它们始终存在。影响它们生成或不生成的唯一方法是从基类继承(甚至有条件地(,或者拥有成员数据,这将导致它们被定义为已删除。


一线希望是,假设两年内没有任何变化,在C++20中,可以用requires子句约束复制构造函数(或任何特殊成员(:

Something(const Something&) requires !std::is_reference_v<T> {}

建议的解决方法(如果我正确理解需求(是将复制/移动功能和同步推迟到基类(必要时可以是混合(:

#include <iostream>
#include <tuple>
#include <mutex>
using std::cout;
using std::endl;    
// A class for handling synchronisation
struct Synchro
{
std::mutex mutex_;
};
// a class defining capabilities given a data type
template<class T>
struct DataManager : Synchro
{
DataManager() {}
DataManager(DataManager const&);
DataManager& operator=(DataManager const&);
DataManager(DataManager&&);
DataManager& operator=(DataManager&&);
};
// special treatment for references
template<class Ref>
struct DataManager<Ref&> : Synchro
{
DataManager() {}
DataManager(DataManager const&) = delete;
DataManager& operator=(DataManager const&) = delete;
DataManager(DataManager&&) = delete;
DataManager& operator=(DataManager&&) = delete;
};
// derive something from the specialised DataManager
template <typename T>
class Something : private DataManager<T>
{
public:
Something() {}
};
int main() {
auto&& one = Something<int>{};
auto two = one;
std::ignore = two;
}

第一条评论中的问题答案:

谢谢!如果你在Something中有一个模板函数,它基本上是一个移动构造函数(但带有模板(,那么当你在DataManager删除了移动构造函数的情况下移动Something的实例时。然后会发生什么?

将移动/复制功能推迟到基类的原因是我们希望从Something中完全删除此问题。

Something是否可以将构造委托给DataManager的默认构造函数?

从具有已删除构造函数的类继承有什么效果?

如果您没有在派生类中定义特殊的构造函数/析构函数(我们故意没有这样做(,并且我们在派生类上没有定义任何数据对象,那么它的效果与将复制/移动构造函数规则推迟到基类相同。这不是完全正确的描述,但实际上是发生了什么。

它是否隐式删除派生类的相应构造函数?

在这种情况下,是的。

派生类构造函数/赋值运算符是否相应地被删除?

在这种情况下有效,是的。

请记住定义基类中的所有数据对象。