c++templatespointer-to-membertemplate-argument-deduction

Can I deduce the argument types of a member function pointer template parameter?


I often work with C-style libraries, where the callback-and-opaque-pointer pattern is common:

void callback(void *user_data, ...) { ... }

register_callback(callback, ptr_to_something_useful);

When working with such libraries from C++ land, I often find myself doing this:

struct my_type {
    static void static_callback(void *user_data) {
        reinterpret_cast<my_type *>(user_data)->callback();
    }
    void callback() { }
};

my_type foo;
register_callback(my_type::static_callback, &foo);

The static_callback can be a lambda... but I thought I'd be clever, and to eliminate some of the boilerplate, I defined the following C++ helper:

template <typename T, typename... Args>
struct dispatcher {
    template <void (T::*Func)(Args... args)>
    static void call(void *arg, Args... args) {
        T& obj = *reinterpret_cast<T *>(arg);
        (obj.*Func)(args...);
    }
};

Now I can omit static_callback and write:

register_callback(dispatcher<my_type>::call<&my_type::callback>, &foo);

Unfortunately, this gets ugly when the callback function takes several arguments (after the opaque user data pointer), because my helper requires me to explicitly specify both the class and argument types:

struct my_type {
    void callback(int a, char b, float c);
};

register_callback(dispatcher<my_type, int, char, float>::call<&my_type::callback>, &foo);

So, can I do better? I tried a few ways to get the compiler to somehow deduce the argument types in the parameter pack, but nothing has worked. The argument type parameter pack has to be defined somewhere before I can specify the non-type template parameter for the member function pointer. I have a feeling something might be possible with auto template parameters and/or type traits to extract the argument types, but I haven't struck upon a working solution yet.

Answers may target any C++ standard version. If there's something for this in Boost I will accept that too (I can't use Boost in my current project, but I would love to look there to see how something's implemented.)

Here's a sort of "test bench" you can use to play around:

struct my_type {
    void callback(int, char, float) { }
};

void register_callback(void (*cb)(void *, int, char, float), void *user_data) {
    cb(user_data, 42, 'X', 3.14f);
}

int main() {
    my_type obj;
    register_callback(/* your magic here */, &obj);
}

Solution

  • You can pass the pointer-to-member-function as a template parameter, then use a second type parameter to do the partial specialization on like so.

    #include <iostream>
    
    struct my_type {
        void callback(int i, char c, float f) {
            std::cout << i << " " << c << " " << f << "\n";
        }
    };
    
    void register_callback(void (*cb)(void *, int, char, float), void *user_data) {
        cb(user_data, 42, 'X', 3.14f);
    }
    
    template <auto Func, typename T = decltype(Func)>
    struct dispatcher;
    
    template <auto Func, typename Ret, typename Class, typename... Args>
    struct dispatcher<Func, Ret (Class::*)(Args...)> {
        static void call(void* obj, Args... args) {
            (reinterpret_cast<Class*>(obj)->*Func)(args...);
        }
    };
    
    int main() {
        my_type obj;
        register_callback(&dispatcher<&my_type::callback>::call, &obj);
    }
    

    This can techically be misused by explicitly specifying the type parameter, but you could probably guard against that with some static asserts if it's needed.