Search code examples
c++std-functionstdbind

How to pass a std::function callback to a function requiring a typedef of a pointer to a function


I've got a C library that takes a function pointer to register commands. I want to use this in my C++ application. I've tried to use std::function in combination with std::bind to create a C compatible function pointer that will call my member function inside a class. When trying to pass the std::function, I get an compilation error.

// The way the C library typedef's the function
typedef int (*console_cmd_func_t)(int argc, char **argv);

// Function to register the callback needs a struct
struct {
    console_cmd_func_t func;
} console_cmd_t;


void console_cmd_register(const console_cmd_t *cmd) {
    // Register the command
}

// In my C++ class
typedef std::function<int(int argc, char **argv)> ConsoleFunction;
ConsoleFunction fn = std::bind(&MyClass::consoleCommandHandler, this, std::placeholders::_1, std::placeholders::_2);

const esp_console_cmd_t runCommand = {
    .func = fn
};
console_cmd_register(&runCommand);

However, this results in the following error:

cannot convert 'ConsoleFunction' {aka 'std::function<int(int, char**)>'} to 'console_cmd_func_t' {aka 'int (*)(int, char**)'} in initialization

Obviously its not the same definition. If I try to correct that however:

typedef std::function<console_cmd_func_t> ConsoleFunction;

I get the following error:

variable 'ConsoleFunction fn' has initializer but incomplete type

How can I successfully register the command?


Solution

  • struct {
        console_cmd_func_t func;
    } console_cmd_t;
    
    
    void console_cmd_register(const console_cmd_t *cmd) {
        // Register the command
    }
    

    Your C program is ill-formed. I'm going to assume that console_cmd_t isn't actually an instance of an unnamed struct as is depicted in the quoted code, but is rather a typedef name:

    typedef struct {
        console_cmd_func_t func;
    } console_cmd_t;
    

    How can I successfully register the command?

    By using the types that the functions expect. They don't expect a std::function, so you may not use std::function. There's also no way to register a non-static member function, nor a capturing lambda.

    A working example (assuming the correction noted above):

    int my_callback(int, char **);
    console_cmd_t my_struct {
        .func = my_callback,
    };
    console_cmd_register(&my_struct);
    

    In order to call a non-static member function, you would typically pass a pointer to the class as an argument into the callback. If the C API doesn't allow passing user defined arguments, then the only option is to use global state. Example:

    static MyClass gobal_instance{};
    
    int my_callback(int argc, char **argv)
    {
        gobal_instance.consoleCommandHandler(argc, argv);
    }
    

    To avoid global state, you need to re-design the C library.