views:

744

answers:

9

There's the following declarations:

void qsort(void *lineptr[], int left, int right, int (*comp)(void *, void *));
int numcmp(char *, char *);
int strcmp(char *s, char *t);

Then, somewhere in the program there is the following call:

  qsort((void**) lineptr, 0, nlines-1, 
                    (int (*)(void*,void*))(numeric ? numcmp : strcmp));

(Ignore the first three arguments and numeric).

I ask what is this:

(int (*)(void*,void*))(numeric ? numcmp : strcmp)

I understand that qsort is expecting a "pointer to function that gets two void pointers and returns an int" as it's 4th argument but how what's written above satisfies that? It seems to me like some sort of cast because it is made of two parentheses, but that would be a very odd cast. Because it takes a function and makes this function a "pointer to function that gets two void pointers and returns an int". Which is meaningless.
(I followed here the rule that a type type in parenthesis before a variable promotes the variable to that type).

So I think I just get it wrong, maybe someone can tell me how to read this, what's the order?

A: 

Your logic is correct i think. It is indeed casting to "pointer to function that gets two void pointers and returns an int" which is the required type by the method signature.

Pedro Daniel
+3  A: 

You've missed the trick here - the portion

(numeric ? numcmp : strcmp)

is using the ternary operator to choose which function is being called inside of qsort. If the data is numeric, it uses numcmp. If not, it uses strcmp. A more readable implementation would look like this:

int (*comparison_function)(void*,void*) = 
    (int (*)(void*,void*))(numeric ? numcmp : strcmp);
qsort((void**) lineptr, 0, nlines-1, comparison_function);
Harper Shelby
I'm really not convinced that is any more readable :)
Sean Bright
@Sean Bright: I really don't think there *is* a way to make that readable. The only advantage I see in it is that it uses a variable name (comparison_function) that tries to explain what is going on.
Harper Shelby
Your syntax is wrong, although admittedly the syntax for function pointers is extremely awkward and confusing.
Adam Rosenfield
@Adam: feel free to fix it - I'm more C++ than C, and function pointers are, as you mentioned, a bit hairy.
Harper Shelby
@Harper: It wasn't really a shot at you. Without typedefs or copious amounts of whitespace, there is no elegant way of cleaning that up.
Sean Bright
sure, there is a much more readable way to do it...typedefs!. typedef int (*compare_t)(void *, void *); then you can do this: qsort((void**) lineptr, 0, nlines-1, (compare_t)(numeric ? numcmp : strcmp));
Evan Teran
or better yet, like your example of using a temp: compare_t cmp = numeric ? numcmp : strcmp; qsort((void **)lineptr, 0, nlines-1, cmp);
Evan Teran
Sorry, I didn't miss the ternary. I also wrote that it seems that there is a cast of a *function* into a pointer which also suggests that I know that the value of the expression (numeric ? numcmp : strcmp) is a function.
Leif Ericson
+2  A: 

What's happening here is indeed a cast. Lets ignore the ternary for a second and pretend that numcmp is always used. For the purpose of this question, functions can act as function pointers in C. So if you look at the type of numeric it is actually

(int (*)(int*,int*))

In order for this to be properly used in qsort it needs to have void parameters. Because the types here all have the same size with respect to parameters and return types, it's possible to substitute on for the other. All that's needed is a cast to make the compiler happy.

(int (*)(void*,void*))(numcmp )
JaredPar
You want to talk about numcmp, not numeric
Brian Campbell
@Brian, thanks. I updated the post
JaredPar
You wrote that "the type of numeric it is actually (int (*)(int*,int*))". I don't get it. The type of the ternary expression is a function and what you wrote is a pointer to a function.
Leif Ericson
@Leif, functions and pointers to functions are *somewhat* equivalent in C. For the purpose of this quesntion they can be considered the same.
JaredPar
Functions automatically decay into function pointers, just like arrays decay into pointers to their first elements.
Adam Rosenfield
You mean that functions decay into pointers to function when passed to other functions I assume?
Leif Ericson
You mean that functions decay into pointers to function when passed to other functions as parameters, right? And I assume that it happens because they are treated as labels in memory, hence constant, hence cannot be passed as-is. Is that correct?
Leif Ericson
A: 

Both numcmp and strcmp are pointers to functions which take two char* as parameters and returns an int. The qsort routine expects a pointer to a function that takes two void* as parameters and returns an int. Hence the cast. This is safe, since void* acts as a generic pointer. Now, on to reading the declaration: Let's take your strcmp's declaration:

 int strcmp(char *, char *);

