If you overload a binary operator as a member function, it ends up asymmetrical: the left operand must be the exact type for which the operator is overloaded, but the right operand can be anything that can be converted to the correct type.
If you overload the operator with a non-member function, then both operands can be converted to get the correct type.
What you have as your second point looks like a concrete example of the same point, not really anything separate at all. Here's a concrete example of what I'm talking about:
class Integer {
int val;
public:
Integer(int i) : val(i) {}
operator int() { return val; }
// Integer operator+(Integer const &other) const { return Integer(val + other.val); }
friend Integer operator+(Integer const &a, Integer const &b) {
return Integer(a.val + b.val);
}
};
int main() {
Integer x(1);
Integer y = x + 2; // works with either operator overload because x is already an Integer
Integer z = 2 + x; // works with global overload, but not member overload because 2 isn't an Integer, but can be converted to an Integer.
return 0;
}
Also note that even though the definition of the friend
function is inside the class definition for Integer
, the fact that it's declared as a friend means it's not a member function -- declaring it as friend
makes it a global function, not a member.
Bottom line: such overloads should usually be done as free functions, not member functions. Providing the user with an operator that works correctly (drastically) outweighs theoretical considerations like "more object oriented". When necessary, such as when the implementation of the operator needs to be virtual, you can do a two-step version, where you provide a (possibly virtual) member function that does the real work, but the overload itself is a free function that invokes that member function on its left operand. One fairly common example of this is overloading operator<<
for a hierarchy:
class base {
int x;
public:
std::ostream &write(std::ostream &os) const {
return os << x;
}
};
class derived : public base {
int y;
public:
std::ostream &write(std::ostream &os) const {
return (base::write(os) << y);
}
};
std::ostream &operator<<(std::ostream &os, base const &b) {
return b.write(os);
}
This supports both polymorphic implementation (and access to a base class' protected members, if necessary) without giving up the normal characteristics of the operator to get it.
The primary exceptions to overloading binary operators as free functions are assignment operators (operator=
, operator+=
, operator-=
, operator*=
and so on). A conversion would produce a temporary object, which you can't assign to anyway, so for this particular case, the overload should be (must be, as a matter of fact) a member function instead.