Out of Sorts: How Not To Overload Operators
By now, I suspect you're getting tired of reading about order relations, so I'm going to go back to my original article about teaching C++ and look at the next topic: overloading operators as member functions in ways that behave differently from their built-in counterparts.
Here is a typical example:
class Thing {
public:
// …
Thing operator+(const Thing&) const;
// …
};
The programmer is defining a class named Thing that, among other members, has one named operator+. This member is called in a context such as
Thing t1, t2;
// …
… t1 + t2 …
in which the subexpression t1+t2 is a shorthand way of writing t1.operator+(t2).
People who write code such as this argue that "it is more object-oriented." Often they have used Java, in which all functions are members of classes, so this style is more familiar. Sometimes they merely want to explain member functions before they explain nonmember functions. Whatever the reason, this style of coding has a serious problem that comes from a discrepancy between readers' expectations of how overloaded operators behave and how they actually behave.
When we see an expression such as t1+t2, we expect that t1 and t2 will have similar constraints. We do not necessarily expect that t1+t2 will have the same value as t2+t1 (because we are used to seeing + for string concatenation, for example), but we expect that if t1+t2 compiles, then t2+t1 will do so as well. Making operator+ a member thwarts this expectation, because the left side of a member function call is not subject to the same conversion rules as the right side.
For example, suppose we add a constructor Thing(int) to our class. This constructor allows us to convert an int to a Thing, so if t1 is a Thing, we can write t1+1 as an expression. What we cannot do, however, is write 1+t1, because the left side of a member-function call is not automatically converted.
Notice that this expectation comes both from the use of an overloaded operator and from the particular choice of operator. It is much more surprising for 1+t1 to fail than for 1+=t1 to fail, because we do not expect the left operand of += to be converted. Similarly, if we had named our member add instead of operator+, we would not expect 1.add(t1) to work even though t1.add(1) does so (and even if we did not notice that 1. is a floating-point literal, which means that the "." is not part of a member-function call at all). In both cases, the usual usage of these members (operator+= and add, respectively) creates expectations that our code follows. These expectations are different from those created by overloading "ordinary" operators such as +.
It is possible to make a case that symmetric operations such as order relations should never be member functions. That's a tricky pedagogical matter, because it means that in order to show students how to define such operations, one must first either explain friend functions or tell the students to make data members public when they should really be private. However, because C++ uses the same syntactic rules for overloaded operators as it uses for the built-in versions of those operators, it is unwise to teach beginners to define overloaded operators that thwart the expectations that readers have developed from the built-in versions of those operators.

