5

Why is std::less (and equivalent other function object) are needed when it just calls < operator and we can anyways overload operators?

Possible answer is in question:

Why is std::less better than "<"?

However I am not totally convinced (specially about weak ordering). Can someone explain a bit more ?

code707
  • 1,663
  • 1
  • 8
  • 20
  • 4
    Possible duplicate of [Why is std::less better than "<"?](https://stackoverflow.com/questions/42762633/why-is-stdless-better-than) – Borgleader Jun 05 '18 at 12:59
  • Sort a vector of integers in descending order... now generalize.. now why omit the default operation? – StoryTeller - Unslander Monica Jun 05 '18 at 12:59
  • 2
    I believe it can be used for algorithms (ex: std::sort) without having to write a lambda doing the comparison. – Pimich Jun 05 '18 at 12:59
  • 1
    We cannot always overload operators. So rest of your question is meaningless. – Slava Jun 05 '18 at 13:08
  • Your edit doesn't actually cover why the other question's answer is not convincing. Are you not convinced that "`<` on pointers doesn't necessarily implement a strict-weak ordering", or that "std::less (...) is required to"? –  Jun 05 '18 at 13:11
  • I am not convinced that "< on pointer does not necessarily implement a strict weak ordering" – code707 Jun 05 '18 at 13:12
  • @code707 Okay. That's covered in the question https://stackoverflow.com/questions/4909766/is-it-unspecified-behavior-to-compare-pointers-to-different-arrays-for-equality (but since it's part of the premise of the question, not one of the answers, it wouldn't make sense to close this as a duplicate of that one) –  Jun 05 '18 at 13:19

3 Answers3

7

The purpose of std::less and friends is it allows you to generalize your code. Lets say we are writing a sorting function. We start with

void sort(int * begin, int * end) { /* sort here using < /* }

So now we can sort a container we can get int*'s to. Now lets make it a template so it will work with all type

template<typename Iterator>
void sort(Iterator begin, Iterator end) { /* sort here using < /* }

Now we can sort any type and we are using an "Iterator" as our way of saying we need something that points to the element. This is all well and good but this means we require any type passed to provide a operator < for it to work. It also doesn't let use change the sort order.

Now we could use a function pointer but that won't work for built in types as there is no function you can point to. If we instead make an additional template parameter, lets call it Cmp, then we can add another parameter to the function of type Cmp. This will be the comparison function. We would like to provide a default value for that so using std::less makes that very easy and gives us a good "default" behavior.

So with something like

template<typename Iterator, typename Cmp>
void sort(Iterator begin, Iterator end, 
          Cmp c = std::less<typename std::iterator_traits<Iterator>::value_type>) 
          { /* sort here using c /* } 

It allows you to sort all built in types, any type that has a operator <, and lets you specify any other way you want to compare elements in the data to sort them.

This is why we need std::less and friends. It lets us make the code generic and flexible without having to write a lot of boiler plate.


Using a function object also gives us some performance benefits. It is easier for the compiler to inline a call to the function call operator then it if it was using a function pointer. It also allows the comparator to have state, like a counter for the number of times it was called. For a more in-depth look at this, see C++ Functors - and their uses.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • 1
    And why can't you simply use "<" in the comparison? The argument that std::less can be used as argument without having to write a lambda is far more convincing. – Goswin von Brederlow Jun 05 '18 at 14:31
  • @GoswinvonBrederlow If you use `<` then you require all types passed to it to have a `operator <` defined.Why make that a requirement when you can let the caller decide if they want to provide a `operator <` or just give the function something to work with? – NathanOliver Jun 05 '18 at 14:37
  • I read "or just give the function something to work with" to passing an argument and for comfort the function would default it to std::less, just like you did above. That was my point. It's the argument passing and allowing overrides that makes it so useful. – Goswin von Brederlow Jun 07 '18 at 06:51
  • Compilers can also inline a call to a pointer to function if the function definition it refers to is available in the same compilation unit. The good thing about function objects is that you can store context information (some data) from one call to the following. The only thing that could be good is that if you can't inline (not defined yet), then the compiler raises an error. – Jorge Bellon Jul 19 '18 at 08:14
2

The first problem with < is that under the C++ standard, < on pointers can be utter nonsense on anything that doesn't point within the same "parent" object or array.

This is because of the existence of segmented memory models, like the 8086's. It is much faster to compare within segments by ignoring the segment number, and objects cannot span over segments; so < can just compare offsets within segments and ignore segment number.

There are going to be other cases like this on equally strange hardware; imagine hardware where const (ROM) and non-const (RAM) data exist in a separate memory space.

std::less<Pointer> meanwhile guarantees a strict weak ordering despite any quirks in the memory architecture. It will pay the price on every comparison.

The second reason we need std::less is to be able to pass the concept of "less than" around easiliy. Look at std::sort's 3 argument overload:

void sort( Iterator, Iterator, Comparator )

here we can pass how we want to sort in the 3rd parameter. If we pass std::greater<Foo>{} we get one sort, and if we pass std::less<Foo>{} we get the opposite.

By default the 2 argument version sorts like std::less, but once you have greater, greater equal, less equal, adding less just makes sense.

And once you have defined less, using it to describe the behavior of default std sort and std map and the like is easier than repeating all of the wording about how it uses < except if it is on pointers then it generates a strict weak ordering that agrees with < where < has fully specied behavior in the standard.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
1

std::less is just a default policy that takes the natural sorting order of an object (i.e., its comparison operators).

The good thing about using std::less as a default template parameter is that you can tune your sorting algorithm (or your ordered data structure), so that you can decide whether to use the natural sorting order (e.g. minor to major in natural numbers) or a different sorting order for your particular problem (e.g. first odd and then even numbers) without modifying the actual object operators or the algorithm itself.

struct natural {
   unsigned value;

   natural( unsigned v ) :
     value(v)
   {
   }

   bool operator< ( natural other ) const {
      return value < other.value;
   }
};

struct first_the_odds {
   bool operator()( natural left, natural right ) const {
      bool left_odd  = left.value % 2 != 0;
      bool right_odd = right.value % 2 != 0;

      if( left_odd == right_odd ) {
         return left < right;
      } else {
         return left_odd;
      }
   }
};

// Sort me some numbers
std::vector<natural> numbers = { 0, 1, 2, 3, 4 };
std::sort( numbers.begin(), numbers.end(), first_the_odds() );
for( natural n : numbers )
   std::cout << n.value << ",";

Output:

1, 3, 0, 2, 4,
Toby Speight
  • 27,591
  • 48
  • 66
  • 103
Jorge Bellon
  • 2,901
  • 15
  • 25