Do we really need two overload for operator () in functor?

Look at this example (from cppreference):

#include <algorithm>
#include <vector>
#include <iostream>

 struct S
 {
     int number;
     char name;
     // note: name is ignored by this comparison operator
     bool operator< ( const S& s ) const { return number < s.number; }
 };

 int main()
 {
     // note: not ordered, only partitioned w.r.t. S defined below
     const std::vector<S> vec = { {1,'A'}, {2,'B'}, {2,'C'}, {2,'D'}, {4,'G'}, {3,'F'} };

     std::cout << "\n" "Using heterogeneous comparison: ";
     struct Comp
     {
         bool operator() ( const S& s, int i ) const { return s.number < i; }
         bool operator() ( int i, const S& s ) const { return i < s.number; }
     };

     const auto p2 = std::equal_range(vec.begin(),vec.end(), 2, Comp{});

     for ( auto i = p2.first; i != p2.second; ++i )
         std::cout << i->name << ' ';
 }

Is there any need to overload operator () in 2 ways as below for a functor if I want to use the structure in std::equal_range?

PS: This is a follow up question of this one How equal_range is making use of heterogeneous comparison?, where apparently there was too many questions in one, hence I split it up to ask only for the two overloads here.


From cppreference about std::equal_range:

Returns a range containing all elements equivalent to value in the range [first, last).

Equivalence between a and b is determined by !comp(a,b) && !comp(b,a).

Hence, both variants are needed when the value is an int and differs from the type of the elements in the range. Note that if S had a converting constructor from int or some S{value,dummy_char} would be passed to std::equal_range as value then the comparator wouldn't be needed in the first place, but the default comparison via < could be used.


Quoted from cppreference:

The signature of the predicate function should be equivalent to the following:

bool pred(const Type1 &a, const Type2 &b);

While the signature does not need to have const &, the function must not modify the objects passed to it and must be able to accept all values of type (possibly const) Type1 and Type2 regardless of value category (thus, Type1 & is not allowed, nor is Type1 unless for Type1 a move is equivalent to a copy (since C++11)). The types Type1 and Type2 must be such that an object of type T can be implicitly converted to both Type1 and Type2, and an object of type ForwardIt can be dereferenced and then implicitly converted to both Type1 and Type2.

So as long as your types fulfill these requirements, one function is all that is needed.