There are serious advantages to non-member functions when dealing with operators.
Most operators are binary (take two arguments) and somewhat symmetrical, and with a member operator they only work if *this
is the left-hand side argument. So you need to use a non-member operator.
The friend pattern both gives the operator full access to the class (and, operators are usually intimate enough that this isn't harmful), and it makes it invisible outside of ADL. In addition, there are significant advantages if it is a template
class.
Invisible outside of ADL lets you do crazy stuff like this:
struct bob {
template<class Lhs, class Rhs>
friend bob operator+( Lhs&&, Rhs&& ) { /* implementation */ }
}
here our operator+
is a template
that seemingly matches anything. Except because it can only be found via ADL on bob
, it will only be matched if it is used on at least one bob
object. This technique can let you pick up on rvalue/lvalue overloads, do SFINAE testing on properties of types, etc.
Another advantage with template
types is that the operator ends up not being a template
function. look here:
template<class T>
struct X {};
template<class T>
bool operator==( X<T>, X<T> ) { return true; }
template<class T>
struct Y {
friend bool operator==( Y, Y ) { return true; }
};
struct A {
template<class T>
operator X<T>() const { return {}; }
};
struct B {
template<class T>
operator Y<T>() const { return {}; }
};
int main() {
A a;
X<int> x;
B b;
Y<int> y;
b == y; // <-- works!
a == x; // <-- fails to compile!
}
X<T>
has a template operator==
, while Y<T>
has a friend operator==
. The template
version must pattern-match both arguments to be a X<T>
, or it fails. So when I pass in an X<T>
and a type convertible to X<T>
, it fails to compile, as pattern matching does not do user defined conversions.
On the other hand, Y<T>
's operator==
is not a template
function. So when b == y
is called, it is found (via ADL on y
), then b
is tested to see if it can convert to a y
(it can be), and the call succeeds.
template
operators with pattern matching are fragile. You can see this problem in the standard library in a few points where an operator is overloaded in ways that prevent conversion from working. Had the operator been declared a friend operator
instead of a public free template
operator, this problem can be avoided.