使用值而不是引用的赋值运算符的分支

Ramification of assignment operators with values instead of references

本文关键字:引用 赋值运算符 分支      更新时间:2023-10-16

这个问题来自于这个答案提出的问题。

通常,我们将类型T的复制赋值运算符定义为T& operator=(const T&),将类型T的移动赋值运算符定义为T& operator=(T&&)

然而,当我们使用值参数而不是引用时会发生什么?

class T
{
public:
  T& operator=(T t);
};

这将使T既可复制也可移动。然而,我想知道的是T的语言分支是什么?

具体而言:

  1. 根据规范,这是否算作T的副本分配运算符
  2. 根据规范,这是否算作T的移动分配运算符
  3. T会有编译器生成的复制赋值运算符吗
  4. T会有编译器生成的移动赋值运算符吗
  5. 这是如何影响像std::is_move_assignable这样的特质类的

大部分内容在§12.8中进行了描述。第17段定义了什么是用户声明的副本分配运算符:

用户声明的复制分配运算符X::operator=是类X的非静态非模板成员函数,仅具有一个类型为XX&const X&volatile X&const volatile X&的参数。

第19段定义了什么算是用户声明的移动分配运算符:

用户声明的移动分配运算符X::operator=是非静态的类CCD_ 19的非模板成员函数键入X&&const X&&volatile X&&const volatile X&&

因此,它算作复制分配运算符,但不算作移动分配运算符。

第18段告诉编译器何时生成复制赋值运算符:

如果类定义没有显式声明副本赋值运算符,其中一个是隐式声明的。如果类定义声明移动构造函数或移动赋值运算符,隐式声明的复制赋值运算符被定义为已删除;否则,它被定义为默认值(8.4)。如果类具有用户声明的复制构造函数或用户声明的析构函数。

第20段告诉我们编译器何时生成移动赋值运算符:

如果类X的定义没有显式声明移动赋值运算符,如果并且仅当
[…]
--X没有用户声明的副本分配运算符,
[…]

由于该类有一个用户声明的复制赋值运算符,因此编译器不会生成这两个隐式运算符。

std::is_copy_assignablestd::is_move_assignable在表49中被描述为分别具有与is_assignable<T&,T const&>::valueis_assignable<T&,T&&>::value相同的值。当:时,该表告诉我们is_assignable<T,U>::valuetrue

表达declval<T>() = declval<U>()在处理时形成良好作为未赋值的操作数(第5条)。访问检查执行为如果在与CCD_ 31和CCD_。只有考虑赋值表达式的直接上下文。

由于declval<T&>() = declval<T const&>()declval<T&>() = declval<T&&>()都是该类的良好形式,因此它仍然算作可复制可分配和可移动可分配。

正如我在评论中提到的,这一切令人好奇的是,在移动构造函数存在的情况下,operator=将正确地执行移动,但从技术上讲,不算移动赋值运算符。如果类没有复制构造函数,那就更奇怪了:它将有一个复制赋值运算符,它不进行复制,只进行移动。