Torayl
Torayl

Reputation: 57

Pass a template variadic function and its arguments to a function

I would like to be able to pass a function taking any number of arguments, plus the matching arguments, as arguments to a function. This is what I've tried so far, I'm lost as I don't quite get a grasp at how parameter-packs work.

template <class T, typename... Args>
static void BindAction(const std::string& name, T* owner, const std::function<void(T*, Args...)>& func, Args... args)
{
    for (InputAction& action : m_actions)
        if (action.m_name == name)
            action.BindFunction(std::bind(func, owner, args...));
}

And I test it with these:

void Test(float f, std::string s, int i);

BindAction<Simulation>("Test", this, &Simulation::Test, 5.f, "a string", 2);

The error is simply that no instance of function template "BindAction()" matches the argument list.

Please don't dumb down the problem by telling me to call std::bind() and then passing the resulting function as argument. I really want the call to be that way. And if this is not possible, too bad but only then will I check for other solutions.

Upvotes: 0

Views: 1898

Answers (2)

max66
max66

Reputation: 66230

The are some problems in your code.

In no particular order

(1) BindAction() receive a std::function<void(T*, Args...)> const &, for some T e Args... types to be deduced, but you pass to it a &Simulation::Test, so a pointer to a (static?) method of a class/struct, that isn't a std::function.

You passing a pointer that can be converted to a std::function but isn't a std::function.

A sort of egg and chicken problem: the compiler can't convert the pointer to a std::function because doesn't know the Args... types and can't deduce the Args... types because doesn't receive a std::function

(2) you needs two different variadic lists of argument types: a Args1... list for the argument of the std::function and a second Args2... list for the arguments (args...) received.

See your call example

void Test(float f, std::string s, int i); 

BindAction<Simulation>("Test", this, &Simulation::Test, 5.f, "a string", 2);

The Test() method receive a float, a std::string and a int; but BindAction() receive a float, a char const [9] and a int.

If you use a single Args... list, the compiler can't deduce the types because has two different list of types.

(3) not sure but... your BindAction() is a static method but uses (if I understand correctly) a member of the object (m_action)

(4) your BindAction() method is a void method but return a function [corrected]

Suggestions:

(a) receive the callable as a template type itself, without deducing the types of the arguments

(b) receive the list of argument as universal references and use template forwarding

To show a simplified example, something as follows

#include <functional>
#include <iostream>

template <typename F, typename ... As>
static auto BindAction (F const & func, As && ... args)
 { return std::bind(func, std::forward<As>(args)...); }

void Test(float f, std::string s, int i)
 { std::cout << "f[" << f << "], s[" << s << "], i[" << i << "]" << std::endl; } 

int main ()
 { 
   auto ba = BindAction(Test, 5.f, "a string", 2);

   std::cout << "post BindAction(), pre ba()\n";

   ba();
 }

Upvotes: 1

Ryan Haining
Ryan Haining

Reputation: 36882

You're binding all of the arguments, so your map will actually be full of std::function<void()>, it won't match the incoming function. You can couple lambdas with std::invoke for a more predictable result, and simply use a template to deduce the type of the incoming callable:

#include <functional>
#include <iostream>

class Actions {
 public:
  template <typename F, typename... Args>
  void BindAction(const std::string& name, F f, Args... args) {
    actions_.emplace(
      name,
      [f, args...] () mutable { return std::invoke(f, args...); }
    );
  }

  void call(const std::string& name) {
    actions_[name]();
  }

 private:
  std::unordered_map<std::string, std::function<void()>> actions_;
};

And a simple use example:

class Cls {
  public:
    void do_func(int i) {
      std::cout << "Cls::do_func(" << i << ")\n";
    }
};

int main() {
  Cls c;
  Actions actions;
  actions.BindAction("test", &Cls::do_func, c, 1);
  actions.call("test");
}

Upvotes: 2

Related Questions