“解包”一个元组来调用一个匹配的函数指针

我试图存储在一个std::tuple不同数量的值,这将被用作参数调用一个函数指针匹配存储的types的参数。

我创build了一个简化的例子来展示我正在努力解决的问题:

 #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(); } 

通常对于涉及std::tuple或variadic模板的问题,我会编写另一个模板,比如template <typename Head, typename ...Tail>recursion地逐一评估所有types,但是我看不到一种方法用于调度函数调用。

真正的动机是比较复杂的,而且大多只是一个学习练习。 你可以假设我是从另一个接口通过契约交给元组的,所以不能改变,但是把它解包到一个函数调用中的愿望是我的。 这排除了使用std::bind作为一个便宜的方式来回避潜在的问题。

什么是使用std::tuple调度调用的干净的方法,或者一个更好的方法来获得相同的净结果存储/转发一些值和函数指针,直到任意的未来点?

您需要构build一个数字参数包并将其解压缩

 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) ...); } // ... 

这是Johanne解决awoodland问题的完整的可编译版本,希望对某人有用。 这在Debian挤压的g ++ 4.7快照上进行了testing。

 ################### 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 

可以使用下面的SConstruct文件

 ##################### 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"]) 

在我的机器上,这给了

 g++-4.7 -o johannes.o -c -Wall -Werror -g -O3 -std=c++11 johannes.cc g++-4.7 -o johannes johannes.o 

这是一个C ++ 14解决scheme。

 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...>{}); } }; 

这仍然需要一个辅助函数( call_func )。 由于这是一个常见的习惯用语,所以标准可能应该直接以std::call支持它

 // 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&params, 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&params) { return call_helper(func,params,std::index_sequence_for<Args...>{}); } 

然后我们延迟派遣成为

 template <typename ...Args> struct save_it_for_later { std::tuple<Args...> params; std::function<void(Args...)> func; void delayed_dispatch() { std::call(func,params); } }; 

C ++ 17解决scheme简单地使用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); 

刚刚觉得应该在这个线程的答案中说一次(之后它已经出现在其中一个评论)。


这个线程中仍然缺less基本的C ++ 14解决scheme。 编辑:不,它实际上在沃尔特的答案中。

这个function是给出的:

 void f(int a, double b, void* c) { std::cout << a << ":" << b << ":" << c << std::endl; } 

用下面的代码调用它:

 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>{}); } 

例:

 int main() { std::tuple<int, double, int*> t; //or std::array<int, 3> t; //or std::pair<int, double> t; call(f, t); } 

DEMO

这实现起来有点复杂(尽pipe这是可能的)。 我build议你使用已经实现的库,即Boost.Fusion ( invoke函数)。 另外,Boost Fusion也可以和C ++ 03编译器一起使用。

思考问题的一些更多的基础上给出的答案我已经find了解决同样的问题的另一种方式:

 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...); } }; 

这需要将delayed_dispatch()的实现更改为:

  void delayed_dispatch() { dispatcher<Args...>::impl(func, params); } 

这是通过recursion地将std::tuple转换成一个参数包在它自己的权利。 call_or_recurse是专门用来终止真正的调用recursion,只是解压完成的参数包。

我不确定这是否是一个“更好”的解决scheme,但它是另一种思考和解决问题的方式。


作为另一种替代解决scheme,您可以使用enable_if来形成比我以前的解决scheme更简单的事情:

 #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(); } 

第一个重载只需要从元组中取出一个参数并将其放入参数包中。 第二次重载需要一个匹配的参数包,然后进行真正的呼叫,在第一次重载被禁用的情况下,唯一的情况是第二次可行。

C ++ 14解决scheme。 一,一些工具样板:

 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>{} ); } 

这些让你用一系列的编译时间整数来调用一个lambdaexpression式。

 void delayed_dispatch() { auto indexer = index_upto<sizeof...(Args)>(); indexer([&](auto...Is){ func(std::get<Is>(params)...); }); } 

我们完成了。

index_uptoindex_over让您使用参数包,而不必生成新的外部重载。

当然,在C ++ 17中,你只是

 void delayed_dispatch() { std::apply( func, params ); } 

现在,如果我们喜欢这样,在C ++ 14中,我们可以写:

 namespace notstd { template<class F, class Tuple> decltype(auto) apply( F&& f, Tuple&& tup ) { auto indexer = index_upto< std::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))... ); } ); } } 

相对容易,并获得清洁的C ++ 17语法准备出货。

 void delayed_dispatch() { notstd::apply( func, params ); } 

当你的编译器升级,bob就是你的叔叔的时候,用std代替notstd

Johannes使用C ++ 14 std :: index_sequence(和函数返回types作为模板参数RetT)的解决scheme的变体:

 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; }