0

Consider the following code snippet:

typedef int (*FNPTR)(const void* p1, const void* p2);

typedef struct {
    char name[100];
    int age;
} Person_t;


void display(Person_t p[], size_t size)
{
    for (size_t i = 0; i < size; i++) {
        printf("%s: %d\n", p[i].name, p[i].age);
    }
}

int compare_by_age(const void* p1, const void* p2)
{
    return ((Person_t *)p1)->age - ((Person_t *)p2)->age;
}

int compare_by_name(const void* p1, const void* p2)
{
    return strcmp(((Person_t *)p1)->name, ((Person_t *)p2)->name);
}

FNPTR compare(int choice)
{
    return (choice == 0) ? compare_by_age : compare_by_name;
}

int main()
{
    Person_t p[] = {
        {"John", 21},
        {"Albert", 23}
    };

    size_t size = sizeof(p)/sizeof(p[0]);

    qsort(p, size, sizeof(p[0]), compare(1));
    display(p, size);
    ...
}

One disadvantage of this program is, if we keep adding fields to the Person structure, then we have to write "n" compare functions. Is it possible to create and return relevant compare function inside the compare itself, based on the choice(without adding "n" compare functions). Something similar to C++ lambads so that we can create and return a function within another function in C (or any other better logic).

Supreeth
  • 63
  • 5
  • What are you even trying to accomplish with such code? You are only making things harder to understand. Have a `compare_names` and `compare_ages` function and choose the right one when needed. – Marco Bonelli Oct 12 '20 at 23:45
  • 1
    Feel free to write your own `qsort` implementation that accepts something like: (1) struct offset; and (2) comparison type. Then you can call it with the offset of the thing you're sorting, and just have a small number of functions that deal with the datatype. Of course, that suffers from other loss of generality since it quickly becomes time-consuming to build secondary/tertiary/... sorting rules. There isn't really a magic way to make your code more generalized without also increasing its complexity. You _could_ create helpers to make your sorting functions less ugly though. – paddy Oct 12 '20 at 23:49
  • 2
    See [Pass extra parameter to comparator for qsort](https://stackoverflow.com/questions/4210689/pass-extra-parameter-to-comparator-for-qsort). – dxiv Oct 13 '20 at 00:02

1 Answers1

2

TBH, I think your best option is to write a custom sort function instead of shoehorning something into qsort(). But below are some ways to do it.

Note: This solution is (probably) not thread safe

It's probably impossible to do in a very neat way. This is C after all. But you could use a global variable like this:

enum field { NAME, AGE } field;

int compare(const void* p1, const void* p2)
{
    switch(field) {
    case AGE: 
        return ((Person_t *)p1)->age - ((Person_t *)p2)->age;        
    case NAME:
        return strcmp(((Person_t *)p1)->name, ((Person_t *)p2)->name);
    }
}

Not the sweetest solution, but at least you don't have to write tons of compare functions.

If you really want to be McGyver to avoid globals, you could use NULL as a sentinel for first argument to trigger configuration mode and then use p2->age as the configuration parameter, but I strongly advice against it. I'm showing it just to show that it's possible, but this is just silly:

int compare(const void* p1, const void* p2)
{
    static enum field field;

    if(!p1) { 
        switch((Person_t *)p2)->age) {
        case NAME: field = NAME; break;
        case AGE: field = AGE; break;
        }
        return 0; // Just to exit
    }

    switch(field) {
    case AGE: 
        return ((Person_t *)p1)->age - ((Person_t *)p2)->age;    
    case NAME:
        return strcmp(((Person_t *)p1)->name, ((Person_t *)p2)->name);
    }
}

A third option, also not a very good one, is to send the data in via the Person_t structs:

typedef struct {
    char name[100];
    int age;
    // Extra fields that the compare function can use
} Person_t;
klutt
  • 30,332
  • 17
  • 55
  • 95