我正在尝试在std::tuple中存储各种数量的值,这些值稍后将用作与存储类型匹配的函数指针的调用的参数。显示我正在努力解决的问题:

#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或可变参数模板的问题,我会编写另一个模板,例如template <typename Head, typename ...Tail>来递归地评估所有类型

一步一步地来做,但是我看不到这样做的方法。您可以假定我是通过另一个接口通过契约传递元组的,因此不能更改,但是将其打包成函数调用的愿望是我的。这就排除了使用std::bind作为避开潜在问题的廉价方法。一些值和函数指针,直到任意将来的点?

评论

为什么不能只使用自动保存= std :: bind(f,a,b,c); ...然后再调用save()?

并非总是我的界面来控制。我通过合同从其他人那里收到一个元组,并希望随后对其进行处理。

#1 楼

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


只是觉得应该在该线程的答案中说明一次(在它已经出现在注释)。


该线程中仍然缺少基本的C ++ 14解决方案。编辑:不,实际上是在Walter的答案中。

此函数被给定:

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


演示

评论


我无法使该演示与智能指针一起使用-这怎么了? http://coliru.stacked-crooked.com/a/8ea8bcc878efc3cb

–大量
17年9月7日在17:01

@Xeverous:您想在这里得到类似的东西吗?

– davidhigh
17年9月7日在17:57



谢谢,我有两个问题:1.为什么我不能直接传递std :: make_unique?是否需要具体的功能实例? 2.为什么要std :: move(ts)...如果我们可以将[](auto ... ts)更改为[](auto && ... ts)?

–大量
17年9月7日在18:01



@Xeverous:1.在签名中不起作用:您的std :: make_unique需要一个元组,并且只有通过另一个对std :: make_tuple的调用才能从解压缩的元组中创建一个元组。这就是我在lambda中所做的事情(尽管它是高度冗余的,因为您也可以将元组简单地复制到唯一的指针中,而无需任何调用)。

– davidhigh
17年9月7日在18:09



现在应该是答案。

–万神殿
19年6月27日在22:27

#2 楼

您需要构建数字参数包并将其解包

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


评论


哇,我不知道可以像这样使用拆包运算符,太好了!

–卢·图拉(Luc Touraille)
11-10-22在13:09

Johannes,我知道距您发布此文档已有2年多了,但我一直在努力的一件事是struct gens泛型定义(该继承自同一个扩展派生的继承)。我看到它最终会达到0的专业化水平。如果心情适合您并且您有空闲的周期,如果您可以对此进行扩展,以及如何将其用于此,我将永远感激不已。我希望我可以投票一百次。使用此代码中的切线,我获得了更多的乐趣。谢谢。

– WhozCraig
13年11月18日在9:38



@WhozCraig:它所做的是生成类型seq <0,1,.. N-1>。工作原理:gens <5>:gens <4,4>:gens <3,3,4>:gens <2,2,3,4>:gens <1,2,3,4>:gens <0,0,1,2,3,4>。最后一种类型是特殊的,创建seq <0,1,2,3,4>。相当聪明的把戏。

–心灵病毒
2014年4月25日15:38



@NirFriedman:当然,只需将gens的非专业版本替换为:template struct gens {typedef typename gens :: type type; };

– marton78
2015年4月16日14:11



值得在沃尔特(Walter)的回答和评论上回音:人们不再需要发明自己的轮子了。生成序列是如此普遍,以至于它在C ++ 14中被标准化为std :: integer_sequence 及其对std :: size_t,std :: index_sequence 的专门化-以及它们相关的帮助函数std: :make_in(teger | dex)_sequence <>()和std :: index_sequence_for ()。在C ++ 17中,库中还集成了许多其他优点-特别包括std :: apply和std :: make_from_tuple,它们可以处理解包和调用位

– underscore_d
16-10-9在14:22



#3 楼

这是约翰内斯对awoodland问题的解决方案的完整可编译版本,希望对某些人有用。已使用Debian squeeze上的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


评论


为什么需要变量s和g?

–shoosh
2015年1月7日18:00

@s我猜他们是不需要的。我忘记了为什么要添加这些内容;已经快三年了。但是我想证明实例化是可行的。

