如何通过前进或移动来捕获参数包?

时间:2016-05-29 14:34:03

标签: c++ c++17

说我有这个功能:

template <typename ...A>
void test(A&& ...a)
{
  [=]()
  {
  };
}

是要转发到lambda还是只是按值复制的参数包?我担心,因为我必须通常明确move()forward(),因为a...是左值。是一个转发/移动它们的元组中介吗?如果是这样,是否有一种简单的方法可以将元组解压缩到参数包中,而不使用索引技巧?

4 个答案:

答案 0 :(得分:7)

一种方法是在Haskell意义上编写一个仿函数。那是一个变异的,它不是Haskell。

编写签名(Ts...)->( ((Ts...)->X) -> X )的功能。即一个带包的函数,并返回一个函数。返回的函数可以采用一个函数来获取该包并对其进行评估。

template<class...Ts>
auto make_functor(Ts&&...ts); // TODO

一旦我们能够轻松解决您的问题。

template<class ...A>
auto test(A&& ...a) {
  return [unpack_a=make_functor(std::forward<A>(a)...)]() mutable
  {
    return unpack_a([&](auto&&...a){
      // here you have access to a...
      return sizeof...(a);
    });
  };
}

test接受一个包,并返回一个返回该包大小的函数(好吧,对包执行任何操作)。

make_functor并不容易:基本上,我们编写一个手动lambda,将args存储在元组中,然后在operator()中解析索引技巧。

实际上,我们在手动伪lambda类中执行包存储和解包一次,然后再重新使用它。

第二个想法,最好写一个延迟的应用程序,它接受一个元组,存储它,然后再使用std::apply

template<class...Ts>
auto delayed_apply(std::tuple<Ts...> tup){
  return [tup=std::move(tup)](auto&&f)->decltype(auto) mutable{
    return std::experimental::apply(decltype(f)(f), std::move(tup));
  };
}

让参数的值/参考不会丢失!

template<class ...A>
auto test(A&& ...a) {
  return [unpack_a=delayed_apply(std::forward_as_tuple(std::forward<A>(a)...))]() mutable
  {
    return unpack_a([&](auto&&...a){
      // here you have access to a...
      return sizeof...(a);
    });
  };
}

这确实需要std::experimental::apply

如果你想存储 rvalues并将左值作为参考:

unpack_a=delayed_apply(std::tuple<A...>(std::forward<A>(a)...))

如果要存储l和r值:

unpack_a=delayed_apply(std::make_tuple(std::forward<A>(a)...))

正如您所看到的,这种方法可以提供很多控制。

如果您需要std::experimental::apply,则有参考实施:比我在智能手机上写的任何内容都要好。

请注意,make_functor可以用delayed_apply来编写,但相反的是......不是真的。

如果您感到困惑,unpack_a接受一个lambda并将用于创建unpack_a的元组解包到其中。基本上我们存储一个整个包的对象,然后当我们需要它在lambda体内时将其解压缩。

如果你希望拆包工作不止一次,那么可能需要处理const和非const以及甚至rvalue重载的更长的delayed_apply&#34; sometimss和&#34;只有一次&#34;其他时间。它必须返回一个类,而不是lambda。烦人。我认为,使示例代码工作仍然没有编译。

Fortunetally这种事情是写一次,使用很多。

答案 1 :(得分:6)

使用std::bind可以完成的少数有用的事情之一。捕获由bind执行,捕获的值作为参数传递给无捕获的通用lambda:

template <typename... A>
auto test(A&&... a)
{
    auto f = [](auto&&... a)
    {
        // use a...
    };
    return std::bind(f, std::forward<A>(a)...);
}

Live demo

上述内容适用于Clang,但此GCC似乎存在虚假volatile限定符的问题。

我们可以在没有bind的情况下通过在调用std::apply(C ++ 17)的第二个lambda中捕获tuple来将元组解压缩到第一个lambda的参数列表中来实现:< / p>

template <typename... A>
auto test(A&&... a)
{
    auto f = [](auto&&... a)
    {
        // use a...
    };
    return [f, tup = std::make_tuple(std::forward<A>(a)...)]() mutable { std::apply(f, tup); };
}

Live demo

与Clang和GCC合作; apply是使用您想要避免的索引技巧实现的,但您不会接触它。 mutable表示第二个lambda的调用运算符是非const的,因此元组元素最终不会获得const限定。

答案 2 :(得分:3)

首先使用完美转发捕获元组中的参数:

template <typename ...A>
void test(A&& ...a)
{
  [tup= std::tuple<A...>(std::forward<A>(a)...)]()
  {
       //tup should contain the forwarded elements
  };
}

然后使用此答案:https://stackoverflow.com/a/7858971/835629在稍后的函数调用中解压缩元组。

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


template<typename F, typename T, int ...S>
void unpackTupleToFunction_utils(F func, const T &tup, seq<S...>) {
 func(std::get<S>(tup) ...);
}

template<typename F, typename ...Args, int ...S>
void unpackTupleToFunction(F func, const std::tuple<Args...> &tup) {
 unpackTupleToFunction_utils(func, tup, typename gens<sizeof...(Args)>::type());
}

最后解压lambda中的元组以便用它调用函数:

template <typename ...Args>
void test(Args&& ...a) {
  auto lambda = [tup= std::tuple<Args...>(std::forward<Args>(a)...)]()
  {
    unpackTupleToFunction(f, tup);
  };

  lambda();
  lambda();
  lambda();
}

PS:遗憾的是[a = (std::forward<Args>(a)...)](){};之类的东西没有编译。

答案 3 :(得分:2)

为什么它不会通过价值?转发仅限于顶级功能。

让我们假设你传递了intstd::string&float&&,所以你的功能看起来像

void test(int,string&,float&&)
{
  [=]()
  {
  };
}

从那里,匿名lambda将复制 intstring&float&&的值。引用的副本仍然是副本。 您可以使用tuple再次打包参数并将其解压缩到lambda中。

如何在lamda中使用元组?

  1. 像我们通常使用variadic模板一样使用递归
  2. 找到std::apply的一些非标准实现,并使用元组作为anotehr函数的参数