如何通过转发或移动捕获参数包?

how to capture a parameter pack by forward or move?

假设我有这个功能:

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

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

一种方法是在Haskell 意义上写一个仿函数。嗯,一个可变的,不是很 Haskell。

写一个签名函数(Ts...)->( ((Ts...)->X) -> X )。即一个接受包的函数,returns 是一个函数。 returned 函数可以采用采用该包的函数并对其求值。

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 需要一个包,return 是一个函数,return 是那个包的大小(好吧,对包做任何事情)。

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

实际上,我们在手动伪 lambda class 中进行一次包存储和解包 ,然后稍后再使用它。

再三考虑,最好编写一个延迟应用程序,它接受一个元组,存储它,然后 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));
  };
}

让参数value/refness不丢失!

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

如果您想存储 右值并保留左值作为引用:

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 主体中。

更长的 delayed_apply 可以处理 const 和非 const,如果您希望解包工作 "more than once" 有时 "only once" 有时甚至可能需要右值重载。它必须 return 一个 class,而不是 lambda。恼人的。让示例代码工作,我认为,仍然没有编译。

好在这种东西是写一次,用很多

为什么不按值传递?仅转发到顶级功能。

假设您传递了 intstd::string&float&& ,那么您的函数将类似于

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

从那里,匿名 lambda 将按值 复制 intstring&float&&。参考文献的副本仍然是副本。 您可以使用 tuple 再次打包参数并将它们解包到 lambda 中。

如何在lamda中使用元组?

  1. 像我们通常对可变参数模板所做的那样使用递归
  2. 找到 std::apply 的一些非标准实现并将元组用作另一个函数的参数

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

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

然后使用此答案: 在以后的函数调用中解压元组。

//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)...)](){}; 编译不了

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 的参数列表中:

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 资格。


C++20

C++20 对完美转发捕获有适当的支持:

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