Copy assignment operators and move assignment operators can return anything, including void
.
However, if you decide to declare them yourself (don’t forget the "Rule-of-Zero" described in S3624), it is a recommended practice to
return a non-const reference to the assigned object (left-operand). It allows the developer to chain the assignment operations, increasing consistency
with what other types do and, in some cases, enabling the writing of concise code.
This rule will raise for assignment operators that deviate from the above expectation.
Using an unconventional return type
This rule will raise an issue if the return type of the copy or move assignment operator, is different from mutable reference to the class
type.
Noncompliant code example
class Clazz {
public:
const Clazz& operator=(const Clazz& other); // Noncompliant, returns const reference
Clazz operator=(Clazz&& other) noexcept; // Noncompliant, returns by value
};
Compliant solution
class Clazz {
public:
Clazz& operator=(const Clazz& other); // Compliant
Clazz& operator=(Clazz&& other) noexcept; // Compliant
};
Returning an object different from *this
An assignment operator should return a reference to the assigned object. Conventionally, such return is expressed as return *this
, and
the rule will mark any return statement as deviating from this convention.
Noncompliant code example
class Clazz {
public:
Clazz& set(Clazz& other);
Clazz& operator=(Clazz const& other) {
return set(other); // Noncompliant: depends on return of `set` member function
}
Clazz&& operator=(Clazz&& other) noexcept {
return other; // Noncompliant, also return type is non-compliant
}
};
Compliant solution
class Clazz {
public:
Clazz& set(Clazz& other);
Clazz& operator=(Clazz const& other) {
set(other);
return *this; // Compliant
}
Clazz& operator=(Clazz&& other) noexcept {
return *this; // Compliant
}
};
In C++23, if the assignment operator is declared using an explicit object argument, the rule will mark any return statement that does not return
the object parameter directly.
Noncompliant code example
class Clazz {
public:
Clazz& set(Clazz& other);
Clazz& operator=(this Clazz& self, Clazz const& other) {
return self.set(other); // Noncompliant: depends on the return of `set` member function
}
};
Compliant solution
class Clazz {
public:
Clazz& set(Clazz& other);
Clazz& operator=(this Clazz& self, Clazz const& other) {
self.set(other);
return self; // Compliant
}
};
Declaring assignment operation as non-mutating
The assignment operation is designed to change the value of the target object, to the same one as the source. Such operation is mutating and thus
should not be declared with a const
qualifier.
Noncompliant code example
class Clazz {
public:
Clazz& operator=(Clazz const& other) const { // Noncompliant: also leads to noncompliant return statement
return const_cast<Clazz&>(*this);
}
Class& operator=(Clazz&& other) const; // Noncompliant
};
Compliant solution
class Clazz {
public:
Clazz& operator=(Clazz const& other) { // Compliant
return *this;
}
Clazz& operator=(Clazz&& other); // Compliant
};
When declaring an assignment operator with C++23 explicit object argument, the object argument should not be passed:
- by const reference - this is equivalent to declaring the implicit object parameter method as
const
, as described above;
- by value - in this case a temporary object will be created, and modified by the assignment operator, instead of the left-hand side of the
assignment operator
Noncompliant code example
class Clazz {
int val;
public:
Clazz& operator=(this Clazz const& self, Clazz const& other) const { // Noncompliant: also leads to non-compliant return
return const_cast<Clazz&>(self);
}
void operator=(this Clazz self, Clazz&& other) { // Noncompliant
self.val = other.val; // Modifies temporary object
}
};
Compliant solution
class Clazz {
int val;
public:
Clazz& operator=(this Clazz& self, Clazz const& other) { // Compliant
self.val = other.val;
return self;
}
Clazz& operator=(this Clazz& self, Clazz&& other) { // Compliant
self.val = other.val; // Modifies referenced object
return self;
}
};
Exceptions
This rule will not raise an issue when the assignment operator’s return type is declared void.
That syntax is commonly used when
assignment operator chaining is not desired. The issue will still be raised if such an assignment operator is declared as non-mutating.
class Clazz {
int val;
public:
void operator=(Clazz const& other) { // Compliant
self.val = other.val;
return self;
}
void operator=(Clazz&& other) const; // Noncompliant: declared as const
};