3

Say I have got a plain old data (POD) struct containing multiple numeric fields.

I want to overload the comparison operators so that I can compare two instances of my POD.

The problem is that I need an overload for those operators - say, operator less() - for each of the numerical fields in the POD.

Obviously, the below does not work because the compiler has no way to pick the two versions apart:

struct my_struct{
    int a;
    int b;
    int c;
};

bool operator<(const my_struct& one, const my_struct& two) {
    return one.a < two.a;
}

bool operator<(const my_struct& one, const my_struct& two) {
    return one.b < two.b;
}

So I wrote my own custom function lessThan(), which accepts a flag and does the job, but I don't particularly like this solution (in MWE below) as I cannot use < as I easily would with a comparison operator.

#include <iostream>
#include <iomanip>

struct my_struct{
  int a;
  int b;
  int c;
};

bool lessThan(const my_struct& one, const my_struct& two, const char flag) {
  switch (flag) {
    case 'a':
      return one.a < two.a;
      break;
    case 'b':
      return one.b < two.b;
      break;        
    case 'c':
      return one.c < two.c;
      break;
    default:
      throw("Either case a, b, or c\n");
      break;
  }     
}

int main()
{
  my_struct one{1, 3, 4};
  my_struct two{2, 2, 4};
  std::cout << "one < two: " << std::boolalpha 
            << lessThan(one, two, 'a') << std::endl;

  return 0;
}

Is there a way to somehow create multiple comparison operator overloads for custom POD structs with many numerical fields, with each overload comparing one of those fields?

(To give you more context, I need those overloads for sorting an array of files according to any of their characteristics: such as by dimension, by last modification date, by number of contained files or subdirectories, etc.

In the simplified example above, each POD instance abstracts a file.)


EDIT:

Sketch of intended usage:

sortAccordingToFieldA(std::vector<my_struct>& my_vec, int a){
  for (auto i : my_vec) {
    for (auto j : [std::find(my_vec.begin(), my_vec.end(), i)+1, my_vec.end()] ) {
      if (i < j)  // here '<' is overloaded according to field 'a'
        // sorting operations
    }
  } 
}

SECOND EDIT

Answer and comments here helped in completing this task

25
  • Can you share some psuedocode of how you'd want to use those overloaded functions/operators? It will help make the requirement clearer. Commented Feb 19 at 16:59
  • If you use std::sort, you can specify a comparison function object, with operator() which returns ​true if the first argument is less than the second. The function object can be of a custom class which accepts e.g. the field id in the constructor and then use it in the operator(). Commented Feb 19 at 17:07
  • @Mureinik pseudocode of intended usage in edit Commented Feb 19 at 17:09
  • 1
    @Giogre other than the comment there, how do intend to "tell" the code that it's comparing based on a and not based on something else? Commented Feb 19 at 17:11
  • Regarding your edit: if you want to use i < j syntax, I don't see how you can specify which field to use. Commented Feb 19 at 17:11

1 Answer 1

6

In your "intended usage" example, my_vec.begin()+i+1 makes no sense, as i is a my_struct instance in the vector, it is not an index of the vector. A range-for loop does not use indexes, it uses iterators.

In any case, since your function is named sortAccordingToFieldA() then just access field a directly, eg:

void sortAccordingToFieldA(std::vector<my_struct>& my_vec){
    for (auto i = my_vec.begin(); i != my_vec.end(); ++i) {
        for (auto j = i+1; j != my_vec.end(); ++j) {
            if (i->a < j->a) {
                // sorting operations
            }
        }
    } 
}

Repeat for sortAccordingToFieldB() and sortAccordingToFieldC() functions, accordingly.

If you really want a single function that is more generic, then you have a couple of choices:

  • pass in a pointer-to-member so the caller can specify which field to compare, eg:

    void sortAccordingToField(std::vector<my_struct>& my_vec, int my_struct::* field){
        for (auto i = my_vec.begin(); i != my_vec.end(); ++i) {
            for (auto j = i+1; j != my_vec.end(); ++j) {
                if ((*i).*field < (*j).*field) {
                    // sorting operations
                }
            }
        } 
    }
    
    sortAccordingToField(my_vec, &my_struct::a);
    sortAccordingToField(my_vec, &my_struct::b);
    sortAccordingToField(my_vec, &my_struct::c);
    
  • pass in a comparison function/lambda so the caller can decide how to compare the items, eg:

    template <typename Comparer>
    void sortAccordingToCompare(std::vector<my_struct>& my_vec, Comparer compare) {
        for (auto i = my_vec.begin(); i != my_vec.end(); ++i) {
            for (auto j = i+1; j != my_vec.end(); ++j) {
                if (compare(*i, *j)) {
                    // sorting operations
                }
            }
        } 
    }
    
    sortAccordingToCompare(my_vec, [](auto& i, auto& j){ return i.a < j.a; });
    sortAccordingToCompare(my_vec, [](auto& i, auto& j){ return i.b < j.b; });
    sortAccordingToCompare(my_vec, [](auto& i, auto& j){ return i.c < j.c; });
    
Sign up to request clarification or add additional context in comments.

5 Comments

I have edited my edit pseudo-code to make it more clunky and incomprehensible but now syntactically correct
@Giogre using std::find() in that manner is worse for performance. Better to just get rid of the range-for loop altogether and use the vector's iterators manually, as shown in my examples.
I will try to use the second option you propose. I'll have to give the sortAccording.. function to make_heap as comparison operator, and then bind the compared field to it as a parameter. It is going to look ugly
@Giogre The easiest option for that would be to use the range library (std::ranges::make_heap instead of std::make_heap) - those accept an additional projection parameter, so you don't need a custom comparator at all. e.g. std::ranges::make_heap(my_vec, {}, &my_struct::a); creates a max heap within my_vec based on the field a. godbolt example - if you still need a comparator (e.g. for std::priority_queue) you could write one that supports projections e.g. godbolt example less_by<my_struct, &my_struct::a>
@Turtlefight In the end I went with make_heap(my_vec.begin(), my_vec.end(), bind(lessThan, placeholders::_1, placeholders::_2, my_flag)) + sort_heap(my_vec.begin(), my_vec.end(), bind(lessThan, placeholders::_1, placeholders::_2, my_flag)) where my_flag is in an enum class. Your solution looks way better

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.