2

All done with gcc 11.2 and libstdc++-11

Below code shows a variety of ways of using std::sort and then std::ranges::sort to sort builtins, user defined types and a std library type.

Only the std library type gives me trouble:

  1. I can't easily define a custom operator< or operator<=> because it is UB to add that to namespace std. I think there is no way around this? (without using a lambda or similar)
  2. for some reason I cannot pass a public member function as the proj parameter on the std lib type (but it works on the user defined type). Why? I was unable to penetrate the "wall of errors" enough to find out. gcc basically says: no known conversion for argument 3 from ‘<unresolved overloaded function type>’ to ‘std::identity’..

Update: My only theory on 2. above is that "unresolved overloaded function type" means that std::complex<double>::real has 2 overloads. One which takes a parameter (the "setter") and one which doesn't (the "getter"). Is there a syntax which allows me to specify that I want the "address of" the one without a parameter?

Update2: Thanks to 康桓瑋 for pointing out in the comments that taking the address of a member function in std is just UB anyway. However if I add a sum(int c) overload to thing (now added below) I get the same "unresolved overloaded" error. So the question remains, how can I select the one with no params. Or is there no way?

#include <algorithm>
#include <compare>
#include <complex>
#include <iostream>
#include <ranges>
#include <vector>

namespace std {
// this is UNDEFINED BEHAVIOUR!!! --- but "it works", so we know this option is what would be
// required, but is not available to us
std::partial_ordering operator<=>(const std::complex<double>& a, const std::complex<double>& b) {
  return std::abs(a) <=> std::abs(b);
}
} // namespace std

// a user defined type
struct thing {
  int x{};
  int y{};

  [[nodiscard]] int sum() const { return x + y; }
  [[nodiscard]] int sum(int c) const { return x + y + c; } // added for update 2


  friend std::strong_ordering operator<=>(const thing& a, const thing& b) {
    return a.x + a.y <=> b.x + b.y;
  }

  friend bool operator==(const thing& a, const thing& b) { return a.x + a.y == b.x + b.y; }

  friend std::ostream& operator<<(std::ostream& os, const thing& rhs) {
    return os << "[" << rhs.x << "," << rhs.y << "]";
  }
};

int main() {
  // builtin types
  auto ints = std::vector<int>{9, 10, 7, 8, 5, 6, 3, 4, 1, 2};
  std::ranges::sort(ints);
  std::ranges::sort(ints, {}, [](const auto& c) { return -c; });
  for (const auto& e: ints) std::cout << e << " ";
  std::cout << "\n";

  auto things = std::vector<thing>{{9, 10}, {7, 8}, {3, 4}, {1, 2}, {5, 6}};
  std::sort(things.begin(), things.end());
  std::ranges::sort(things);
  std::ranges::sort(things, {}, [](const auto& e) { return e.sum(); });
  std::ranges::sort(things, [](const auto& a, const auto& b) { return a < b; }, {});
  std::ranges::sort(things, {}, &thing::x);
  std::ranges::sort(things, {}, &thing::sum); // COMPILE ERROR afte r update 2
  for (const auto& e: things) std::cout << e << " ";
  std::cout << "\n";

  auto complexes = std::vector<std::complex<double>>{{9, 10}, {7, 8}, {3, 4}, {1, 2}, {5, 6}};
  std::sort(complexes.begin(), complexes.end()); // requires operator< or <=> which is UB
  std::ranges::sort(complexes);                  // requires operator<=> which is UB
  std::ranges::sort(complexes, {}, [](const auto& c) { return std::abs(c); });
  std::ranges::sort(complexes, {}, &std::complex<double>::real); // COMPILE ERROR!!
  for (const auto& e: complexes) std::cout << e << " ";
  std::cout << "\n";

  return EXIT_SUCCESS;
}
Oliver Schönrock
  • 1,038
  • 6
  • 11

1 Answers1

1

You need to specify a comparison for T if std::less<T> is unavailable.

Both std::sort and std::ranges::sort require a strict weak ordering predicate, not operator<=> (but that can supply one, via < and std::less)

template<typename T>
bool complex_less (std::complex<T> lhs, std::complex<T> rhs) { return abs(lhs) < abs(rhs); };

int main() {
  auto things = std::vector<thing>{{9, 10}, {7, 8}, {3, 4}, {1, 2}, {5, 6}};
  std::sort(things.begin(), things.end());
  std::ranges::sort(things);
  std::ranges::sort(things, {}, [](const auto& e) { return e.sum(); });
  std::ranges::sort(things, [](const auto& a, const auto& b) { return a < b; }, {});
  std::ranges::sort(things, {}, &thing::x);
  std::ranges::sort(things, {}, static_cast<int (thing::*)()>(&thing::sum)); // need cast to disambiguate pointer-to-member
  for (const auto& e: things) std::cout << e << " ";
  std::cout << "\n";

  auto complexes = std::vector<std::complex<double>>{{9, 10}, {7, 8}, {3, 4}, {1, 2}, {5, 6}};
  std::sort(complexes.begin(), complexes.end(), complex_less<double>); // fine
  std::ranges::sort(complexes, complex_less<double>);                  // also fine
  std::ranges::sort(complexes, {}, [](const auto& c) { return std::abs(c); }); // still fine
  std::ranges::sort(complexes, {}, [](const auto& c) { return c.real(); }); // fine
  for (const auto& e: complexes) std::cout << e << " ";
  std::cout << "\n";

  return EXIT_SUCCESS;
}
Caleth
  • 52,200
  • 2
  • 44
  • 75
  • yeah, this is all fine, and i know how to do that... but it "works around the issue" above. If it turns out, that there is no other solution, then yes.. these are some of the alternative ways – Oliver Schönrock Feb 08 '22 at 14:35
  • @OliverSchönrock you can't add `<` or `<=>` in the sensible place, so yes, there is no other option for `std::complex`. Similarly I would warn against adding to any namespace you don't control (although that isn't *prohibited* by the language) – Caleth Feb 08 '22 at 14:39
  • Yeah that's fair enoough... so it just remains to be seen if we can find a syntax to pass an "Overloaded member function pointer" to the `proj` parameter (as per comment discussion above).. which may be a terse option in some cases.. – Oliver Schönrock Feb 08 '22 at 14:45
  • It was the brackets, you should add the member function pointer static cast to this answer. – Oliver Schönrock Feb 08 '22 at 16:18