SyncMaster
SyncMaster

Reputation: 9936

Differences between various Custom Comparator functions in C++

I found that there are different ways to define custom compare functions for a user defined object. I would like to know the things that I should take into account before choosing one over another.

If I have student object, I can write a custom compare function in the following ways.

struct Student
{
    string name;
    uint32_t age;

    // Method 1: Using operator <
    bool operator<(const Student& ob)
    {
        return age < ob.age;
    }
};

// Method 2: Custom Compare Function
bool compStudent(const Student& a, const Student& b)
{
    return a.age < b.age;
}

// Method 3: Using operator ()
struct MyStudComp
{
    bool operator() (const Student& a, const Student& b)
    {
        return a.age < b.age;
    }
}obComp;

To sort a vector of students I can use either of the below methods.

vector<Student> studs; // Consider I have this object populated
std::sort(studs.begin(), studs.end());  // Method 1
std::sort(studs.begin(), studs.end(), compStudent);    // Method 2
std::sort(studs.begin(), studs.end(), obComp);  // Method 3

// Method 4: Using Lambda
sort(studs.begin(), studs.end(), 
     [](const Student& a, const Student& b) -> bool
     { 
        return a.age < b.age; 
     });

How are these methods different and how should I decide between these. Thanks in advance.

Upvotes: 2

Views: 140

Answers (4)

Richard Hodges
Richard Hodges

Reputation: 69864

How are these methods different and how should I decide between these.

They differ in their implicit statements of intent. You should use the form that expresses your intent most succinctly.

Relying on operator< implies to someone reading your code that your objects are implicitly ordered, like numbers or strings. They ought to be things that people would say, "well obviously x comes before y".

If the ordering of the map is more abstract, then an ordering function might be better because it expresses the idea that you are imposing an order on the map which may not be a natural order.

in the example you give, I might choose to express the intent in either a function object called ageIsLess for example. As a reader of code using the map is now fully aware of intent.

For example:

#include <cstdint>
#include <set>
#include <string>
#include <algorithm>
#include <iterator>

struct Student
{
    std::string name;
    std::uint32_t age;

};

struct ByAscendingAge
{
    bool operator() (const Student& a, const Student& b) const
    {
        return a.age < b.age;
    }
};


bool age_is_less(const Student& l, const Student& r)
{
  return l.age < r.age;
};

bool name_is_less(const Student& l, const Student& r)
{
  return l.name < r.name;
};

int main()
{

  // this form expresses the intent that any 2 different maps of this type can have different ordering

  using students_by_free_function = std::set<Student, bool (*)(const Student&, const Student&)>;

  // ordered by age
  students_by_free_function by_age_1(age_is_less);

  // ordered by name
  students_by_free_function by_name_1(name_is_less);

  // above two maps are the same type so we can assign them, which implicitly reorders
  by_age_1 = by_name_1;


  // this form expresses the intent that the ordering is a PROPERTY OF THIS TYPE OF SET
  using students_by_age = std::set<Student, ByAscendingAge>;

  // note that we don't need a comparator in the constructor
  students_by_age by_age_2;

  // by_age_2 = by_age_1;  // not allowed because the sets are a different type

  // but we can assign iterator ranges of course
  std::copy(std::begin(by_age_1), 
            std::end(by_age_1), 
            std::inserter(by_age_2, 
                          std::end(by_age_2)));


}

Upvotes: 0

Adam Martin
Adam Martin

Reputation: 1218

The performance between the different methods is not very different, however, using < will let you be more flexible, and makes using built-ins much easier. I also think using () is kind of weird.

The bigger issue in your example is that your methods should be using const refs instead of values. I.e. bool operator<(Student ob) could be friend bool operator<(const Student& ls, const Student& rs){...}. Also, see here for some examples of different things to consider when overloading operators.

Upvotes: 2

sjrowlinson
sjrowlinson

Reputation: 3355

There really is no "right" way per se, but if it makes sense for your object to have custom comparators (i.e. operator< etc.) then it would be wise to simply use those. However you may want to sort your object based on a different field member and so providing a custom lambda based on those field comparisons would make sense in that case.

For example, your Student class currently uses an overloaded operator< to compare student ages, so if you are sorting a container of Students based on age then just use this operator implicitly. However, you may want (at another time) to sort based on the names so in this case you could provide a custom lambda as the most elegant method:

std::vector<Student> vec;
// populate vec
std::sort(vec.begin(), vec.end(), [](auto& lhs, auto& rhs) { return lhs.name < rhs.name; });

where the student names are sorted via lexicographical comparisons.

Upvotes: 0

Jesper Juhl
Jesper Juhl

Reputation: 31467

The performance is not going to be noticably different. But it's convenient (and expected) in many cases to have a operator<, so I'd go for that over the special compare function.

Upvotes: 0

Related Questions