c++niebloid

std::ranges algorithm function object customisation points AKA niebloids - problems with std lib types


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;
}

Solution

  • 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;
    }