Search code examples
ccastingfunction-pointers

C: Cast void (*)(void *) to void (*)(char *)


If I try to call a function with a parameter void (*)(char *) with an actual void (*)(void *) I get this error:

note: expected ‘void (*)(char *)’ but argument is of type ‘void (*)(void *)’

Isn't it allowed to cast void (*)(void *) to void (*)(char *)? If not, why not? If it is safe, how can the error be suppressed without suppressing all casting errors.

Thanks!


Solution

  • I agree there should be 'assignment compatibility' when passing in an actual parameter of type void (*)(void *) when void (*)(char *) is expected.

    To some this may seem strange, as void * is the lesser specific type, so you shouldn't be allowed to assign it to a parameter or variable of type char *. But in this case it is the other way around. That is sometimes described as 'contravariance'. It is best demonstrated with an example.

    #include <stdio.h>
    #include <string.h>
    
    char globalData[10];
    
    void PassHello(void (*func)(char *))
    {
        char *text = "hello";
        func(text);
    }
    
    void CopyData(void *source)
    {
        memcpy(globalData, source, sizeof globalData);
    }
    
    void CopyHello(void)
    {
        PassHello(CopyData);
    }   
    
    int main()
    {
        CopyHello();
        puts(globalData);
    }
    

    There should be no problem for func(text) to make the call to CopyData. Even though CopyData is declared to accept void * as a parameter, it should also accept a more specific type, in this case char *.

    It is counter-intuitive, but true: void (*)(void *) as a type is more specific than void (*)(char *). That is what contravariance is all about.

    Unfortunately, the C language does not care about contravariance, allowing implicit pointer conversions for naked void * only. Compilers could implement this as a language extension, but current major compilers don't.

    Consequently, the code above gives a type error.

    Note: As pointed out by other posters, the type error is justified by the fact that the C standard does not guarantee different pointer types to be compatible. C compilers can take the liberty to generate different code depending on the type of a pointer. It's theoretically possible to make this work for 'nested' types too (in this case, the type of the parameter(s) of a function pointer), but compiler complexity would probably explode. So it made sense to draw a line somewhere; C is not Haskell.

    I think the cleanest solution (i.e. not involving explicit typecasts) is to make a 'wrapper' function around the void (*)(void *) function pointer. For example:

    void CopyCharData(char *source)
    {
        CopyData(source);
    }
    

    And then pass the wrapper instead:

    PassHello(CopyCharData);
    

    This will compile and run just fine.

    Live demo: https://repl.it/repls/ImmediateWindingOrders