– Faheem Mitha
15年1月7日在18:09

#4 楼

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


这仍然需要一个辅助函数(call_func)。由于这是一个常见的习惯用法,因此也许标准应该将其作为std::call直接支持并可能实现。

评论


为std :: call的(建议的)实现提议。 C ++ 14的integer_sequence和index_sequence帮助程序类型的混乱动物园解释如下:en.cppreference.com/w/cpp/utility/integer_sequence注意std :: make_index_sequence(Args ...)的明显缺失,这就是Walter的原因强制使用笨拙的语法std :: index_sequence_for {}。

– Quuxplusone
2014年1月26日21:02

显然自3/2016起以std :: apply(func,tup)的形式投票支持C ++ 17:en.cppreference.com/w/cpp/utility/apply

– ddevienne
16年5月9日在15:28

#5 楼

实现起来有点复杂(即使可能)。我建议您使用已经实现该功能的库,即Boost.Fusion(invoke函数)。另外,Boost Fusion还可以与C ++ 03编译器一起使用。

#6 楼

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


这些让您可以调用带有一系列编译时整数的lambda。 >
我们就完成了。

index_uptoindex_over使您可以使用参数包而不必产生新的外部重载。

当然,在c + +17您只需

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


现在,如果愿意,在c ++ 14中我们可以这样写:

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


比较容易,并准备好发布更清洁的c ++ 17语法。 。

评论


std ::应用<-音乐到我的耳朵

– Flexo♦
17年8月1日在20:22

@Flexo仅比index_upto短一点,但灵活性较差。 ;)尝试分别使用index_upto和std :: apply向后调用参数的func。诚然,到底谁想要从元组向后调用函数。

– ak牛-亚当·内夫罗蒙特
17年8月1日在20:24



次要点:std :: tuple_size_v是C ++ 17,因此对于C ++ 14解决方案,必须将其替换为类型名std :: tuple_size :: value

–basteln
18年2月23日在7:50

@basteln我希望值不是一种类型。但是无论如何都解决了。

– ak牛-亚当·内夫罗蒙特
18-2-23在14:14



@Yakk不,它是sizeof ...(类型)。我喜欢没有类型名称的解决方案。

–basteln
18-2-28在9:47

#7 楼

根据给出的答案对问题进行更多思考,我找到了解决同一问题的另一种方法:

br />
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()递归地转换为参数包,可以正常工作。需要使用std::tuple作为专业化来终止实际调用的递归,这将解压缩完整的参数包。思考和解决它。


作为另一种替代解决方案,您可以使用call_or_recurse,形成比我以前的解决方案更简单的东西: >
第一个重载只是从元组中再获取一个参数,并将其放入参数包中。第二个重载采用匹配的参数包,然后进行真正的调用,第一个重载仅在第二个可行的情况下被禁用。

评论


不久前,我从事着与此非常相似的工作。如果有时间,我会再去看看,看看它与当前答案的比较。

– Michael Price
2011-10-22 18:45

@MichaelPrice-纯粹从学习的角度来看,我很感兴趣的是看到任何其他解决方案都不会归结为一些令人讨厌的技巧,它们会破坏堆栈指针(或类似地调用约定的特技)。

– Flexo♦
2011-10-22 19:45

#8 楼

我使用C ++ 14 std :: index_sequence(和函数返回类型作为模板参数RetT)从Johannes解决方案的变体:


评论


所有这些解决方案都可以解决最初的问题,但是老实说,从简单性和可维护性方面来说,这些模板材料不是朝错误的方向发展吗?

– x y
2015年10月16日14:30在

我认为使用C ++ 11和14可以使模板变得更好,更易理解。几年前,当我研究引擎盖下的模板的增强功能时,我真的很沮丧。我同意,开发好的模板要比仅使用模板困难得多。

–schwart
15-10-26在12:01

@xy首先,就模板复杂度而言,这没什么。其次,大多数帮助程序模板都是初始投资,可节省大量时间,以便稍后实例化时使用。最后,什么,您宁愿没有能力执行允许您执行的模板?您不能使用它,也不能留下似乎无关紧要的注释,这些注释似乎在使其他程序员感到吃惊。

– underscore_d
16 Jan 30'10:30