如果有用户定义的移动分配运算符,则删除模板化的移动分配运算符

时间:2018-07-15 05:25:00

标签: c++ templates c++14 copy-constructor

我有一个带有复制构造函数的类,该类仅在满足条件时才启用,例如在本示例中,当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

2 个答案:

答案 0 :(得分:2)

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

为完整起见,引用标准,重点是我的:

  

[class.copy]/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的默认构造函数?

  

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

如果您没有在派生类中定义特殊的构造函数/析构函数(我们故意没有做到),并且在派生类中没有定义数据对象,则其作用与推迟复制/移动构造函数规则相同到基层。这不是完全正确的描述,但实际上是发生的情况。

  

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

在这种情况下,是的。

  

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

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

记住要在基类中定义所有数据对象。

相关问题