2

C++20 ranges::sort supports projections, and that is great, but I want to do stuff that is more complex, in particular sort on result of function that operates on projected member.

For function call I tried the transform and then sort that view, but C++20 sort seems to not work on views, this answer explains why(for range-v3).

In other words can I write this without using a lambda/functor?

struct S{
    int val;
};

int origin = 47;

// assume we can not change Distance to take S
int Distance(int val){
    return std::abs(val - origin);
}

void my_sort(std::vector<S> ss) {
  std::ranges::sort(ss, std::greater<>{}, [](const S& s){
      return Distance(s.val);
  });
}
NoSenseEtAl
  • 28,205
  • 28
  • 128
  • 277
  • What's wrong with lambda? You can create a function to have something like `Chained(&S::val, &Distance)`... – Jarod42 Sep 09 '21 at 12:50
  • One other way is to implement `bool operator < (const S& ss) const` in struct and use `std::sort` – Mayur Sep 09 '21 at 12:54
  • @Jarod42 nothing in particular, I just prefer to not write lambdas if I dont have to, for example I really love projections since they allow me to do a lot of stuff that required lambdas before. – NoSenseEtAl Sep 09 '21 at 12:55
  • 1
    Something like [`compose`](https://www.boost.org/doc/libs/master/libs/hof/doc/html/include/boost/hof/compose.html) from boost::hof? – Caleth Sep 09 '21 at 13:22
  • @Caleth yes, that is in the accepted answer, nice suggestion – NoSenseEtAl Sep 09 '21 at 14:05

2 Answers2

4

Seems like you want function composition:

void my_sort(std::vector<S>& ss) {
  std::ranges::sort(ss, std::greater<>{}, compose(Distance, &S::val));
}

Where compose(f, g)(x) does f(g(x)). Boost.Hof has a compose, there's one in range-v3 somewhere too. Not one in the standard library though.


For function call I tried the transform and then sort that view,

There is a big difference between sort(r, less(), f) and sort(r | transform(f), less()): the former sorts r using the function f as the sort key, the latter sorts the transformed values specifically. A concrete example demonstrating the difference:

struct X {
    int i;
    int j;
};

vector<X> xs = {X{1, 2}, X{2, 1}, X{0, 7}};

// this would give you (0, 7), (1, 2), (2, 1)
// because we're sorting by the i
ranges::sort(xs, less(), &X::i);

// this would have given you (0, 2), (1, 1), (2, 7)
// because we're sorting the i's independently
ranges::sort(xs | views::transform(&X::i), less());
Barry
  • 286,269
  • 29
  • 621
  • 977
  • Unintiutive but it makes sense, I thought that "transformed" items are still seen as one object since there is .base in view iterators... but I guess when I think about it this also makes sense. :) I presume there is no way to ask politely std::ranges::sort to "sort entire objects"(use .base()) – NoSenseEtAl Sep 09 '21 at 14:03
  • 1
    @NoSenseEtAl I disagree that it's unintuitive. `sort` sorts the thing you give it. If the thing you give it is the `i` members, it'll sort the `i` members. If you want to sort the whole `X`, then you ask it to sort the `X`s. It's also the difference between the largest value by key -- `ranges::max(f, less(), key)` -- and the largest key -- `ranges::max(f | transform(key))`. – Barry Sep 09 '21 at 15:09
  • 1
    @NoSenseEtAl Also if your transformation returns prvalues, they don't have an address to be swapped. A projection returning prvalues is fine, because that is immediately consumed by the comparison – Caleth Sep 09 '21 at 15:12
  • @Barry now that I think about it you are right, I guess I based my expectation on old std::sort that never worked on transform_view. – NoSenseEtAl Sep 09 '21 at 15:39
  • @Caleth good point, was just thinking how to do projection and I thought of transform_view but it is obviously very different thing... – NoSenseEtAl Sep 09 '21 at 15:58
  • 1
    ...or, if you are using an implementation that doesn't implement LWG3520, then the `views::transform` version can arbitrarily permute the remaining parts. – T.C. Sep 09 '21 at 20:15
1

You can use views::transform if your S has only one member:

void my_sort(std::vector<S> ss) {
  std::ranges::sort(
    ss | std::views::transform(&S::val), std::ranges::greater{}, Distance
  );
}

Demo.

康桓瑋
  • 33,481
  • 5
  • 40
  • 90