What's the difference between overloading operator<(Class& a) and operator<(Class& a, Class& b)?

What is the difference between overloading operator<(Class& a) and operator<(Class& a, Class& b)?

Which needs to be defined for a user class for the std::is_sorted to work?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// Example program
#include <iostream>
#include <algorithm>
#include <climits>

using namespace std;

struct Foo {
  Foo(int val) { this->val = val; }
  Foo() { this->val = INT_MIN; }
  
  /*
  Causes output to be:
     is_sorted v1: 0
     is_sorted v2: 1
  bool operator<(const Foo& left) {
    return left.val < this->val;   
  }
  */
   
  /*
  Causes output to be:
     is_sorted v1: 1
     is_sorted v2: 0
  */
  friend bool operator<(const Foo& left, const Foo& right) {
    return left.val < right.val;   
  }
  
private:
  int val; 
};

int main() {
  // is_sorted(v1)should return true
  vector<Foo> v1 = {Foo(-1), Foo(1), Foo(2)};
  // is_sorted(v2) should return false
  vector<Foo> v2 = {Foo(2), Foo(1), Foo(-1)}; 
  cout << "is_sorted v1:" << is_sorted(begin(v1), end(v1)) << endl;
  cout << "is_sorted v2:" << is_sorted(begin(v2), end(v2)) << endl;
  return 0; 
}
Last edited on
At line 16, the parameter, which you've called "left" is actually the right side of the operator. *this is the left side. In other words, when you write
a < b
it will call a::operator<(b), not b::operator<(a)

So the output is reversed because the two comparison functions reverse the comparison.
@dhayden
I see. So in this use case, it should be defined like so:

1
2
3
bool operator<(const Foo& right) {
   return this->val < right.value;   
}


How is the other version used?
1
2
3
friend bool operator<(const Foo& left, const Foo& right) {
    return left.val < right.val;   
}
Last edited on
bool operator<(const Foo& right) is a member function. You can call it like a.operator<(b) (You could think of operator< as the name of the member function).


friend bool operator<(const Foo& left, const Foo& right) is not a member function. If you hadn't declared it as friend it would have to be declared outside the class.

1
2
3
4
5
6
7
struct Foo {
  ...
};

bool operator<(const Foo& left, const Foo& right) {
  ..
}

You can call it like operator<(a, b) (just like you would with a regular function that is not a member of a class).

To be able to access private members of the class it needs to be declared as a friend. You could do this by first declaring the function as friend inside the class and defining it outside.

1
2
3
4
5
6
7
8
struct Foo {
  friend bool operator<(const Foo&, const Foo&);
  ...
};

bool operator<(const Foo& left, const Foo& right) {
  ..
}

Or you could define the function at the same time you declare it as friend.

1
2
3
4
5
6
struct Foo {
  friend bool operator<(const Foo& left, const Foo& right) {
    ..
  }
  ...
};

