Search code examples
c++c++17variadic-templatessfinae

is there any way to obtain the callable's argument list and then use it as parameter pack in template?


callable should be any function pointer, std::function or lambda. I want to obtain their argument list and use them as parameter pack:

template <typename callable_T>
class callback2_t
{
public:
    using callable_t = callable_T;
    using ret_T = some_magic<callable_T>::ret_t;
    using data_T = the_first_type<argTs>;
    ...
    static ret_T _callback(some_magic<callable_T>::argTs... args);
};

The purpose is to simplify follow templates to make it work for all kind of callable without creating alias:

// other library only accept function pointer as callback, I want to wrap it to remove the callback when data go out of scope.

template <typename callable_T, typename ret_T, typename data_T, typename ...arg_Ts>
class callback_t
{
    using callable_t = callable_T;
public:
    callback_t(const char* signal, callable_t callable, data_T data)
            : f_{std::move(callable)}, data_{std::move(data)}
    {
        std::cout << signal << " " << typeid(callable).name() << std::endl;
        //register_callback(signal, _callback, this);

    }
    ~callback_t()
    {
        //unregister_callback(signal);
    }
    void test_callback(arg_Ts... args)
    {
        _callback(args..., this);
    }
private:
    callable_t f_;
    data_T data_;
    static ret_T _callback(arg_Ts... args, callback_t * self)
    {
        return self->f_(&self->data_, args...);
    }
};

// I donot want convert function pointer to std::function
// if possible. std::function is a heavy class.
template <typename ret_T, typename data_T, typename ...arg_Ts>
using fp_callback_t = callback_t<ret_T(*)(void *, arg_Ts...), ret_T, data_T, arg_Ts...>;

template <typename ret_T, typename data_T, typename ...arg_Ts>
using func_callback_t = callback_t<std::function<ret_T(void *, arg_Ts...)>, ret_T, data_T, arg_Ts...>;

We can use the template like this:

struct A{float x;};
struct B{int x;};
struct C{uint x;};
int func1(void * data, A* a)
{
    auto c = reinterpret_cast<C*>(data);
    std::cout<< a->x << ", " << c->x << std::endl;
    return a->x + c->x;
}

void func2(void *data, B* b, C* c)
{
    auto a = reinterpret_cast<A*>(data);
    std::cout << b->x << ", " << c->x << ", " << a->x << std::endl;
}
int main()
{
    A a1{-10.5f};
    B b1 {5};
    C c1{300};
    auto callback1 = fp_callback_t<int, C, A*>("signal1", &func1, c1);
    callback1.test_callback(&a1);
    auto callback2 = fp_callback_t<void, A, B*, C*>("signal2", &func2, a1);
    callback2.test_callback(&b1, &c1);
    std::function<int(void*, A*)> fc1 = [=](void* data, A* a){
        auto c = reinterpret_cast<C*>(data);
        std::cout<< a->x << ", " << c->x << ", " << a1.x << std::endl;
        return (int)a1.x;
    };
    std::function<void(void*, B*, C*)> fc2 = [=](void* data, B* b, C* c){
        auto a = reinterpret_cast<A*>(data);
        std::cout << b->x << ", " << c->x << ", " << a->x << ", " << c1.x << std::endl;
    };
    auto callback3 = func_callback_t<int, C, A*>("signal3", fc1, c1);
    callback3.test_callback(&a1);
    auto callback4 = func_callback_t<void, A, B*, C*>("signal4", fc2, a1);
    callback4.test_callback(&b1, &c1);
    return 0;
}

The out put is:

signal1 PFiPvP1AE
-10.5, 300
signal2 PFvPvP1BP1CE
5, 300, -10.5
signal3 NSt3__18functionIFiPvP1AEEE
-10.5, 300, -10.5
signal4 NSt3__18functionIFvPvP1BP1CEEE
5, 300, -10.5, 300

The deduction should work without specialize template parameters explicitly; I want to avoid alias; It should work with function pointer, std::function and lambda; the callable_t should be as it is given instead of casting all of them to std::function. like following:

auto callback1 = callback2_t("signal1", &func1, c1);
callback1.test_callback(&a1);
auto callback2 = callback2_t("signal2", &func2, a1);
callback2.test_callback(&b1, &c1);
std::function<int(void*, A*)> fc1 = [=](void* data, A* a){
    auto c = reinterpret_cast<C*>(data);
    std::cout<< a->x << ", " << c->x << ", " << a1.x << std::endl;
    return (int)a1.x;
};
auto callback3 = callback2_t("signal3", fc1, c1);
callback3.test_callback(&a1);
auto lambda1 = [=](void* data, B* b, C* c){
    auto a = reinterpret_cast<A*>(data);
    std::cout << b->x << ", " << c->x << ", " << a->x << ", " << c1.x << std::endl;
};
auto callback4 = callback2_t("signal4", lambda1, a1);
callback4.test_callback(&b1, &c1);

Solution

  • You don't need to deduce the parameters at all. Just let the template soak them up and forward them to the callable.

    template <typename callable_T, typename data_T>
    class callback_t
    {
        using callable_t = callable_T;
    public:
        callback_t(const char* signal, callable_t callable, data_T data)
                : f_{std::move(callable)}, data_{std::move(data)}
        {
        }
    
        template<typename...arg_Ts>
        auto test_callback(arg_Ts... args)
        {
            return _callback(this, args...);
        }
    private:
        callable_t f_;
        data_T data_;
        template<typename...arg_Ts>
        static auto _callback(callback_t * self, arg_Ts... args)
        {
            return self->f_(&self->data_, args...);
        }
    };
    

    Works great even if operator() is overloaded:

    int test()
    {
        callback_t cc("test", [](auto x, auto y){ return *x + y;}, 42);
        return cc.test_callback(9); // returns 42 + 9 = 51
    }