Say I want to override the operator =
so I can do something like
Poly p1; // an object representing a polynomial
Poly p2; // another object of the
One might be tempted to make the copy-assignment operator return void
if you won't ever need chained assignments (as shown in the other answers) anyway. After all, chained assignments are often hard to read and understand, so not allowing them might be considered an improvement.
However, an often overlooked aspect is that void operator=(Poly& const)
means that your type would no longer fulfuill the CopyAssignable concept, which requires a T&
return type.
A type which does not fulfill the CopyAssignable
concept cannot be officially used for some standard-container operations, for example std::vector::insert
, which means that the following seemingly innocent piece of code yields undefined behaviour, even though it probably runs perfectly fine:
#include <vector>
struct Poly
{
void operator=(Poly const&) {} // Poly is not CopyAssignable
};
int main()
{
std::vector<Poly> v;
Poly p;
v.insert(v.begin(), p); // undefined behaviour
}
As the C++ standard explains in § 17.6.4.8/2.3 where it talks about constraints on programs using the standard library:
(...) the effects are undefined in the following cases:
(...) for types used as template arguments when instantiating a template component, if the operations on the type do not implement the semantics of the applicable Requirements subclause (...).
Of course, it's precisely because of the undefined behaviour that a compiler is allowed to ignore the error and make the program behave nicely, matching the obviously intended behaviour. But it's not required to do so.
You should also consider that you cannot predict all future uses of your Poly
type. Someone might write some template function such as:
template <class T>
void f(T const& t)
{
T t2;
T t3 = t2 = t;
// ...
}
This function would then not work with your Poly
class.
Just don't violate this C++ convention and you won't run into troubles.
Returning a reference to the target object allow assignment chaining (cascading), and overloading operators within a class follows right-associative (click here for detailed operator overloading rules)
Poly a, b, c;
a = b = c;
p2 = p1
is a shorthand for p2.operator=(p1)
. It is just calling your operator=
function, which is returning a reference to p2
, which you are then ignoring. To make this clear, let's call it assign
instead of operator=
:
Poly& Poly::assign(const Poly &source) {
.
.
.
return *this;
}
Now instead of p2 = p1
, you would write
p2.assign(p1);
In this case, the result of calling assign
is being ignored, but you don't have to ignore it. For example, you could write:
p3.assign(p2.assign(p1));
Using operator=
instead of assign
, this becomes
p3 = (p2 = p1);
but since assignment is right-associative, this can also be written as
p3 = p2 = p1;
This form of being able to do multiple assignments at once originally comes from C and has been preserved in C++ through the convention of returning *this
in operator=()
.
You return *this
so you can write normal compound C++ =
statements like:
Poly p1; //an object representing a polynomial
Poly p2;
Poly p2;
// ...
p3 = p2 = p1; //assigns all the contents of p1 to p2 and then to p3
because that statement is basically:
p3.operator=(p2.operator=(p1));
If p2.operator=(...)
didn't return *this
you'd have nothing meaningful to pass into p3.operator=(...)
.
what happens when you return *this?
In your example (p2 = p1;
), nothing. The method copies p1
into p2
and returns a reference to the 'this' object, which the calling code doesn't use.
In code such as p3 = p2 = p1;
, the first invocation is p2 = p1
, which copies p1
into p2
and returns a reference to p2
. The calling code then copies from that reference-to-p2
into p3
(and ignores the reference to p3
that is returned).
(In passing: do your unit tests ensure that p1 = p1
works properly? It's easy to forget that case!)