1

I have a set of pointers and I want the set to be sorted in a specific order.

I came up with this code and it works as expected:

#include <string>
#include <iostream>
#include <set>

class Data
{
public:
  std::string name;
  int data;
  bool operator < (const Data& other) const
  {
    return name < other.name; 
  }

  bool operator < (const Data* other) const
  {
    std::cout << "never called ";
    return name < other->name;
  }
};

struct DataComparator
{
  bool operator()(const Data* lhs, const Data* rhs) const 
  {
    return *lhs < *rhs;
  }
};

int main() {
  Data d1{ "bb", 1 };
  Data d2{ "cc", 2 };
  Data d3{ "aa", 3 };

  std::set<Data*, DataComparator> s;
  s.insert(&d1);
  s.insert(&d2);
  s.insert(&d3);

  // print set members sorted by "name" field
  for (auto& d : s)
    std::cout << d->name << "\n";

  return 0;
}

What bothers me is the fact that I need to use the DataComparator struct in order to implement a custom sort order. I'd like the comparator be part of the Data class. I tried to implement the bool operator < (const Data* other) const class member and declare the set as std::set<Data*> s;, but now the operator < function is (unsurprisingly) never called and the sort order is by pointer address.

Is there some way to implement the custom comparator directly in the Data class so I can just have this:

 std::set<Data*> s;
 ...         
 // print set members sorted by "name" field
 for (auto& d : s)
   std::cout << d->name << "\n";
Jabberwocky
  • 48,281
  • 17
  • 65
  • 115
  • 2
    Are you aware that your `bool Data::operator < (const Data* other) const` has implicitly `const Data&` as it's 1st argument? That's the reason why it's never called. – Scheff's Cat May 20 '21 at 13:10
  • 1
    But the set is of pointers (Data*), not of Data itself. So the comparator would need to be of pointers. You can't implicitly use the comparison operator of the Data class to be used instead of the pointer comparison (without creating a bypass operator like you did) – Adrian Maire May 20 '21 at 13:10
  • 1
    Probably have to make a specialization of `std::less`, because that's what `std::set` is using as default parameter. – Dialecticus May 20 '21 at 13:14
  • @Dialecticus [Extending the namespace `std`](https://en.cppreference.com/w/cpp/language/extending_std) : *"t is allowed to add template specializations for any standard library class template to the namespace std only if the declaration depends on at least one program-defined type ... "* Does a pointer to a user-defined type count as a program defined type? – François Andrieux May 20 '21 at 13:17
  • @FrançoisAndrieux no, pointers are never user defined – Caleth May 20 '21 at 13:24
  • @FrançoisAndrieux "Program-defined types are non-closure class types or enumeration types that are not part of the C++ standard library and not defined by the implementation, or closure type of non-implementation-provided lambda expressions (since C++11), or instantiation of program-defined specializations." – Caleth May 20 '21 at 13:34
  • @FrançoisAndrieux it's down at the bottom of the page you linked – Caleth May 20 '21 at 13:39
  • @Caleth Oh, haha! Thanks, I thought I had looked everywhere, but I guess I forgot the most obvious place. – François Andrieux May 20 '21 at 14:07

1 Answers1

2

Is there some way to implement the custom comparator directly in the Data class so I can just have [stuff]:

No. I'd write a template

template <typename T>
struct PointerLess
{
    bool operator()(const T * lhs, const T * rhs) const
    {
        return *lhs < *rhs;
    }
};

Then you'd have std::set<Data*, PointerLess<Data>> etc.

Jabberwocky
  • 48,281
  • 17
  • 65
  • 115
Caleth
  • 52,200
  • 2
  • 44
  • 75
  • It's not quite what I want, but still somewhat better than my solution. BTW you forgot the `const` after `...T * rhs)`. – Jabberwocky May 20 '21 at 13:36