When you overload operator= , you can write it to return the type you want. If you want strong enough, you can overload X::operator= to return (for example) an instance of some completely different class Y or Z This is usually extremely impractical.
In particular, you usually want to support the operator= chain, like C. For example:
int x, y, z; x = y = z = 0;
In this case, you usually want to return the lvalue or rvalue of the assigned type. This only leaves the question of whether to return a reference to X, a reference constant to X or X (by value).
Returning a reference to constant X is usually a bad idea. In particular, a const reference can be bound to a temporary object. The lifetime of a temporary object extends to the lifetime of the link to which it is bound, but not recursively, to the lifetime of what can be assigned. This makes it easier to return a dangling reference - a const reference refers to a temporary object. This lifetime of the object extends to the lifetime of the link (which ends at the end of the function). By the time the function returns, the lifetime of the reference and temporary ends, so the assigned link is a dangling link.
Of course, returning a non-constant link does not provide complete protection against this, but at least makes you work a little harder. You can (for example) define some local ones and return a link to it (but most compilers can and will warn about it too).
Returning a value instead of a link has both theoretical and practical problems. On the theoretical side, you have a basic disconnect between = , which usually means what it means in this case. In particular, when an assignment usually means "take this existing source and assign its value to this existing destination", it begins to mean something more than "take this existing source, create a copy of it and assign this value to this existing destination."
From a practical point of view, especially before rvalue references were invented, this could have a significant impact on performance - creating an entire new object when copying AB was unexpected and often quite slow. If, for example, I had a small vector and assigned it to a larger vector, I would expect at most to copy the elements of a small vector plus a (small) fixed invoice to adjust the size of the destination vector. If two copies are involved instead: one from the source to the tempo, the other from temp to the destination, and (even worse) the dynamic allocation for the time vector, my expectation of the complexity of the operation will be completely destroyed. For a small vector, the time for dynamic distribution can be many times higher than the time for copying elements.
The only other option (added in C ++ 11) is to return the rvalue link. This can easily lead to unexpected results - chain assignment of type a=b=c; can destroy the contents of b and / or c , which would be rather unexpected.
This returns a returning normal link (and not a constant reference or a rvalue reference) as the only possibility that (reasonably) ensures that most people want it to work reliably.