There are a few reasons people use friend:
sometimes granting friendship is actually reasonable, as the public API shouldn't expose some members than need to be compared
it's convenient for a lazy programmer to grant access to all the
privateandprotecteddata members, ensuring you can write the operator implementation without needing to go back to grant access later or use a less obvious/direct public function (that's NOT a good reason, just a lazy one)sometimes granting friendship is actually reasonable, as the public API shouldn't expose some members than need to be compared
you can define the operator function inside the class, where any template parameters, typedefs, constants etc. don't need to be explicitly qualified as they would in the surrounding [namespace] scope. That's considerably simpler for those new to C++.
e.g.:
template <typename T> struct X { friend bool operator==(const X& lhs, const X& rhs) { ... } }; ...vs... ...struct X as above without ==...
template <typename T> bool operator==(const X<T>& lhs, const X<T>& rhs) { ... } - in a two-birds-with-one-stone scoop, it makes the function nominally inline, avoiding One Definition Rule complications
None of these are particularly sound reasonsOnly the first reason above is a compelling functional reason for making the operator a friend, rather than making it a non-member function, given the lesser encapsulation and correspondingly higher maintenance burden involved.
There are excellent reasons though to prefer either a friend or non-friend non-member function to a member function thoughprefer either a friend or non-friend non-member function to a member function, as an implicit constructor can then kick in to allow the operator to work with one instance of the class and another value from which a second instance can be constructed:
struct X { X(int); }; bool operator==(const X& lhs, const X& rhs); x == 3; // ok for member or non-member operator== 3 == x; // only works for non-member operator== after implicit X(3) for lhs