Ramification of assignment operators with values i

2019-02-04 13:02发布

This question comes from issues raised by this answer.

Normally, we define copy assignment operators for type T as T& operator=(const T&), and move assignment operators for type T as T& operator=(T&&).

However, what happens when we use a value parameter rather than a reference?

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

This should make T both copy and move assignable. However, what I want to know is what are the language ramifications for T?

Specifically:

  1. Does this count as a copy assignment operator for T, according to the specification?
  2. Does this count as a move assignment operator for T, according to the specification?
  3. Will T have a compiler-generated copy assignment operator?
  4. Will T have a compiler-generated move assignment operator?
  5. How does this affect traits classes like std::is_move_assignable?

1条回答
\"骚年 ilove
2楼-- · 2019-02-04 13:39

Most of this is described in §12.8. Paragraph 17 defines what counts as user-declared copy assignment operators:

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

Paragraph 19 defines what counts as user-declared move assignment operators:

A user-declared move assignment operator X::operator= is a non-static non-template member function of class X with exactly one parameter of type X&&, const X&&, volatile X&&, or const volatile X&&.

So, it counts as a copy assignment operator, but not as a move assignment operator.

Paragraph 18 tells when the compiler generates copy assignment operators:

If the class definition does not explicitly declare a copy assignment operator, one is declared implicitly. If the class definition declares a move constructor or move assignment operator, the implicitly declared copy assignment operator is defined as deleted; otherwise, it is defined as defaulted (8.4). The latter case is deprecated if the class has a user-declared copy constructor or a user-declared destructor.

Paragraph 20 tells us when the compiler generates move assignment operators:

If the definition of a class X does not explicitly declare a move assignment operator, one will be implicitly declared as defaulted if and only if
[...]
— X does not have a user-declared copy assignment operator,
[...]

Since the class has a user-declared copy assignment operator, neither of the implicit ones will be generated by the compiler.

std::is_copy_assignable and std::is_move_assignable are described in table 49 as having the same value as, respectively is_assignable<T&,T const&>::value and is_assignable<T&,T&&>::value. That table tells us that is_assignable<T,U>::value is true when:

The expression declval<T>() = declval<U>() is well-formed when treated as an unevaluated operand (Clause 5). Access checking is performed as if in a context unrelated to T and U. Only the validity of the immediate context of the assignment expression is considered.

Since both declval<T&>() = declval<T const&>() and declval<T&>() = declval<T&&>() are well-formed for that class, it still counts as copy assignable and move assignable.

As I mentioned in the comments, what's curious about all this is that, in the presence of a move constructor, that operator= will correctly perform moves, but technically not count as a move assignment operator. It's even stranger if the class has no copy constructor: it will have a copy assignment operator that doesn't do copies, but only moves.

查看更多
登录 后发表回答