1

I remeber C++ Primer tells us operator< should be non-member function, and I always obey the rule. But now I want to know the reason.

I wrote following code:

#include <iostream>
using std::cout;
using std::endl;

struct Point1
{
  int x, y;
  Point1(const int a, const int b): x(a), y(b) { }
};
inline bool operator<(const Point1& lhs, const Point1& rhs)
{
  return lhs.x < rhs.x || (lhs.x == rhs.x && lhs.y < rhs.y);
}

struct Point2
{
  int x, y;
  Point2(const int a, const int b): x(a), y(b) { }
  bool operator<(const Point2& rhs)
  {
    return x < rhs.x || (x == rhs.x && y < rhs.y);
  }
};

int main()
{
  Point1 a(1, 2), b(1, 3);
  cout << (a < b) << " " << (b < a) << endl;
  Point2 c(2, 3), d(2, 4);
  cout << (c < d) << " " << (d < c) << endl;
}

In this case, It seems they don't make difference and member function seems much simpler.

But in this case:

#include <iostream>
using std::cout;
using std::endl;

 // Usually I write it for comparing floats
class Float1
{
  long double _value;
public:
  static const long double EPS = 1e-8;
  Float1(const long double value): _value(value) { }
  const long double Get() const { return _value; }
};
inline bool operator<(const Float1& lhs, const Float1& rhs)
{
  return rhs.Get() - lhs.Get() > Float1::EPS;
}
inline bool operator<(const Float1& lhs, const long double rhs)
{
  return rhs - lhs.Get() > Float1::EPS;
}

class Float2
{
  long double _value;
public:
  static const long double EPS = 1e-8;
  Float2(const long double value): _value(value) { }
  const long double Get() const { return _value; }
  bool operator<(const Float2& rhs)
  {
    return rhs._value - _value > Float2::EPS;
  }
  bool operator<(const long double rhs)
  {
    return rhs - _value > Float2::EPS;
  }
};

int main()
{
  Float1 x(3.14);
  Float2 y(2.17);
  long double zero = .0;
  cout << (x < zero) << " " << (zero < x) << endl;
  //cout << (y < zero) << " " << (zero < y) << endl; Compile Error!
}

Both (x < zero) and (zero < x) work! (is long double converted to Float?)

But (zero < y) don't, because zero is not a Float.

You see, in first case, member function costs less code length, and in second case, non-member function makes comparing easier. So I want to know

  • In first case, should I use member function instead of non-member function?
  • Why C++ Primer suggests binary operators be non-member function?
  • Is there any other case that member function and non-member function make difference?

Thanks for helping!

abcdabcd987
  • 2,043
  • 2
  • 23
  • 34

3 Answers3

6

I think the basic answer is that non-member functions play better with implicit conversion. So if you can write your binary operator as a non-member function, you should.

Lily Ballard
  • 182,031
  • 33
  • 381
  • 347
4

The question can be answered in different levels. In the highest level, from a design standpoint, operator< is a binary operator. It is no more of an operation on the left hand side that it is on the right. Member functions, on the other hand are bound to the first argument, they are an operation on the first type.

From a technical point of view, and going right to the C++ language, this comes down to what you already noticed: member functions are not symmetric with respect to the types. That lack of symmetry means that an operator< declared as a member function can only be applied when the left hand side is of the type that contains the member. No conversions can be applied before a member operator is called. On the other hand, because free functions are not more bound to the first argument than they are for the second, the free function operator< will be picked up by ADL whenever any of the two arguments is of the appropriate type, allowing the same conversions to the first and second arguments.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
2

As a non-member the comparison operator works also for derived class (left hand side) arguments.


EDIT: and as @jonathan points out in a comment, it also allows value conversions, such as e.g. an int left hand side argument.

If constructors and conversion operators are not explicit, such conversions can allow meaningless and probably unintended code, e.g. comparing a T instance to 5.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • That means `non-member function` play better with implicit conversion? – abcdabcd987 Aug 21 '12 at 01:02
  • @abcdabcd987: yes. but it's really opposite: " play better with implicit conversion" is a general phrase that could mean just about anything, and what it concretely means in this context is what I wrote. by the way, often one wants that, but not always, so the "should" advice is best ignored. – Cheers and hth. - Alf Aug 21 '12 at 01:11
  • 1
    But what you wrote is a limited subset of what the non-member operator permits. There are other conversions that are possible when you have a non-member function, such as a `double` on the LHS of the operator and a `Float1` on the RHS, a use that is not related to derived classes. – Jonathan Leffler Aug 21 '12 at 01:14
  • @Jonathan: yes you have a point. and those other conversions are those that can be troublesome. – Cheers and hth. - Alf Aug 21 '12 at 01:26