This might look like operator< is a member of the class but it isn't. It's the same as the previous code (except for some minor differences in how the function is looked up which I don't think is relevant here).


Note that the way of calling the operators mentioned above are meant to prove a point about them being members or not. Normally you would just call them using the normal infix operator syntax: a < b
Last edited on
Note: The stand-alone non-member function does not need to be a friend, if it can me implemented via use of public interface of the class:
1
2
3
4
5
6
7
8
9
struct Foo {
...
public:
  int value() const { return val; }
};

bool operator< (const Foo& left, const Foo& right) {
    return left.value() < right.value() ;
}
Thanks Peter87, keskiverto.

I understand. The use of friend depends on whether you want the operator to have access to private members.

One thing that wasn't mentioned, beyond the scope of my original post, was what happens when both are defined.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <algorithm>
#include <climits>

using namespace std;

struct Foo {
  Foo(int val) { this->val = val; }
  Foo() { this->val = INT_MIN; }
  
  bool operator<(const Foo& right) {
    cout << "Member Overload Used\n";
    return this->val < this->val;   
  }
  
  friend bool operator<(Foo& left, Foo& right);
  
private:
  int val; 
};

bool operator<(Foo& left, Foo& right) {
  cout << "Friend Overload Used\n";
  return left.val < right.val;
}

int main() {
  cout << "Test 1: " << (Foo(-1) < Foo(3)) << endl; // Member Overload Used
    
  vector<Foo> v1 = {Foo(-1), Foo(1), Foo(2)};
  bool isv1Sorted = is_sorted(begin(v1), end(v1));
  cout << "is_sorted v1? " << isv1Sorted << endl; // As expected.
  
  vector<Foo> v2 = {Foo(2), Foo(1), Foo(-1), Foo(-5), Foo(10)}; // What if it's unsorted?
  bool isv2Sorted = is_sorted(begin(v2), end(v2));
  cout << "is_sorted v2? " << isv2Sorted << endl; // As expected. 
  
  vector<Foo> v3 = {Foo(-5), Foo(-1), Foo(1), Foo(2), Foo(10)}; // How many calls are used when it's sorted
  bool isv3Sorted = is_sorted(begin(v2), end(v2)); // Apparently still just 1. 
  cout << "is_sorted v3? " << isv3Sorted << endl; 
  return 0; 
}


Output
Member Overload Used
Test 1: 0
Friend Overload Used
Friend Overload Used
is_sorted v1? 1
Friend Overload Used
is_sorted v2? 0
Friend Overload Used
is_sorted v3? 0


If this definition were commented out:
1
2
3
4
bool operator<(Foo& left, Foo& right) {
  cout << "Friend Overload Used\n";
  return left.val < right.val;
}


The compiler complains:

/tmp/cc668t8J.o: In function `bool std::is_sorted<__gnu_cxx::__normal_iterator<Foo*, std::vector<Foo, std::allocator<Foo> > > >(__gnu_cxx::__normal_iterator<Foo*, std::vector<Foo, std::allocator<Foo> > >, __gnu_cxx::__normal_iterator<Foo*, std::vector<Foo, std::allocator<Foo> > >)':
:(.text._ZSt9is_sortedIN9__gnu_cxx17__normal_iteratorIP3FooSt6vectorIS2_SaIS2_EEEEEbT_S8_[_ZSt9is_sortedIN9__gnu_cxx17__normal_iteratorIP3FooSt6vectorIS2_SaIS2_EEEEEbT_S8_]+0x31): undefined reference to `operator<(Foo&, Foo&)'
collect2: error: ld returned 1 exit status

So apparently, std::is_sorted relies on the "friend" version of the overload.

[1] SO: Number of arguments in operator overload in C++
https://stackoverflow.com/a/15441840/5972766
Last edited on
GCC wrote:
warning: self-comparison always evaluates to false
 
return this->val < this->val;


The compiler complains ... undefined reference to `operator<(Foo&, Foo&)

You need to also remove the friend declaration on line 16.


So apparently, std::is_sorted relies on the "friend" version of the overload.

It's all got to do with which one is a better match. Note that the constness differ.

In Test 1 it prefers the member overload because the right parameter is const and the operands are temporary objects.

For non-temporary/non-const arguments it prefers the non-const overload.

If you mark everywhere const ...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Foo {
  ...
  bool operator<(const Foo& right) const {
    cout << "Member Overload Used\n";
    return this->val < right->val;   
  }
  friend bool operator<(const Foo& left, const Foo& right);
  ...
};

bool operator<(const Foo& left, const Foo& right) {
  cout << "Friend Overload Used\n";
  return left.val < right.val;
}

... then it will give you errors about the calls being ambiguous so it's probably a bad idea to declare < as both member and non-member.
Last edited on
The is_sorted has two versions; first uses operator< and the second you give a comparator:
https://cplusplus.com/reference/algorithm/is_sorted/
https://en.cppreference.com/w/cpp/algorithm/is_sorted

The first could be written with *next < *first or operator<(*next, *first)
It could also be implemented in terms of the second form; just call the second is_sorted with operator< as third parameter.


Your error, however is not from compiler. It is from linker. You have declared a function, used it, but provide no implementation.

This gives "undefined reference" too:
1
2
3
4
5
int add( int, int );

int main() {
  int answer =  add( 7, 35 );
}


If you comment out also the friend declaration, then you will learn some more.
How many calls are used when it's sorted
Apparently still just 1.

Make sure you pass the right vector to is_sorted.
Topic archived. No new replies allowed.