The compiler reads it as strcmp is actually:

 int (strcmp)(char *, char *)

a function (decaying to a pointer to a function in most cases) which takes two char * arguments. The type of the pointer strcmp is therefore:

 int (*)(char *, char *)

Hence, when you need to cast another function to be compatible to strcmp you'd use the above as the type to cast to.

Similarly, since qsort's comparator argument takes two void *s and thus the odd cast!

dirkgently
+3  A: 

You can do it without the function pointer cast. Here's how. In my experience, in most places, if you are using a cast, you are doing it wrong.

sigjuice
+1  A: 

I would probably read it like this:

typedef int (*PFNCMP)(void *, void *);

PFNCMP comparison_function;

if (numeric)
{
    comparison_function =  numcmp;
}
else
{
    comparison_function = strcmp;
}

qsort((void**) lineptr, 0, nlines-1, comparison_function);

The example in the question has an explicit case.

MeThinks
+2  A: 

Note that the standard definition of qsort() includes const:

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

Note that the string comparator is given two 'char **' values, not 'char *' values.

I write my comparators so that casts are unnecessary in the calling code:

#include <stdlib.h>    /* qsort() */
#include <string.h>    /* strcmp() */

int num_cmp(const void *v1, const void *v2)
{
    int i1 = *(const int *)v1;
    int i2 = *(const int *)v2;
    if (i1 < i2)
        return -1;
    else if (i1 > i2)
        return +1;
    else
        return 0;
}

int str_cmp(const void *v1, const void *v2)
{
    const char *s1 = *(const char **)v1;
    const char *s2 = *(const char **)v2;
    return(strcmp(s1, s2));
}

Forcing people to write casts in the code using your functions is ugly. Don't.

The two functions I wrote match the function prototype required by the standard qsort(). The name of a function when not followed by parentheses is equivalent to a pointer to the function.

You will find in older code, or code written by those who were brought up on older compilers, that pointers to functions are used using the notation:

result = (*pointer_to_function)(arg1, arg2, ...);

In modern style, that is written:

result = pointer_to_function(arg1, arg2, ...);

Personally, I find the explicit dereference clearer, but not everyone agrees.

Jonathan Leffler
+1  A: 

As others have pointed out, for

(int (*)(void*,void*))(numeric ? numcmp : strcmp)

then the following is a type cast

(int (*)(void*,void*))

and the expression is

(numeric ? numcmp : strcmp)


C declarations can be quite difficult to read, but it is possible to learn. The method is to start at the inner part and then go right one step, then left one step, continuing right, left, right, left, etc outwards until finished. You do not cross outside a parenthesis before everything inside has been evaluated. For instance for the type cast above, (*) indicates this is a pointer. Pointer was the only thing inside the parenthesis so then we evaluate to the right side outside it. (void*,void*) indicates that is a pointer to a function with two pointer arguments. Finally int indicates the return type of the function. The outer parenthesis makes this a type cast. Update: two more detailed articles: The Clockwise/Spiral Rule and Reading C Declarations: A Guide for the Mystified.

However, the good news is that although the above is extremely useful to know, there is an extremely simple way to cheat: the cdecl program can convert from C to English description and vice versa:

cdecl> explain (int (*)(void*,void*))
cast unknown_name into pointer to function (pointer to void, pointer to void) returning int
cdecl> declare my_var as array 5 of pointer to int
int *my_var[5]
cdecl>


Exercise: What kind of variable is i?

int *(*(*i)[])(int *)

Answer in rot13 in case you do not have cdecl installed on your machine (but you really should!):

pqrpy> rkcynva vag *(*(*v)[])(vag *)
qrpyner v nf cbvagre gb neenl bs cbvagre gb shapgvba (cbvagre gb vag) ergheavat cbvagre gb vag
pqrpy>
hlovdal
+2  A: 

Whoever wrote that code snippet was trying to be too clever. In his mind, he probably thinks he is being a good programmer by making a clever "one-liner". In reality, he is making code that is less readable and is obnoxious to work with over the long term and should be rewritten in a more obvious form similar to Harper Shelby's code.

Remember the adage from Brian Kernighan:

Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.


I do lots of performance critical coding with hard real time deadlines... and I have still not seen a place where a dense one-liner is appropriate.

I have even messed around with compiling and checking the asm to see if the one-liner has a better compiled asm implementation but have never found the one-liner to be worth it.

Trevor Boyd Smith
Leif Ericson
insert mouth into foot... now.
Trevor Boyd Smith
it's not cleverness to use the standard library properly...
Alexandre C.