Search code examples
c++templatescallbackvariadic-templates

Save and pass to function list of template arguments in C++


I want to save and pass list of template arguments to function.
Like std::thread passes arguments to a thread. Types of arguments are templated and arguments count is not static.

Example, how it will work:

class CallbackList {
public:
    Callback(/* Type of list of template args */ args) {
        this->saved_args = args;
    }


    void Call() {
        this->callback(saved_args);
    }
private:
    /* Type of list of template args */ saved_args;

    CallbackType callback;
}   

Or how can I implement that:

template<typename ...Args>
class CallbackList {
public:
    using CallbackPrototype = /* some prototype */;

    void RegisterCallback(CallbackPrototype callback, Args... args) {
        CallbackInfo callback_info;
        callback_info.callback = callback;
        callback_info.args = { args... };
        this->callbacks.push_back(callback_info);
    }

    void Call() {
        for (CallbackInfo& callback_info : this->callbacks)
            callback_info.callback(callback_info.args);
    }

private:
    struct CallbackInfo {
        CallbackPrototype callback;
        /* what type should be here? tuple? args count are not static */ args;
    };

    std::vector<CallbackInfo> callbacks;
}   

It is possible?
How can I implement it?


Solution

  • If you do not want your callback to depend on the types of the arguments you have to use some kind of type erasure. You can, for example, use std::function from <functional>:

    #include <functional>
    #include <iostream>
    
    
    class Lazy_Callback
    {
    public:
      template <typename F, typename ...Args>
      Lazy_Callback(F && f, Args && ...args)
      : _fun([=]() { return f(args...); })
      { }
        
      void call() const
      {
        _fun();
      }
    protected:
    private:
      std::function<void()> _fun;
    };
    
    void print_int(int x)
    {
      std::cout << "x = " << x << "\n";
    }
    
    int main()
    {
      Lazy_Callback lc(print_int, 5);
      
      lc.call();
    }
    

    If the callback can be templated then you can use std::tuple to store your arguments:

    #include <tuple>
    #include <iostream>
    
    
    
    template <typename F, typename ...Args>
    class Lazy_Callback
    {
    public:
      template <typename ...Ts>
      Lazy_Callback(F f, Ts && ...ts)
      : _f(f), _args(ts...)
      { }
      
      void call() const
      {
        return std::apply(_f, _args);
      }
      
    protected:
    private:
      F _f;
      std::tuple<Args...> _args;
    };
    
    
    
    template <typename F, typename ...Ts>
    Lazy_Callback<F, std::decay_t<Ts>...> make_callback(F && f, Ts && ...ts)
    {
      return { std::forward<F>(f), std::forward<Ts>(ts)... };
    }
    
    void print_int(int x)
    {
      std::cout << "x = " << x << "\n";
    }
    
    int main()
    {
      auto lc = make_callback(print_int, 5);
      
      lc.call();
    }