51

Is there a difference between defining a global operator that takes two references for a class and defining a member operator that takes only the right operand?

Global:

class X
{
public:
    int value;
};

bool operator==(X& left, X& right) 
{
    return left.value == right.value;
};

Member:

class X
{
    int value;
    bool operator==( X& right) 
    {
        return value == right.value;
    };
}
Gordon Gustafson
  • 40,133
  • 25
  • 115
  • 157
Vargas
  • 2,125
  • 2
  • 33
  • 53
  • 6
    It is good practice to declare arguments not only reference, but also const, whenever you are sure you can. Comparison operators can be const and take const references for sure. (If nothing else, it is promise to compiler which allows it to do more optimizations.) – Jan Smrčina Mar 16 '16 at 21:08

5 Answers5

56

One reason to use non-member operators (typically declared as friends) is because the left-hand side is the one that does the operation. Obj::operator+ is fine for:

obj + 2

but for:

2 + obj

it won't work. For this, you need something like:

class Obj
{
    friend Obj operator+(const Obj& lhs, int i);
    friend Obj operator+(int i, const Obj& rhs);
};

Obj operator+(const Obj& lhs, int i) { ... }
Obj operator+(int i, const Obj& rhs) { ... }
Tim Sylvester
  • 22,897
  • 2
  • 80
  • 94
  • As shown in Drew Dormann's answer, implicit conversion to Obj would result in having only one non-member function to deal with such commutative operations. – Hari Mar 11 '23 at 14:21
11

Your smartest option is to make it a friend function.

As JaredPar mentions, the global implementation cannot access protected and private class members, but there's a problem with the member function too.

C++ will allow implicit conversions of function parameters, but not an implicit conversion of this.

If types exist that can be converted to your X class:

class Y
{
public:
    operator X();  // Y objects may be converted to X
};


X x1, x2;
Y y1, y2;

Only some of the following expressions will compile with a member function.

x1 == x2;   // Compiles with both implementations
x1 == y1;   // Compiles with both implementations
y1 == x1;   // ERROR!  Member function can't convert this to type X
y1 == y2;   // ERROR!  Member function can't convert this to type X

The solution, to get the best of both worlds, is to implement this as a friend:

class X
{
    int value;

public:

    friend bool operator==( X& left, X& right ) 
    {
        return left.value == right.value;
    };
};
Drew Dormann
  • 59,987
  • 13
  • 123
  • 180
  • Item 24 of Effective C++ by Scott Meyers discusses about parameters of an operator requiring type conversion. The item is titled "Declare non-member functions when type conversions should apply to all parameters." – Hari Mar 11 '23 at 14:17
7

To sum up to the answer by Codebender:

Member operators are not symmetric. The compiler cannot perform the same number of operations with the left and right hand side operators.

struct Example
{
   Example( int value = 0 ) : value( value ) {}
   int value;

   Example operator+( Example const & rhs ); // option 1
};
Example operator+( Example const & lhs, Example const & rhs ); // option 2
int main()
{
   Example a( 10 );
   Example b = 10 + a;
}

In the code above will fail to compile if the operator is a member function while it will work as expected if the operator is a free function.

In general a common pattern is implementing the operators that must be member functions as members and the rest as free functions that delegate on the member operators:

class X
{
public:
   X& operator+=( X const & rhs );
};
X operator+( X lhs, X const & rhs )
{
   lhs += rhs; // lhs was passed by value so it is a copy
   return lhs;
}
David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
6

There is at least one difference. A member operator is subject to access modifiers and can be public, protected or private. A global member variable is not subject to access modifier restrictions.

This is particularly helpful when you want to disable certain operators like assignment

class Foo { 
  ...
private:
  Foo& operator=(const Foo&); 
};

You could achieve the same effect by having a declared only global operator. But it would result in a link error vs. a compile error (nipick: yes it would result in a link error within Foo)

JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • A declared only global operator will not link (i.e. no program), while a private member operator will. Only illegal uses of the private member will cause a compile error. Correct? – Panayiotis Karabassis Jul 14 '12 at 10:11
1

Here's a real example where the difference isn't obvious:

class Base
{
public:
    bool operator==( const Base& other ) const
    {
        return true;
    }
};

class Derived : public Base
{
public:
    bool operator==( const Derived& other ) const
    {
        return true;
    }
};

Base() == Derived(); // works
Derived() == Base(); // error

This is because the first form uses equality operator from base class, which can convert its right hand side to Base. But the derived class equality operator can't do the opposite, hence the error.

If the operator for the base class was declared as a global function instead, both examples would work (not having an equality operator in derived class would also fix the issue, but sometimes it is needed).

riv
  • 6,846
  • 2
  • 34
  • 63
  • this example misses whole problem and is misleading, since error you writing about has completely different root cause: implementation in `Derived` just hides operator overload in `Base`. [Demo](https://godbolt.org/z/x5rezPqE1) – Marek R Jan 15 '22 at 21:30