Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ramification of assignment operators with values instead of references

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?
like image 256
Nicol Bolas Avatar asked Mar 17 '12 05:03

Nicol Bolas


People also ask

What is the value of assignment operator?

The assignment operator = assigns the value of its right-hand operand to a variable, a property, or an indexer element given by its left-hand operand. The result of an assignment expression is the value assigned to the left-hand operand.

What happens if you dont define a copy assignment operator?

If no user-defined copy assignment operators are provided for a class type (struct, class, or union), the compiler will always declare one as an inline public member of the class.

What happens when you use an assignment operator with objects?

The assignment operator (operator=) is used to copy values from one object to another already existing object. The purpose of the copy constructor and the assignment operator are almost equivalent -- both copy one object to another.


1 Answers

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.

like image 99
R. Martinho Fernandes Avatar answered Sep 21 '22 06:09

R. Martinho Fernandes