Reputation: 57
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
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 [corrected]BindAction()
method is a void
method but return a function
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
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