Home > Back-end >  Casting function pointer arguments without a helper function
Casting function pointer arguments without a helper function

Time:04-30

void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))

Is there a way to pass, let's say strcmp to qsort without making a helper function?

I was trying to do:

qsort(..., (int (*) (const void*, const void*) (strcmp)));

CodePudding user response:

Your attempt at the cast simply has a misplaced right (closing) parenthesis. The one at the end should be after the type of the cast. So, you can change:

(int (*) (const void*, const void*) (strcmp))
//                                          ^ wrong

to

(int (*) (const void*, const void*)) (strcmp)
//                                 ^ right

Alternatively, although hiding pointer types in typedef aliases is severely frowned-upon, function pointer types are an exception to that guideline. So, it is easier/clearer to define the required type for the qsort comparator first:

typedef int (*QfnCast) (const void*, const void*);

Then, you can cast to that type:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef int (*QfnCast) (const void*, const void*);

int main(void)
{
    char list[5][8] = {
        "Fred",
        "Bob",
        "Anna",
        "Gareth",
        "Joe"
    };

    qsort(list, 5, 8, (QfnCast)(strcmp));
    for (int i = 0; i < 5;   i) printf("%s\n", list[i]);
    return 0;
}

CodePudding user response:

int (*)(const void*, const void*) and int (*)(const char*, const char*) are not compatible function pointer types.

Casting between different, non-compatible function pointer types is explicitly undefined behavior, C17 6.3.2.3/8 emphasis mine:

A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer. If a converted pointer is used to call a function whose type is not compatible with the referenced type, the behavior is undefined.


So if you cast strcmp to something else, you are explicitly invoking undefined behavior. It will likely work in practice on any system where all pointer types are of equal size. But if you are going to rely on that, you might as well cook up something like this:

typedef union
{
  int (*strcmp) (const char*, const char*);
  int (*compare)(const void*, const void*);
} strcmp_t;

const strcmp_t hack = { strcmp };
...
qsort(str, x, y, hack.compare);

This is just as undefined behavior (and as likely to work in practice) but more readable.


You can never do qsort(str, x, y, strcmp) because again strcmp is not compatible with the function pointer type expected by qsort. Function parameter passing is done as per assignment, so the rules of simple assignment are the relevant part, from C17 6.5.11:

Constratints
...

  • the left operand has atomic, qualified, or unqualified pointer type, and (considering the type the left operand would have after lvalue conversion) both operands are pointers to qualified or unqualified versions of compatible types, and the type pointed to by the left has all the qualifiers of the type pointed to by the right;

Therefore qsort(str, x, y, strcmp) is always invalid C and this is not a quality of implementation issue. Rather, compilers letting this through without diagnostics are to be regarded as hopelessly broken.


And finally as noted in comments, strcmp only makes sense to use with bsearch/qsort in case you have a true 2D array of characters such as char str[x][y];. In my experience that's a rather rare use-case. When dealing with strings, you are far more likely to have char* str[x], in which case you must write a wrapper around strcmp anyway.

CodePudding user response:

There are two problems with what you're trying to do.

First, strcmp has type int (*)(const char *, const char *). This type is incompatible with the type int (*)(const void*, const void*) expected by the function because the parameter types are not compatible. This will result in qsort calling strcmp via an incompatible pointer type, and doing so triggers undefined behavior.

This might work if char * and void * have the same representation, but there's no guarantee this will be the case.

The second problem is that even if the call "works", what's ultimately being passed to strcmp isn't actually a char * but a char **. This means that strcmp will be attempting to read a char * value as if it were a sequence of char values.

So you have to use a helper function to get the results you want:

int compare(const void *a, const void *b)
{
    const char **s1 = a;
    const char **s2 = b;
    return strcmp(*a, *b);
}

CodePudding user response:

as @some programmer dude has already stated, it depends on what you're sorting. If it's an array of strings, you can use strcmp without a helper function and do a cast to avoid ugly warnings:

char s_array[100][100] = { "z", "a", ... };

qsort( s_array, 100, 100, (int (*)(const void *, const void *))strcmp );

If it's an array of pointers you need a helper function because it gets passed pointers to pointers:

char *p_array[100] = { "z", "a", ... };

int cmp( const void *p1, const void *p2 )
{
    return strcmp( *(const char **)p1, *(const char **)p2 );
}

qsort( p_array, 100, sizeof *p_array, cmp );
  • Related