【问题标题】:Templated move assignment operator deleted when there is a user defined move assignment operator当有用户定义的移动赋值运算符时,模板化的移动赋值运算符被删除
【发布时间】:2018-07-15 05:25:00
【问题描述】:

我有一个带有复制构造函数的类,该构造函数仅在满足条件时才启用,例如在此示例中,当类型参数不是引用时。还有一个既不能移动也不能复制的成员(比如互斥锁)。 例如(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

【问题讨论】:

  • 您的 SFINAE 每次都失败。你的意思是std::is_reference_vstd::is_reference&lt;Type&gt;::value;您当前的尝试将operator! 应用于没有它的结构,因此无论它是否是引用类型都会遇到替换失败。
  • @DanielH 并非如此,这适用于隐式整数转换, std::integral_constant 可转换为其相应的整数类型。在本例中为布尔值。
  • 糟糕,是的,我检查了,但仍然没有看到。我的错。

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


【解决方案1】:

特殊成员函数(复制/移动)由它们的签名标识。当您尝试对它们进行模板化时,您正在定义具有 another 签名的模板,因为模板参数是模板签名的一部分,并且该功能与您尝试的复制/移动 c'tors 无关不允许。

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

[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> {}

【讨论】:

  • 有趣,我没有意识到这一点,那么这种情况下的解决方案是什么?如何有条件地启用/禁用特殊功能?
  • @Curious - 没有解决方案。这些成员函数总是以一种形式或形式存在。要么您自己定义它们,要么它们由编译器定义(如删除)。您无法将它们从您自己设置的重载中移除。没有达到 C++17,我不确定 C++20。
  • @Curious - 根据我的阅读和 GCC,您可以使用 C++20 编译器来完成。对你没有帮助,但你有它。
  • 这很有趣,我不知道这个限制。好像我的理解不正确:(
  • @StoryTeller 在 libstdc++ 中,他们 inherit from a base class 那个 conditionally enables the copy and move consructor,就像你建议的那样。
【解决方案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 中完全消除这种担忧。

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

是的

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

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

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

在这种情况下,是的。

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

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

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

【讨论】:

  • 谢谢!如果您在Something 中有一个模板函数,它基本上是一个移动构造函数(但带有模板),那么当您移动Something 的实例时,DataManager 删除了移动构造函数。然后会发生什么? Something 可以将构造委托给DataManager 的默认构造函数吗?从具有已删除构造函数的类继承有什么影响?是否隐式删除了派生类对应的构造函数?派生类的构造函数/赋值运算符会被相应地删除吗?
  • @Curious 为问题添加了答案
猜你喜欢
  • 1970-01-01
  • 2017-11-21
  • 1970-01-01
  • 1970-01-01
  • 2021-06-08
  • 1970-01-01
  • 2018-09-21
  • 2011-11-19
  • 2016-05-19
相关资源
最近更新 更多