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

how to capture a parameter pack by forward or move?

本文关键字:参数 移动 何通过      更新时间:2023-10-16

假设我有这个函数:

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

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

一种方法是编写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,将参数存储在元组中,并在运算符()中解包musing the indexes技巧。

实际上,我们在手动伪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));
};
}

这使得参数的值/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);
});
};
}

这确实需要CCD_ 8。

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

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来处理常量和非常量,甚至可能需要右值重载。它必须返回一个类,而不是lambda。烦人的使示例代码工作,我认为,仍然没有编译。

幸运的是,这种东西写一次,用多次。

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

现场演示

以上内容适用于Clang,但是这个GCC似乎有一个伪volatile限定符的问题。

我们可以在没有bind的情况下通过在第二个lambda中捕获tuple来实现这一点,该lambda调用std::apply(C++17)将元组解压缩到第一个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); };
}

现场演示

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

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

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)...)](){};这样的东西没有编译。

为什么不按值传递?转发仅限于顶部功能。

比方说,您传递了一个intstd::string&float&&,所以您的函数看起来像

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

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

如何在lamda中使用元组?

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