如何通过前进或移动来捕获参数包
how to capture a parameter pack by forward or move?
假设我有这个函数:
template <typename ...A>
void test(A&& ...a)
{
[=]()
{
};
}
参数包是要转发到lambda中,还是只是按值复制?我很担心,因为我通常必须做一个显式的move()
或forward()
,因为a...
是lvalue。是否需要元组中介来转发/移动它们?如果是这样,是否有一种简单的方法可以在不使用索引技巧的情况下将元组解压缩到参数包中?
写一个签名函数(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)...)](){};
这样的东西没有编译。
为什么不按值传递?转发仅限于顶部功能。
比方说,您传递了一个int
、std::string&
和float&&
,所以您的函数看起来像
void test(int,string&,float&&)
{
[=]()
{
};
}
从那里,匿名lambda将按值复制int
、string&
和float&&
。引用的副本仍然是副本。您可以使用tuple
来再次打包参数,并在lambda中对其进行解包。
如何在lamda中使用元组?
- 使用递归,就像我们通常使用可变模板一样
- 找到
std::apply
的一些非标准实现,并将元组用作另一个函数的参数
- MSVC将仅移动结构参数解释为指针
- 如果需要转换,我可以在读取参数的同时将其移动到另一个参数吗?
- 使用移动语义:右值引用作为方法参数
- 为什么参数在构造 std::thread 时移动两次
- 移动类的成员作为常量引用参数传递
- 当变量和参数名称匹配时,移动语义构造失败
- 使用 std::move 将参数传递给函数,如果该参数声明为按值传递或使用移动操作数 &&,是否有区别?
- 移动构造函数中的默认参数
- 将参数传递给构造函数和成员函数时移动或复制
- 从函数返回元组时,将复制元组的参数,而不是移动元组参数
- C++具有移动语义的可变参数工厂会导致运行时崩溃
- 覆盖Qt的鼠标按下事件中断移动事件参数
- 在简单地移动参数时使用函数模板参数的优点
- 如果没有带有函数签名的 rvalue 参数,是否会执行 C++ 11 中的移动语义?
- x64 函数调用参数推送/移动顺序 (MSVC)
- 复制文件成功,移动文件失败,参数相同 - C++
- 从从可调用参数创建的线程对象参数移动构造 C++11 线程
- 将std::enable_if从参数移动到模板参数
- 将所有参数移动到 lambda
- 将参数移动到std::线程中