enable_if 与复制/移动赋值运算符

Posted

技术标签:

【中文标题】enable_if 与复制/移动赋值运算符【英文标题】:enable_if with copy/move assignment operator 【发布时间】:2015-06-23 15:24:00 【问题描述】:

我有一个类,只有当类的类型参数不能分别复制/移动构造时,我才想在其中启用复制/移动赋值运算符。所以我试试这个:

#include <type_traits>

template<typename T>
struct Foobar 

    Foobar(T value) : x(value) 
    Foobar(const Foobar &other) : x(other.x) 
    Foobar(Foobar &&other) : x(std::move(other.x)) 

    template<bool Condition = std::is_nothrow_copy_constructible<T>::value,
             typename = typename std::enable_if<Condition>::type>
    Foobar &operator=(const Foobar &rhs) 
        x = rhs.x;
        return *this;
    

    template<bool Condition = std::is_nothrow_move_constructible<T>::value,
             typename = typename std::enable_if<Condition>::type>
    Foobar &operator=(Foobar &&rhs) 
        x = std::move(rhs.x);
        return *this;
    

    T x;
;

int main() 
    Foobar<int> foo(10);
    Foobar<int> bar(20);

    foo = bar;
    foo.operator=(bar);

    return 0;

现在,Clang 给了我以下错误:

enable_if_test.cpp:31:9: error: object of type 'Foobar<int>' cannot be assigned because its copy assignment operator is implicitly
      deleted
    foo = bar;
        ^
enable_if_test.cpp:8:5: note: copy assignment operator is implicitly deleted because 'Foobar<int>' has a user-declared move
      constructor
    Foobar(Foobar &&other) : x(std::move(other.x)) 
    ^
enable_if_test.cpp:32:9: error: call to deleted member function 'operator='
    foo.operator=(bar);
    ~~~~^~~~~~~~~
enable_if_test.cpp:4:8: note: candidate function (the implicit copy assignment operator) has been implicitly deleted
struct Foobar 
       ^
enable_if_test.cpp:12:13: note: candidate function [with Condition = true, $1 = void]
    Foobar &operator=(const Foobar &rhs) 
            ^
enable_if_test.cpp:19:13: note: candidate function [with Condition = true, $1 = void] not viable: no known conversion from
      'Foobar<int>' to 'Foobar<int> &&' for 1st argument
    Foobar &operator=(Foobar &&rhs) 
            ^
2 errors generated.

现在,我包含了对赋值运算符的显式调用,以展示错误的怪异之处。它只是说我的模板是一个候选函数,并没有说明为什么不选择它。

我的猜测是,由于我定义了移动构造函数,编译器隐式删除了复制赋值运算符。由于它被隐式删除,它甚至不想实例化任何模板。但是,我不知道它为什么会这样。

我怎样才能实现这种行为?

(注意:实际代码有合理的理由来定义每个成员,我知道它在这里没有用。)

【问题讨论】:

T 不在赋值运算符的直接上下文中,因此不会发生 SFINAE。如果您添加一个默认为 T 的虚拟模板参数,然后用该参数替换 T 来替代您的模板参数的其余部分,它应该可以工作。 @0x499602D2 我不能完全理解你的论点。据我所知,OP 中的问题是函数模板不被视为特殊成员函数(默认 ctor 除外)。 “它甚至不想实例化任何模板” 据我所知,你最终会得到一个已删除的 operator=(Foo const&amp;) 和一个函数模板特化 @987654324 @。作为最后的手段,非模板函数优先于函数模板特化——这会选择已删除的运算符。 【参考方案1】:

最好也是唯一的方法是通过零规则——使用编译器提供的赋值运算符和构造函数,它们复制或移动每个成员。如果成员T x 无法被复制(移动)分配,那么您班级的复制(移动)分配运算符将默认为删除。

SFINAE 不能用于禁用复制和/或移动赋值运算符的原因是 SFINAE 需要模板上下文,但复制和移动赋值运算符是非模板成员函数。

用户声明的复制赋值operator X::operator=class X 的非静态非模板 成员函数,只有一个XX&amp;const X&amp; 类型的参数, volatile X&amp;const volatile X&amp;.

由于您的模板版本不算作用户声明的复制(移动)赋值运算符,因此它们不会禁止生成默认版本,并且由于首选非模板,因此默认版本将优先于您的模板定义(当参数是const Foobar&amp;,否则模板是更好的匹配,但禁用模板仍然不会禁用自动生成的模板)。

如果除了调用成员的复制(移动)赋值运算符之外还需要一些特殊逻辑,请在子对象中实现(基类或成员都可行)。


您也许可以通过从您用作基类的类模板的特化中进行选择,并在继承时传递适当的类型特征来实现您的目标:

template<bool allow_copy_assign, bool allow_move_assign>
struct AssignmentEnabler;

template<typename T>
struct Foobar : AssignmentEnabler<std::is_nothrow_copy_constructible<T>::value, 
                                  std::is_nothrow_move_constructible<T>::value>

;

The derived type will use the rule of zero to default to having copy and move assignment if and only if the selected AssignmentEnabler base class does.您必须针对四种组合中的每一种(既不复制也不移动、不移动复制、不复制移动)中的每一种都专门化AssignmentEnabler

你问题中代码的完整转换:

#include <type_traits>

template<bool enable>
struct CopyAssignmentEnabler ;

template<>
struct CopyAssignmentEnabler<false>

    CopyAssignmentEnabler() = default;
    CopyAssignmentEnabler(const CopyAssignmentEnabler&) = default;
    CopyAssignmentEnabler(CopyAssignmentEnabler&&) = default;
    CopyAssignmentEnabler& operator=(const CopyAssignmentEnabler&) = delete;
    CopyAssignmentEnabler& operator=(CopyAssignmentEnabler&&) = default;
;

template<bool enable>
struct MoveAssignmentEnabler ;

template<>
struct MoveAssignmentEnabler<false>

    MoveAssignmentEnabler() = default;
    MoveAssignmentEnabler(const MoveAssignmentEnabler&) = default;
    MoveAssignmentEnabler(MoveAssignmentEnabler&&) = default;
    MoveAssignmentEnabler& operator=(const MoveAssignmentEnabler&) = default;
    MoveAssignmentEnabler& operator=(MoveAssignmentEnabler&&) = delete;
;

template<typename T>
struct Foobar : CopyAssignmentEnabler<std::is_nothrow_copy_constructible<T>::value>,
                MoveAssignmentEnabler<std::is_nothrow_move_constructible<T>::value>

    Foobar(T value) : x(value) 

    T x;
;

int main() 
    Foobar<int> foo(10);
    Foobar<int> bar(20);

    foo = bar;
    foo.operator=(bar);

    return 0;

【讨论】:

不错!有些人可能会发现这也很有用:scottmeyers.blogspot.se/2012/10/…

以上是关于enable_if 与复制/移动赋值运算符的主要内容,如果未能解决你的问题,请参考以下文章

自动生成默认/复制/移动 ctor 和复制/移动赋值运算符的条件?

如何利用模板复制&移动构造函数和赋值运算符?

复制构造函数与赋值运算符(=)有何不同

c++ 拷贝构造函数与赋值运算符重载函数的区别是

派生自std :: exception的类的赋值运算符

对象的赋值运算符