How to use ranges::sort for ascending or descending sort controlled by a boolean

610 views Asked by At

Using ranges allowed me to reduce boilerplate, so it's great but I could not find a way sort either in ascending order or descending order. The following snippet compiles just fine (g++ 10.2.0) and projection really simplifies the code, no need for a lambda.

  struct Player {
    double score_;
    string name_;
  };

  vector<Player> players{
    {10.0, "Thorin"}, {20.0, "Bombur"}, {15.0, "Bofur"}, {5.0, "Bifur"},
    { 2.0, "Balin"},  {25.0, "Kili"  }, {23.0, "Fili"},  {4.0, "Dwalin"}
  };

  std::ranges::sort(players, std::ranges::less{}, &Player::score_ );

  for(auto const &player : players) {
    cout <<  "Name = " << std::left << setw(10) << player.name_ 
         << " Score = " << player.score_ << endl;
  }

Now I need to have a boolean controlling ascending or descending order sort.

I would like to write a simple statement like this one:

 std::ranges::sort(players, sort_ascending ? std::ranges::less() : std::ranges::greater() , &Player::score_);

but std::ranges::less and std::ranges::greater do not have the same type, so a ternary operator won't work.

error: operands to ‘?:’ have different types ‘std::ranges::less’ and ‘std::ranges::greater’

I could have a lambda with capture has shown below, but that's adding more lines of code. Any simple solution in sight?

  auto mycompare = [sort_ascending](
                       const Player &a, 
                       const Player &b) -> bool  {
      return sort_ascending ^ (b.score_ < a.score_);

    };
  std::ranges::sort(players, mycompare);
1

There are 1 answers

0
cigien On

If sort_ascending is a run-time boolean value, then I don't think it's possible to choose a different function object inside the call to sort, since the type of this object must be known at compile time.

One option is to refactor this, with a couple of extra lines:

auto sort_if = [] (bool sort_ascending, auto &range, auto proj) 
{ 
    if (sort_ascending) 
        std::ranges::sort(range, std::ranges::less{}, proj);
    else
        std::ranges::sort(range, std::ranges::greater{}, proj);
};

and call it like this:

sort_if(sort_ascending, players, &Player::score_);

Also, note that your last snippet is broken if sort_ascending is false. The predicate would end up being the negation of std::less, which is std::greater_equal, not std::greater. This violates the strict-weak-ordering required by sort, and you end up with undefined behavior.