我有一个带有复制构造函数的类,该类仅在满足条件时才启用,例如在本示例中,当type参数不是引用时才启用。以及既不可移动也不可复制的成员(例如互斥体)。 例如(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
答案 0 :(得分:2)
特殊成员功能(复制/移动)由其签名标识。当您尝试对它们进行模板化时,您正在使用另一个签名定义模板,因为模板参数是模板签名的一部分,并且该功能与您尝试复制/移动的控件无关。不允许。
为完整起见,引用标准,重点是我的:
类X的非模板构造器是复制构造器,如果其第一个参数的类型为X&,const X&,volatile X&或const volatile X&类型,并且没有其他参数,否则所有其他参数都有默认参数([dcl.fct.default])。
在描述其他特殊成员时,出现类似的措词。它们必须是非模板。
因此,您不会通过SFINAE影响这些功能。而且您不能直接使用SFINAE来影响它们,因为编译器确保它们始终存在。影响它们生成或缺失的唯一方法是从基类继承(有条件甚至是有条件的)或拥有成员数据,这将导致将它们定义为已删除。
假设两年内没有任何变化,一线希望是,在C ++ 20中,可以使用requires
子句约束复制构造函数(或任何特殊成员):
Something(const Something&) requires !std::is_reference_v<T> {}
答案 1 :(得分:2)
建议的解决方法(如果我正确理解了要求)是将复制/移动功能和同步推迟到基类(必要时可以是mixins):
#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
中完全消除此担忧。
是否可以将构造委托给DataManager的默认构造函数?
是
从具有已删除构造函数的类继承有什么效果?
如果您没有在派生类中定义特殊的构造函数/析构函数(我们故意没有做到),并且在派生类中没有定义数据对象,则其作用与推迟复制/移动构造函数规则相同到基层。这不是完全正确的描述,但实际上是发生的情况。
是否隐式删除派生类的相应构造函数?
在这种情况下,是的。
派生类的构造函数/赋值运算符是否得到相应删除?
在这种情况下有效,是的。
记住要在基类中定义所有数据对象。