How to store variadic template arguments?
I think you have an XY problem. Why go to all the trouble to store the parameter pack when you could just use a lambda at the callsite? i.e.,
#include <functional>
#include <iostream>
typedef std::function<void()> Action;
void callback(int n, const char* s) {
std::cout << s << ": " << n << '\n';
}
int main() {
Action a{[]{callback(13, "foo");}};
a();
}
This question was from C++11 days. But for those finding it in search results now, some updates:
A std::tuple
member is still the straightforward way to store arguments generally. (A std::bind
solution similar to @jogojapan's will also work if you just want to call a specific function, but not if you want to access the arguments in other ways, or pass the arguments to more than one function, etc.)
In C++14 and later, std::make_index_sequence<N>
or std::index_sequence_for<Pack...>
can replace the helper::gen_seq<N>
tool seen in 0x499602D2's solution:
#include <utility>
template <typename... Ts>
class Action
{
// ...
template <typename... Args, std::size_t... Is>
void func(std::tuple<Args...>& tup, std::index_sequence<Is...>)
{
f(std::get<Is>(tup)...);
}
template <typename... Args>
void func(std::tuple<Args...>& tup)
{
func(tup, std::index_sequence_for<Args...>{});
}
// ...
};
In C++17 and later, std::apply
can be used to take care of unpacking the tuple:
template <typename... Ts>
class Action
{
// ...
void act() {
std::apply(f, args);
}
};
Here's a full C++17 program showing the simplified implementation. I also updated make_action
to avoid reference types in the tuple
, which was always bad for rvalue arguments and fairly risky for lvalue arguments.
To accomplish what you want done here, you'll have to store your template arguments in a tuple:
std::tuple<Ts...> args;
Furthermore, you'll have to change up your constructor a bit. In particular, initializing args
with an std::make_tuple
and also allowing universal references in your parameter list:
template <typename F, typename... Args>
Action(F&& func, Args&&... args)
: f(std::forward<F>(func)),
args(std::forward<Args>(args)...)
{}
Moreover, you would have to set up a sequence generator much like this:
namespace helper
{
template <int... Is>
struct index {};
template <int N, int... Is>
struct gen_seq : gen_seq<N - 1, N - 1, Is...> {};
template <int... Is>
struct gen_seq<0, Is...> : index<Is...> {};
}
And you can implement your method in terms of one taking such a generator:
template <typename... Args, int... Is>
void func(std::tuple<Args...>& tup, helper::index<Is...>)
{
f(std::get<Is>(tup)...);
}
template <typename... Args>
void func(std::tuple<Args...>& tup)
{
func(tup, helper::gen_seq<sizeof...(Args)>{});
}
void act()
{
func(args);
}
And that it! So now your class should look like this:
template <typename... Ts>
class Action
{
private:
std::function<void (Ts...)> f;
std::tuple<Ts...> args;
public:
template <typename F, typename... Args>
Action(F&& func, Args&&... args)
: f(std::forward<F>(func)),
args(std::forward<Args>(args)...)
{}
template <typename... Args, int... Is>
void func(std::tuple<Args...>& tup, helper::index<Is...>)
{
f(std::get<Is>(tup)...);
}
template <typename... Args>
void func(std::tuple<Args...>& tup)
{
func(tup, helper::gen_seq<sizeof...(Args)>{});
}
void act()
{
func(args);
}
};
Here is your full program on Coliru.
Update: Here is a helper method by which specification of the template arguments aren't necessary:
template <typename F, typename... Args>
Action<Args...> make_action(F&& f, Args&&... args)
{
return Action<Args...>(std::forward<F>(f), std::forward<Args>(args)...);
}
int main()
{
auto add = make_action([] (int a, int b) { std::cout << a + b; }, 2, 3);
add.act();
}
And again, here is another demo.
You can use std::bind(f,args...)
for this. It will generate a movable and possibly copyable object that stores a copy of the function object and of each of the arguments for later use:
#include <iostream>
#include <utility>
#include <functional>
template <typename... T>
class Action {
public:
using bind_type = decltype(std::bind(std::declval<std::function<void(T...)>>(),std::declval<T>()...));
template <typename... ConstrT>
Action(std::function<void(T...)> f, ConstrT&&... args)
: bind_(f,std::forward<ConstrT>(args)...)
{ }
void act()
{ bind_(); }
private:
bind_type bind_;
};
int main()
{
Action<int,int> add([](int x, int y)
{ std::cout << (x+y) << std::endl; },
3, 4);
add.act();
return 0;
}
Notice that std::bind
is a function and you need to store, as data member, the result of calling it. The data type of that result is not easy to predict (the Standard does not even specify it precisely), so I use a combination of decltype
and std::declval
to compute that data type at compile time. See the definition of Action::bind_type
above.
Also notice how I used universal references in the templated constructor. This ensures that you can pass arguments that do not match the class template parameters T...
exactly (e.g. you can use rvalue references to some of the T
and you will get them forwarded as-is to the bind
call.)
Final note: If you want to store arguments as references (so that the function you pass can modify, rather than merely use, them), you need to use std::ref
to wrap them in reference objects. Merely passing a T &
will create a copy of the value, not a reference.
Operational code on Coliru