enable_if 和 copy/move 赋值运算符
enable_if with copy/move assignment operator
我有一个 class,我想在其中启用 copy/move 赋值运算符,仅当 class 的类型参数分别不可构造 copy/move 时。所以我尝试这样做:
#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, = void]
Foobar &operator=(const Foobar &rhs) {
^
enable_if_test.cpp:19:13: note: candidate function [with Condition = true, = void] not viable: no known conversion from
'Foobar<int>' to 'Foobar<int> &&' for 1st argument
Foobar &operator=(Foobar &&rhs) {
^
2 errors generated.
现在,我包括了对赋值运算符的显式调用,以展示错误的奇怪之处。它只是说我的模板是一个候选函数,并没有说明为什么不选择它。
我的猜测是,由于我定义了移动构造函数,编译器隐式删除了复制赋值运算符。由于它被隐式删除,它甚至不想实例化任何模板。但是,我不知道为什么会这样。
我怎样才能实现这种行为?
(注意:实际代码中有合理的理由定义每个成员,我知道它在这里没有用。)
最好也是唯一的方法是通过零规则——使用编译器提供的赋值运算符和构造函数,它们复制或移动每个成员。如果成员 T x
不能被复制(移动)赋值,那么你的 class 的复制(移动)赋值运算符将默认为 deleted。
SFINAE 不能用于禁用复制 and/or 移动赋值运算符的原因是 SFINAE 需要模板上下文,但复制和移动赋值运算符是非模板成员函数。
A user-declared copy assignment operator X::operator=
is a non-static non-template member function of class X
with exactly one parameter of type X
, X&
, const X&
, volatile X&
or const volatile X&
.
由于您的模板版本不算作用户声明的复制(移动)赋值运算符,因此它们不会抑制默认版本的生成,并且由于首选非模板,默认版本将优先于您的模板定义(当参数为 const Foobar&
时,否则模板更匹配,但禁用模板仍然不会禁用自动生成的模板)。
如果除了调用成员的复制(移动)赋值运算符外还需要一些特殊的逻辑,在子对象中实现(基或成员都可行)。
您也许可以通过从用作基础 class 的 class 模板的特化中进行选择,在继承时传递适当的类型特征来实现您的目标:
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;
}
我有一个 class,我想在其中启用 copy/move 赋值运算符,仅当 class 的类型参数分别不可构造 copy/move 时。所以我尝试这样做:
#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, = void]
Foobar &operator=(const Foobar &rhs) {
^
enable_if_test.cpp:19:13: note: candidate function [with Condition = true, = void] not viable: no known conversion from
'Foobar<int>' to 'Foobar<int> &&' for 1st argument
Foobar &operator=(Foobar &&rhs) {
^
2 errors generated.
现在,我包括了对赋值运算符的显式调用,以展示错误的奇怪之处。它只是说我的模板是一个候选函数,并没有说明为什么不选择它。
我的猜测是,由于我定义了移动构造函数,编译器隐式删除了复制赋值运算符。由于它被隐式删除,它甚至不想实例化任何模板。但是,我不知道为什么会这样。
我怎样才能实现这种行为?
(注意:实际代码中有合理的理由定义每个成员,我知道它在这里没有用。)
最好也是唯一的方法是通过零规则——使用编译器提供的赋值运算符和构造函数,它们复制或移动每个成员。如果成员 T x
不能被复制(移动)赋值,那么你的 class 的复制(移动)赋值运算符将默认为 deleted。
SFINAE 不能用于禁用复制 and/or 移动赋值运算符的原因是 SFINAE 需要模板上下文,但复制和移动赋值运算符是非模板成员函数。
A user-declared copy assignment
operator X::operator=
is a non-static non-template member function ofclass X
with exactly one parameter of typeX
,X&
,const X&
,volatile X&
orconst volatile X&
.
由于您的模板版本不算作用户声明的复制(移动)赋值运算符,因此它们不会抑制默认版本的生成,并且由于首选非模板,默认版本将优先于您的模板定义(当参数为 const Foobar&
时,否则模板更匹配,但禁用模板仍然不会禁用自动生成的模板)。
如果除了调用成员的复制(移动)赋值运算符外还需要一些特殊的逻辑,在子对象中实现(基或成员都可行)。
您也许可以通过从用作基础 class 的 class 模板的特化中进行选择,在继承时传递适当的类型特征来实现您的目标:
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;
}