Reputation: 88711
I'm trying to store in a std::tuple
a varying number of values, which will later be used as arguments for a call to a function pointer which matches the stored types.
I've created a simplified example showing the problem I'm struggling to solve:
#include <iostream>
#include <tuple>
void f(int a, double b, void* c) {
std::cout << a << ":" << b << ":" << c << std::endl;
}
template <typename ...Args>
struct save_it_for_later {
std::tuple<Args...> params;
void (*func)(Args...);
void delayed_dispatch() {
// How can I "unpack" params to call func?
func(std::get<0>(params), std::get<1>(params), std::get<2>(params));
// But I *really* don't want to write 20 versions of dispatch so I'd rather
// write something like:
func(params...); // Not legal
}
};
int main() {
int a=666;
double b = -1.234;
void *c = NULL;
save_it_for_later<int,double,void*> saved = {
std::tuple<int,double,void*>(a,b,c), f};
saved.delayed_dispatch();
}
Normally for problems involving std::tuple
or variadic templates I'd write another template like template <typename Head, typename ...Tail>
to recursively evaluate all of the types one by one, but I can't see a way of doing that for dispatching a function call.
The real motivation for this is somewhat more complex and it's mostly just a learning exercise anyway. You can assume that I'm handed the tuple by contract from another interface, so can't be changed but that the desire to unpack it into a function call is mine. This rules out using std::bind
as a cheap way to sidestep the underlying problem.
What's a clean way of dispatching the call using the std::tuple
, or an alternative better way of achieving the same net result of storing/forwarding some values and a function pointer until an arbitrary future point?
Upvotes: 293
Views: 115105
Reputation: 188
With C++17 for access to std::apply
(though as others have pointed out, you can make your own in earlier standards), we can make it much more simple and flexible to use. Notice the usage of perfect lambda captures (see this answer and this article).
We can boil it down to a single function returning a lambda (which is our 'saved function and parameters' object):
template <typename Fn, typename ...Args>
auto save_it_for_later(Fn&& f, Args&&... args)
{
return [f = std::tuple<Fn>(std::forward<Fn>(f)),
params = std::tuple<Args...>(std::forward<Args>(args)...)]
() mutable { return std::apply(std::get<0>(f), params); };
}
This allows for very ergonomic usage like
double increment_and_sum(int& i, double j)
{
++i;
return i + j;
}
int int_ref = 2;
auto saved = save_it_for_later(increment_and_sum, int_ref, 2.5);
std::cout << "Sum is " << saved() << " and int_ref is now " << int_ref << '\n';
Prints: Sum is 5.5 and int_ref is now 3
.
You can of course pass a lambda to save_it_for_later
and pass the returned function object around.
Expanded example compiled on godbolt: https://godbolt.org/z/7rG6dfdb8
Upvotes: 2
Reputation: 92
I recently had to handle this problem and found the discussion here useful, but the examples a bit cumbersome especially for my usecase using functors. Hopefully my solution will benefit those that come later.
#include <iostream>
#include <tuple>
void f(int a, double b, void* c) {
std::cout << a << ":" << b << ":" << c << std::endl;
}
template<void func(int, double, void*)>
struct foo {
template<class... args_t>
constexpr void operator()(args_t... args) {
func(args...);
}
template<class... T, size_t... I>
constexpr void operator()(std::tuple<T...>& t, std::index_sequence<I...>) {
this->operator()(std::get<I>(std::forward<std::tuple<T...>>(t))...);
}
template<typename... T>
constexpr void operator()(std::tuple<T...>&& t)
{
this->operator()(t, std::index_sequence_for<T...>{});
}
};
int main() {
foo<&f>()(std::make_tuple(1, 45.2, nullptr));
return 0;
}
Upvotes: 0
Reputation: 41
a lot of answers have been provided but I found them too complicated and not very natural. I did it another way, without using sizeof or counters. I used my own simple structure (ParameterPack) for parameters to access the tail of parameters instead of a tuple. Then, I appended all the parameters from my structure into function parameters, and finnally, when no more parameters were to be unpacked, I run the function. Here is the code in C++11, I agree that there is more code than in others answers, but I found it more understandable.
template <class ...Args>
struct PackParameters;
template <>
struct PackParameters <>
{
PackParameters() = default;
};
template <class T, class ...Args>
struct PackParameters <T, Args...>
{
PackParameters ( T firstElem, Args... args ) : value ( firstElem ),
rest ( args... ) {}
T value;
PackParameters<Args...> rest;
};
template <class ...Args>
struct RunFunction;
template <class T, class ...Args>
struct RunFunction<T, Args...>
{
template <class Function>
static void Run ( Function f, const PackParameters<T, Args...>& args );
template <class Function, class... AccumulatedArgs>
static void RunChild (
Function f,
const PackParameters<T, Args...>& remainingParams,
AccumulatedArgs... args
);
};
template <class T, class ...Args>
template <class Function>
void RunFunction<T, Args...>::Run (
Function f,
const PackParameters<T, Args...>& remainingParams
)
{
RunFunction<Args...>::template RunChild ( f, remainingParams.rest,
remainingParams.value );
}
template <class T, class ...Args>
template<class Function, class ...AccumulatedArgs>
void RunFunction<T, Args...>::RunChild ( Function f,
const PackParameters<T, Args...>& remainingParams,
AccumulatedArgs... args )
{
RunFunction<Args...>:: template RunChild ( f, remainingParams.rest,
args..., remainingParams.value );
}
template <>
struct RunFunction<>
{
template <class Function, class... AccumulatedArgs>
static void RunChild ( Function f, PackParameters<>, AccumulatedArgs... args )
{
f ( args... );
}
template <class Function>
static void Run ( Function f, PackParameters<> )
{
f ();
}
};
struct Toto
{
std::string k = "I am toto";
};
void f ( int i, Toto t, float b, std::string introMessage )
{
float res = i * b;
std::cerr << introMessage << " " << res << std::endl;
std::cerr << "Toto " << t.k << std::endl;
}
int main(){
Toto t;
PackParameters<int, Toto, float, std::string> pack ( 3, t, 4.0, " 3 * 4 =" );
RunFunction<int, Toto, float, std::string>::Run ( f, pack );
return 0;
}
Upvotes: 0
Reputation: 6326
This is a complete compilable version of Johannes' solution to awoodland's question, in the hope it may be useful to somebody. This was tested with a snapshot of g++ 4.7 on Debian squeeze.
###################
johannes.cc
###################
#include <tuple>
#include <iostream>
using std::cout;
using std::endl;
template<int ...> struct seq {};
template<int N, int ...S> struct gens : gens<N-1, N-1, S...> {};
template<int ...S> struct gens<0, S...>{ typedef seq<S...> type; };
double foo(int x, float y, double z)
{
return x + y + z;
}
template <typename ...Args>
struct save_it_for_later
{
std::tuple<Args...> params;
double (*func)(Args...);
double delayed_dispatch()
{
return callFunc(typename gens<sizeof...(Args)>::type());
}
template<int ...S>
double callFunc(seq<S...>)
{
return func(std::get<S>(params) ...);
}
};
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wunused-variable"
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
int main(void)
{
gens<10> g;
gens<10>::type s;
std::tuple<int, float, double> t = std::make_tuple(1, 1.2, 5);
save_it_for_later<int,float, double> saved = {t, foo};
cout << saved.delayed_dispatch() << endl;
}
#pragma GCC diagnostic pop
One can use the following SConstruct file
#####################
SConstruct
#####################
#!/usr/bin/python
env = Environment(CXX="g++-4.7", CXXFLAGS="-Wall -Werror -g -O3 -std=c++11")
env.Program(target="johannes", source=["johannes.cc"])
On my machine, this gives
g++-4.7 -o johannes.o -c -Wall -Werror -g -O3 -std=c++11 johannes.cc
g++-4.7 -o johannes johannes.o
Upvotes: 46
Reputation: 275200
c++14 solution. First, some utility boilerplate:
template<std::size_t...Is>
auto index_over(std::index_sequence<Is...>){
return [](auto&&f)->decltype(auto){
return decltype(f)(f)( std::integral_constant<std::size_t, Is>{}... );
};
}
template<std::size_t N>
auto index_upto(std::integral_constant<std::size_t, N> ={}){
return index_over( std::make_index_sequence<N>{} );
}
These let you call a lambda with a series of compile-time integers.
void delayed_dispatch() {
auto indexer = index_upto<sizeof...(Args)>();
indexer([&](auto...Is){
func(std::get<Is>(params)...);
});
}
and we are done.
index_upto
and index_over
let you work with parameter packs without having to generate a new external overloads.
Of course, in c++17 you just
void delayed_dispatch() {
std::apply( func, params );
}
Now, if we like that, in c++14 we can write:
namespace notstd {
template<class T>
constexpr auto tuple_size_v = std::tuple_size<T>::value;
template<class F, class Tuple>
decltype(auto) apply( F&& f, Tuple&& tup ) {
auto indexer = index_upto<
tuple_size_v<std::remove_reference_t<Tuple>>
>();
return indexer(
[&](auto...Is)->decltype(auto) {
return std::forward<F>(f)(
std::get<Is>(std::forward<Tuple>(tup))...
);
}
);
}
}
relatively easily and get the cleaner c++17 syntax ready to ship.
void delayed_dispatch() {
notstd::apply( func, params );
}
just replace notstd
with std
when your compiler upgrades and bob is your uncle.
Upvotes: 9
Reputation: 15468
The C++17 solution is simply to use std::apply
:
auto f = [](int a, double b, std::string c) { std::cout<<a<<" "<<b<<" "<<c<< std::endl; };
auto params = std::make_tuple(1,2.0,"Hello");
std::apply(f, params);
Just felt that should be stated once in an answer in this thread (after it already appeared in one of the comments).
The basic C++14 solution is still missing in this thread. EDIT: No, it's actually there in the answer of Walter.
This function is given:
void f(int a, double b, void* c)
{
std::cout << a << ":" << b << ":" << c << std::endl;
}
Call it with the following snippet:
template<typename Function, typename Tuple, size_t ... I>
auto call(Function f, Tuple t, std::index_sequence<I ...>)
{
return f(std::get<I>(t) ...);
}
template<typename Function, typename Tuple>
auto call(Function f, Tuple t)
{
static constexpr auto size = std::tuple_size<Tuple>::value;
return call(f, t, std::make_index_sequence<size>{});
}
Example:
int main()
{
std::tuple<int, double, int*> t;
//or std::array<int, 3> t;
//or std::pair<int, double> t;
call(f, t);
}
Upvotes: 126
Reputation: 187
My variation of the solution from Johannes using the C++14 std::index_sequence (and function return type as template parameter RetT):
template <typename RetT, typename ...Args>
struct save_it_for_later
{
RetT (*func)(Args...);
std::tuple<Args...> params;
save_it_for_later(RetT (*f)(Args...), std::tuple<Args...> par) : func { f }, params { par } {}
RetT delayed_dispatch()
{
return callFunc(std::index_sequence_for<Args...>{});
}
template<std::size_t... Is>
RetT callFunc(std::index_sequence<Is...>)
{
return func(std::get<Is>(params) ...);
}
};
double foo(int x, float y, double z)
{
return x + y + z;
}
int testTuple(void)
{
std::tuple<int, float, double> t = std::make_tuple(1, 1.2, 5);
save_it_for_later<double, int, float, double> saved (&foo, t);
cout << saved.delayed_dispatch() << endl;
return 0;
}
Upvotes: 2
Reputation: 45414
Here is a C++14 solution.
template <typename ...Args>
struct save_it_for_later
{
std::tuple<Args...> params;
void (*func)(Args...);
template<std::size_t ...I>
void call_func(std::index_sequence<I...>)
{ func(std::get<I>(params)...); }
void delayed_dispatch()
{ call_func(std::index_sequence_for<Args...>{}); }
};
This still needs one helper function (call_func
). Since this is a common idiom, perhaps the standard should support it directly as std::call
with possible implementation
// helper class
template<typename R, template<typename...> class Params, typename... Args, std::size_t... I>
R call_helper(std::function<R(Args...)> const&func, Params<Args...> const¶ms, std::index_sequence<I...>)
{ return func(std::get<I>(params)...); }
// "return func(params...)"
template<typename R, template<typename...> class Params, typename... Args>
R call(std::function<R(Args...)> const&func, Params<Args...> const¶ms)
{ return call_helper(func,params,std::index_sequence_for<Args...>{}); }
Then our delayed dispatch becomes
template <typename ...Args>
struct save_it_for_later
{
std::tuple<Args...> params;
std::function<void(Args...)> func;
void delayed_dispatch()
{ std::call(func,params); }
};
Upvotes: 43
Reputation: 88711
Thinking about the problem some more based on the answer given I've found another way of solving the same problem:
template <int N, int M, typename D>
struct call_or_recurse;
template <typename ...Types>
struct dispatcher {
template <typename F, typename ...Args>
static void impl(F f, const std::tuple<Types...>& params, Args... args) {
call_or_recurse<sizeof...(Args), sizeof...(Types), dispatcher<Types...> >::call(f, params, args...);
}
};
template <int N, int M, typename D>
struct call_or_recurse {
// recurse again
template <typename F, typename T, typename ...Args>
static void call(F f, const T& t, Args... args) {
D::template impl(f, t, std::get<M-(N+1)>(t), args...);
}
};
template <int N, typename D>
struct call_or_recurse<N,N,D> {
// do the call
template <typename F, typename T, typename ...Args>
static void call(F f, const T&, Args... args) {
f(args...);
}
};
Which requires changing the implementation of delayed_dispatch()
to:
void delayed_dispatch() {
dispatcher<Args...>::impl(func, params);
}
This works by recursively converting the std::tuple
into a parameter pack in its own right. call_or_recurse
is needed as a specialization to terminate the recursion with the real call, which just unpacks the completed parameter pack.
I'm not sure this is in anyway a "better" solution, but it's another way of thinking about and solving it.
As another alternative solution you can use enable_if
, to form something arguably simpler than my previous solution:
#include <iostream>
#include <functional>
#include <tuple>
void f(int a, double b, void* c) {
std::cout << a << ":" << b << ":" << c << std::endl;
}
template <typename ...Args>
struct save_it_for_later {
std::tuple<Args...> params;
void (*func)(Args...);
template <typename ...Actual>
typename std::enable_if<sizeof...(Actual) != sizeof...(Args)>::type
delayed_dispatch(Actual&& ...a) {
delayed_dispatch(std::forward<Actual>(a)..., std::get<sizeof...(Actual)>(params));
}
void delayed_dispatch(Args ...args) {
func(args...);
}
};
int main() {
int a=666;
double b = -1.234;
void *c = NULL;
save_it_for_later<int,double,void*> saved = {
std::tuple<int,double,void*>(a,b,c), f};
saved.delayed_dispatch();
}
The first overload just takes one more argument from the tuple and puts it into a parameter pack. The second overload takes a matching parameter pack and then makes the real call, with the first overload being disabled in the one and only case where the second would be viable.
Upvotes: 3
Reputation: 15154
This is a bit complicated to achieve (even though it is possible). I advise you to use a library where this is already implemented, namely Boost.Fusion (the invoke function). As a bonus, Boost Fusion works with C++03 compilers as well.
Upvotes: 18
Reputation: 506837
You need to build a parameter pack of numbers and unpack them
template<int ...>
struct seq { };
template<int N, int ...S>
struct gens : gens<N-1, N-1, S...> { };
template<int ...S>
struct gens<0, S...> {
typedef seq<S...> type;
};
// ...
void delayed_dispatch() {
callFunc(typename gens<sizeof...(Args)>::type());
}
template<int ...S>
void callFunc(seq<S...>) {
func(std::get<S>(params) ...);
}
// ...
Upvotes: 293