Shortcut from creating a comparator based on a member field or function

  • A+
Category:Languages

I often find myself wanting to create a comparator objects for a struct or class which simply extracts one member of the class and does the usual < comparison on that.

For example:

struct student {    int id;    std::string name; }  // sort by ID std::sort(students.begin(), students.end(), [](const student& l, const student& r){ return l.id < r.id; }); 

There's a lot of boilerplate there, in particular because we have to repeat the declaration for l and r. Is there a way in the standard library to create a comparator based on an "extractor" function which returns an object to compare on?

Something like:

std::sort(students.begin(), students.end(), compare_on([](const student& s){ return s.id; }); 

I'm using C++11, but also interested if there are solutions in later standards that don't apply in C++11 (so I can add something to my "reasons to upgrade" list).

 


You can define a utility class

template <class Fct> class compare_on {   public:     compare_on(Fct&& get) : get(std::forward<Fct>(get)) {}      template <class T> bool operator()(const T& lhs, const T& rhs)     {        return get(lhs) < get(rhs);     }    private:     Fct get; }; 

and then pass it to std::sort just like you depicted it (using C++17 class template argument deduction)

std::sort(students.begin(), students.end(),     compare_on([](const student& s){ return s.id; })); 

As of C++17, @Justin pointed out int the comments that the actual comparison can be improved (with #include <functional>) such that

return std::invoke(get, lhs) < std::invoke(get, rhs); 

which allows for an instantiation with data member references:

std::sort(students.begin(), students.end(), compare_on(&student::id)); 

When bound to C++11, forget about std::invoke and go with an explicit instantiation of compare_on. The latter isn't suitable for lambdas, hence the usual argument-deduction make_* helper:

template <class Fct> auto make_compare_on(Fct&& get)     -> decltype(compare_on<Fct>(std::forward<Fct>(get))) {     return compare_on<Fct>(std::forward<Fct>(get)); } 

Note that you can remove the trailing return type in C++14.

As a final note, the naming here should be improved: compare_on is misleading, as it hides what the function object really does - comparing by operator <. Maybe compare_less_then or something similar would be better, or adding another template parameter that can be specified as one of the standard predicates (std::less etc.).

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: