enable_if with copy / move assignment operator

时间:2015-04-16 23:59:29

标签: c++ templates c++11 assignment-operator

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

#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.

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

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

我该如何实现这种行为?

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

1 个答案:

答案 0 :(得分:13)

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

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

  

用户声明的副本分配operator X::operator=class X的非静态非模板成员函数,其中只有一个X类型的参数,{ {1}},X&const X&volatile X&

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

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


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

const Foobar&

当且仅当所选的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> { }; 基类具有时,派生类型将使用零规则来默认具有复制和移动赋值。您必须为四种组合中的每种组合专门化AssignmentEnabler(无论是复制还是移动,无需移动即可复制,无需复制,都可以移动)。

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

AssignmentEnabler