Search code examples
c++functioncastingfunction-pointersfirst-class

C++ function pointer at declaration and argument


I am confused about using C++ function pointers.

using fn_p1 = void(int);  // function pointer
using fn_p2 = void (*)(int);

void functional(fn_p1 f) {
    f(1);
}

void callback(int value){
    // do something
}

int main() {
    fn_p2 f = callback; //works
    fn_p1 f1 = static_cast<fn_p1>(f); //does not work
    fn_p1 f2 = callback; //does not work
    fn_p1 f2 = static_cast<fn_p1>(callback); //does not work

    functional(f);  // works, the argument is form of void(*)(int)
    f(1);           // works
    
    functional(*f);  // works, the argument is dereferenced, void(int)
    (*f)(1);         // works
    return 0;
}

I know there is no difference if you call a function pointer with f(1), (*f)(1), or (*****f)(1). I don't get how functional(f); works but fn_p1 f1 = static_cast<fn_p1>(f); and its variants can not since they define the function pointer as using fn_p1 = void(int);. Could anyone explain how the function pointer works or how the compiler deals with it?


Solution

  • The most useful error that the IDE should give you is on the line fn_p1 f2 = callback;:

    Illegal initializer (only variables can be initialized) [illegal_initializer]

    (This is the message I get from clangd.)

    That means literally that an entity of type void(int) (or more in general someReturnType(someArgTypes...)) is not a variable.

    Indeed, in C++ functions are not first class, which means that you can't pass them to function and don't get them in return from function; when you think you are successfully doing so, you're in reality passing or taking back function pointers.

    In other words there's no such a thing in C++ as "a function value". Functions are not values that you can assign to.

    When you write

    fn_p1 f2 = callback; // assuming using fn_p1 = void(int);
    

    you are truly trying to create a variable of type void(int). But such a thing doesn't exist, hence the error.

    The static_casts don't work for fundamentally the same reason.


    As regards

    void functional(fn_p1 f) {
        f(1);
    }
    

    function-to-(function)pointer decaying is happening. f is truly of type fn_p1*.

    You can easily verify it by writing an invalid statement in functional in order to trigger a compiler error telling you what the type of f is, like this

    void functional(fn_p1 f) {
        int foo = f;
        f(1);
    }
    

    Clangd answers this

    Cannot initialize a variable of type 'int' with an lvalue of type 'fn_p1 *' (aka 'void (*)(int)') [init_conversion_failed]

    which indirectly tells you that f is of type fn_p1*, so you better write that instead of fn_p1 as the type of the parameter of functional, at least for clarity (similarly to how you should prefer writing T* instead of T[] as a parameter type in a function taking a c-style array of Ts).


    If you truly want to assign functions to variables, you should look at lambdas, structs+operator(), std::function, and the topic of function objects in general.