2

I have a simple MyElement class, and I would like to use a bool MyElement::SomeMethod(...) {...} as the custom comparator for a std::set of MyElement items.

I have made my research, and I am already aware of some alternative solutions, which I list below. I also know how to change, for example, the comparator with std::greater instead of the default std::less, with code like this:

std::set<MyElement, std::greater<MyElement> > s;

My exact problem is that I want to use bool MyElement::SomeMethod(...) {...} as custom comparator. The only solution I come up with is analogous to the last one in the list below, namely the solution for boolean function:

using Cmp = std::integral_constant<decltype(&MyElement::SomeMethod), 
                                   &MyElement::SomeMethod>;
std::set<MyElement, Cmp> my_set;

This solution only works for a static MyElement::SomeMethod, though.

I am wondering if there is an analgous, or more concise, way for a non static method.

List of alternative solutions:

method for C++20

auto cmp = [](const MyElement& lhs, const MyElement& rhs) { return ... };
std::set<MyElement, decltype(cmp)> s;

method for C++11

auto cmp = [](const MyElement& lhs, const MyElement& rhs) { return ... };
std::set<MyElement, decltype(cmp)> s(cmp);

function instead of a lambda

 bool cmp(const MyElement& lhs, const MyElement& rhs) { return ...; }

and then

std::set<MyElement, decltype(cmp)*> s(cmp);

or

std::set<int, decltype(&cmp)> s(&cmp);

struct and operator()

struct cmp {
    bool operator() (const MyElement& lhs, const MyElement& rhs) const {
        return ...
    }
};

and then

std::set<MyElement, cmp> s;

boolean function

bool cmp(const MyElement& lhs, const MyElement& rhs) {
    return ...;
}

and then

#include <type_traits>
using Cmp = std::integral_constant<decltype(&cmp), &cmp>;
std::set<MyElement, Cmp> s;
vaeVictis
  • 484
  • 1
  • 3
  • 13
  • 2
    C++20 can also do `std::set s;` – Artyer Dec 29 '21 at 21:49
  • @Artyer thanks for the reminder, I am deciding whether to add it to my question or if it could make confusion :) – vaeVictis Dec 29 '21 at 21:53
  • 1
    Is `MyElement::SomeMethod` static? – HolyBlackCat Dec 29 '21 at 21:55
  • @HolyBlackCat no, it isn't. – vaeVictis Dec 29 '21 at 21:55
  • 1
    Then the `integral_constant` trick doesn't work: https://gcc.godbolt.org/z/3e41zzsY4 – HolyBlackCat Dec 29 '21 at 21:57
  • @HolyBlackCat thank you! I talked too early because I compiled my example only declaring the set but not using it. I update my question. – vaeVictis Dec 29 '21 at 22:09
  • 1
    Are you looking for `typedef std::function comparer; std::set{comparer{&MyElement::SomeNonStaticMemberFunction}};` ? – Ben Voigt Dec 29 '21 at 22:25
  • @BenVoigt thanks. To me (meaning my programming skill) this solution is a bit tricky. I think it's because of its two parameter signature. The first one is supposed to be ```this``` pointer, right? Because it only works if ```SomeNonStaticMemberFunction``` has one parameter. – vaeVictis Dec 30 '21 at 21:22
  • 1
    @vaeVictis: Yes, the `std::function` constructor will turn a pointer-to-non-static-member-function into a callable whose first parameter is the target object, which is known as `this` inside that member function, and the remaining parameters of the callable match the real parameters of that member function. – Ben Voigt Dec 30 '21 at 22:45
  • 1
    @vaeVictis: If you intend for the comparator to accept both objects to be compared, you can use a lambda or `std::bind` to build the `std::function` bound to a particular target object. But you asked specifically how to do it with a non-static member function, which means you want a `this` object somehow used in the comparison, and I'm confused how you will use it if it is not either of the two objects being compared. – Ben Voigt Dec 30 '21 at 22:55

2 Answers2

2

This is a bit subjective, but to me the cleanest option is struct + operator() to match the definition of std::less, the default comparator for std::set. There's nothing wrong with the other options but a comparison functor is a common pattern and easy to recognize.

You could also define MyElement::operator<, and then you wouldn't need to pass in a comparator separately.

Ari
  • 1,102
  • 9
  • 17
  • I agree, and I use this solution or a lambda. I am just exploring alternative ways. – vaeVictis Dec 30 '21 at 20:46
  • 1
    Keep in mind readability. Most of those approaches aren't idiomatic and harder to read. That doesn't necessarily disqualify them, but styles that follow the common patterns of the language are easier for others to understand at a glance. – Ari Jan 04 '22 at 15:32
1

You can use std::mem_fn to bind a member function.

#include <functional>
#include <iostream>
#include <set>
#include <utility>

struct S {
  int i;

  bool cmp(const S& other) const { return i < other.i; }
};

// Define make function to avoid having to write out template types.
template <typename T, typename Cmp>
std::set<T, Cmp> make_set(Cmp&& cmp) {
  return std::set<T, Cmp>{std::forward<Cmp>(cmp)};
}

int main(int argc, char* argv[]) {
  auto s = make_set<S>(std::mem_fn(&S::cmp));
  s.emplace(S{0});

  std::cout << s.begin()->i << std::endl;
  return 0;
}
Deev
  • 471
  • 2
  • 9
  • +1 for you, but my vote is locked. Please, just edit your answer so my vote could be shown. This being said, this solution works fine but I have just one question. Is it possible to use a two-parameter cmp? Something with ```lhs``` instead of ```this``` and ```rhs``` instead of ```other```. – vaeVictis Dec 30 '21 at 21:25
  • 1
    @vaeVictis: You certainly can name the parameter `rhs` instead of `other`. But I can't understand what a comparator should mean with three objects in play (`*this`, `lhs`, and `rhs`). No, you cannot rename the `this` keyword, but you can alias it. For example: `bool cmp(const S& rhs) const { const S& lhs = *this; return lhs.i < rhs.i; }`. If you just want to have two parameters and no `this`, then use the `static` keyword. But your question said you already could do it with a static member function and you specifically asked how to use a non-static member function (with a `this` pointer) – Ben Voigt Dec 30 '21 at 22:48
  • @BenVoigt my questions, badly written both here and under your other comment, was due to a little confusion with your solution function signature and Dev's one. I read some other documentation and I can say that you two have completely unraveled all my doubts. I appreciate. Thanks. – vaeVictis Dec 30 '21 at 23:42