C++ “解包”一个元组以调用匹配的函数指针
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/7858817/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
"unpacking" a tuple to call a matching function pointer
提问by Flexo
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.
我正在尝试存储std::tuple
不同数量的值,这些值稍后将用作调用与存储类型匹配的函数指针的参数。
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.
通常对于涉及std::tuple
或可变参数模板的问题,我会编写另一个模板,例如template <typename Head, typename ...Tail>
一个一个地递归评估所有类型,但我看不到一种方法来调度函数调用。
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.
这样做的真正动机有些复杂,而且它主要只是一种学习练习。你可以假设我从另一个接口通过契约传递了元组,所以不能改变,但是将它解包到函数调用中的愿望是我的。这排除了使用std::bind
作为回避潜在问题的廉价方式的可能性。
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?
使用 调度调用的干净方法是什么std::tuple
,或者实现存储/转发某些值和函数指针直到任意未来点的相同净结果的替代更好方法?
回答by Johannes Schaub - litb
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) ...);
}
// ...
回答by davidhigh
The C++17 solution is simply to use std::apply
:
C++17 解决方案只是使用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.
此线程中仍然缺少基本的 C++14 解决方案。编辑:不,它实际上在沃尔特的回答中。
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);
}
回答by Faheem Mitha
This is a complete compilable version of Johannes' solutionto 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对 awoodland 问题的解决方案的完整可编译版本,希望对某些人有用。这是在 Debian 挤压上用 g++ 4.7 的快照测试的。
###################
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 文件
#####################
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
回答by Walter
Here is a C++14 solution.
这是一个 C++14 解决方案。
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
这仍然需要一个辅助函数 ( 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¶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); }
};
回答by Karel Petranek
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 invokefunction). As a bonus, Boost Fusion works with C++03 compilers as well.
这实现起来有点复杂(即使这是可能的)。我建议你使用一个已经实现的库,即Boost.Fusion(调用函数)。作为奖励,Boost Fusion 也适用于 C++03 编译器。
回答by Yakk - Adam Nevraumont
c++14solution. First, some utility boilerplate:
C++14解决方案。首先,一些实用程序样板:
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.
这些使您可以使用一系列编译时整数调用 lambda。
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.
index_upto
并index_over
让你与参数包工作,而无需产生新的外部重载。
Of course, in c++17you just
当然,在c++17 中你只是
void delayed_dispatch() {
std::apply( func, params );
}
Now, if we like that, in c++14we can write:
现在,如果我们喜欢这样,在c++14 中我们可以这样写:
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++17syntax ready to ship.
相对容易,并准备好发布更清晰的c++17语法。
void delayed_dispatch() {
notstd::apply( func, params );
}
just replace notstd
with std
when your compiler upgrades and bob is your uncle.
只需在您的编译器升级时替换notstd
为std
,而 bob 是您的叔叔。
回答by Flexo
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:
这需要将实现更改delayed_dispatch()
为:
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.
这是通过递归地将std::tuple
其本身转换为参数包来实现的。call_or_recurse
需要作为特殊化来终止具有真正调用的递归,它只是解包完成的参数包。
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:
作为另一种替代解决方案,您可以使用enable_if
, 来形成可以说比我以前的解决方案更简单的东西:
#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.
第一个重载只是从元组中再获取一个参数并将其放入参数包中。第二个重载采用匹配的参数包,然后进行真正的调用,第一个重载在第二个可行的唯一情况下被禁用。
回答by schwart
My variation of the solution from Johannes using the C++14 std::index_sequence (and function return type as template parameter RetT):
我使用 C++14 std::index_sequence(和函数返回类型作为模板参数 RetT)对 Johannes 的解决方案的变体:
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;
}