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:
T
, according to the specification?T
, according to the specification?T
have a compiler-generated copy assignment operator?T
have a compiler-generated move assignment operator?std::is_move_assignable
?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.
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.
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.
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 classX
with exactly one parameter of typeX
,X&
,const X&
,volatile X&
, orconst 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 classX
with exactly one parameter of typeX&&
,const X&&
,volatile X&&
, orconst 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 toT
andU
. 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With