c ++ lambdas如何从上层范围捕获可变参数包
Posted
技术标签:
【中文标题】c ++ lambdas如何从上层范围捕获可变参数包【英文标题】:c++ lambdas how to capture variadic parameter pack from the upper scope 【发布时间】:2018-05-09 19:57:14 【问题描述】:我研究了通用 lambda,并对示例稍作修改,
所以我的 lambda 应该捕获上层 lambda 的可变参数包。
所以基本上作为(auto&&...)
给上层lambda 的内容应该以某种方式在[=]
块中捕获。
(完美转发是另一个问题,我很好奇这里有可能吗?)
#include <iostream>
#include<type_traits>
#include<utility>
// base case
void doPrint(std::ostream& out)
template <typename T, typename... Args>
void doPrint(std::ostream& out, T && t, Args && ... args)
out << t << " "; // add comma here, see below
doPrint(out, std::forward<Args&&>(args)...);
int main()
// generic lambda, operator() is a template with one parameter
auto vglambda = [](auto printer)
return [=](auto&&... ts) // generic lambda, ts is a parameter pack
printer(std::forward<decltype(ts)>(ts)...);
return [=] // HOW TO capture the variadic ts to be accessible HERE ↓
printer(std::forward<decltype(ts)>(ts)...); // ERROR: no matchin function call to forward
; // nullary lambda (takes no parameters)
;
;
auto p = vglambda([](auto&&...vars)
doPrint(std::cout, std::forward<decltype(vars)>(vars)...);
);
auto q = p(1, 'a', 3.14,5); // outputs 1a3.14
//q(); //use the returned lambda "printer"
【问题讨论】:
旁白:你可能不想转发同一个包两次 【参考方案1】:C++20 中的完美捕捉
template <typename ... Args>
auto f(Args&& ... args)
return [... args = std::forward<Args>(args)]
// use args
;
C++17 和 C++14 解决方法
在 C++17 中,我们可以使用元组的解决方法:
template <typename ... Args>
auto f(Args&& ... args)
return [args = std::make_tuple(std::forward<Args>(args) ...)]()mutable
return std::apply([](auto&& ... args)
// use args
, std::move(args));
;
不幸的是 std::apply
是 C++17,在 C++14 中你可以自己实现它或者用 boost::hana
做类似的事情:
namespace hana = boost::hana;
template <typename ... Args>
auto f(Args&& ... args)
return [args = hana::make_tuple(std::forward<Args>(args) ...)]()mutable
return hana::unpack(std::move(args), [](auto&& ... args)
// use args
);
;
通过函数capture_call
简化解决方法可能很有用:
#include <tuple>
// Capture args and add them as additional arguments
template <typename Lambda, typename ... Args>
auto capture_call(Lambda&& lambda, Args&& ... args)
return [
lambda = std::forward<Lambda>(lambda),
capture_args = std::make_tuple(std::forward<Args>(args) ...)
](auto&& ... original_args)mutable
return std::apply([&lambda](auto&& ... args)
lambda(std::forward<decltype(args)>(args) ...);
, std::tuple_cat(
std::forward_as_tuple(original_args ...),
std::apply([](auto&& ... args)
return std::forward_as_tuple< Args ... >(
std::move(args) ...);
, std::move(capture_args))
));
;
像这样使用它:
#include <iostream>
// returns a callable object without parameters
template <typename ... Args>
auto f1(Args&& ... args)
return capture_call([](auto&& ... args)
// args are perfect captured here
// print captured args via C++17 fold expression
(std::cout << ... << args) << '\n';
, std::forward<Args>(args) ...);
// returns a callable object with two int parameters
template <typename ... Args>
auto f2(Args&& ... args)
return capture_call([](int param1, int param2, auto&& ... args)
// args are perfect captured here
std::cout << param1 << param2;
(std::cout << ... << args) << '\n';
, std::forward<Args>(args) ...);
int main()
f1(1, 2, 3)(); // Call lambda without arguments
f2(3, 4, 5)(1, 2); // Call lambda with 2 int arguments
这是capture_call
的C++14实现:
#include <tuple>
// Implementation detail of a simplified std::apply from C++17
template < typename F, typename Tuple, std::size_t ... I >
constexpr decltype(auto)
apply_impl(F&& f, Tuple&& t, std::index_sequence< I ... >)
return static_cast< F&& >(f)(std::get< I >(static_cast< Tuple&& >(t)) ...);
// Implementation of a simplified std::apply from C++17
template < typename F, typename Tuple >
constexpr decltype(auto) apply(F&& f, Tuple&& t)
return apply_impl(
static_cast< F&& >(f), static_cast< Tuple&& >(t),
std::make_index_sequence< std::tuple_size<
std::remove_reference_t< Tuple > >::value >);
// Capture args and add them as additional arguments
template <typename Lambda, typename ... Args>
auto capture_call(Lambda&& lambda, Args&& ... args)
return [
lambda = std::forward<Lambda>(lambda),
capture_args = std::make_tuple(std::forward<Args>(args) ...)
](auto&& ... original_args)mutable
return ::apply([&lambda](auto&& ... args)
lambda(std::forward<decltype(args)>(args) ...);
, std::tuple_cat(
std::forward_as_tuple(original_args ...),
::apply([](auto&& ... args)
return std::forward_as_tuple< Args ... >(
std::move(args) ...);
, std::move(capture_args))
));
;
capture_call
按值捕获变量。完美意味着尽可能使用移动构造函数。下面是一个 C++17 代码示例,以便更好地理解:
#include <tuple>
#include <iostream>
#include <boost/type_index.hpp>
// Capture args and add them as additional arguments
template <typename Lambda, typename ... Args>
auto capture_call(Lambda&& lambda, Args&& ... args)
return [
lambda = std::forward<Lambda>(lambda),
capture_args = std::make_tuple(std::forward<Args>(args) ...)
](auto&& ... original_args)mutable
return std::apply([&lambda](auto&& ... args)
lambda(std::forward<decltype(args)>(args) ...);
, std::tuple_cat(
std::forward_as_tuple(original_args ...),
std::apply([](auto&& ... args)
return std::forward_as_tuple< Args ... >(
std::move(args) ...);
, std::move(capture_args))
));
;
struct A
A()
std::cout << " A::A()\n";
A(A const&)
std::cout << " A::A(A const&)\n";
A(A&&)
std::cout << " A::A(A&&)\n";
~A()
std::cout << " A::~A()\n";
;
int main()
using boost::typeindex::type_id_with_cvr;
A a;
std::cout << "create object end\n\n";
[b = a]
std::cout << " type of the capture value: "
<< type_id_with_cvr<decltype(b)>().pretty_name()
<< "\n";
();
std::cout << "value capture end\n\n";
[&b = a]
std::cout << " type of the capture value: "
<< type_id_with_cvr<decltype(b)>().pretty_name()
<< "\n";
();
std::cout << "reference capture end\n\n";
[b = std::move(a)]
std::cout << " type of the capture value: "
<< type_id_with_cvr<decltype(b)>().pretty_name()
<< "\n";
();
std::cout << "perfect capture end\n\n";
[b = std::move(a)]()mutable
std::cout << " type of the capture value: "
<< type_id_with_cvr<decltype(b)>().pretty_name()
<< "\n";
();
std::cout << "perfect capture mutable lambda end\n\n";
capture_call([](auto&& b)
std::cout << " type of the capture value: "
<< type_id_with_cvr<decltype(b)>().pretty_name()
<< "\n";
, std::move(a))();
std::cout << "capture_call perfect capture end\n\n";
输出:
A::A()
create object end
A::A(A const&)
type of the capture value: A const
A::~A()
value capture end
type of the capture value: A&
reference capture end
A::A(A&&)
type of the capture value: A const
A::~A()
perfect capture end
A::A(A&&)
type of the capture value: A
A::~A()
perfect capture mutable lambda end
A::A(A&&)
type of the capture value: A&&
A::~A()
capture_call perfect capture end
A::~A()
捕获值的类型在capture_call
版本中包含&&
,因为我们必须通过引用访问内部元组中的值,而支持的语言捕获支持直接访问该值。
【讨论】:
我对 C++17 解决方案(使用 std::make_tuple() 和 std::apply() 的解决方案)有点困惑。首先,我希望 make_tuple() 创建的元组包含 std::decay-ed 类型,除了 std::reference_wrapper 类型的参数。此外,std::apply() 会将元组转发给 std::get(),如果我们将左值引用传递给元组,我相信它将返回左值引用,否则将返回右值引用。所以总而言之,看起来我们并没有完美地转发捕获中的参数包。我错过了什么? @fireboot 捕获的对象作为值存储在 lambda 对象中。 “完美”捕获是指通过完美转发完成这些值的构建。 apply 将始终为您提供对捕获的(在存储的元组中)值的右值引用,这相当于定义了可变的 C++20 版本 lambda。我添加了一个示例以便更好地理解。多亏了你,我在原始的 capture_call 中发现了一个错误!之前的第二个 make_tuple 现在被第二个 apply 替换,它为捕获的值创建一个右值引用“tuple-view”。 为什么是 make_tuple,而不是 forward_as_tuple? C++20 解决方案不是也需要mutable
吗? Args&& args
应该是 Args&& ...args
。
@Mr.Wonko 你对...
的看法是正确的,我已修复它。谢谢!是否需要mutable
取决于闭包中的值是否应为const
。所以是的,如果你想要旧解决方法的确切行为,那么是的。【参考方案2】:
您可以使用std::bind
将可变参数绑定到您的 lambda(对于 C++20 之前的解决方案),而不是使用 std::tuple
和 std::apply
,这会使代码变得很混乱:
template <typename... Args>
auto f(Args&&... args)
auto functional = [](auto&&... args) /* lambda body */ ;
return std::bind(std::move(functional), std::forward<Args>(args)...);
【讨论】:
【参考方案3】:完美转发是另一个问题,我很好奇这里有可能吗?
嗯...在我看来,完美的转发是的问题。
ts...
的捕获效果很好,如果您在内部 lambda 中进行更改,
printer(std::forward<decltype(ts)>(ts)...);
与
printer(ts...);
程序编译。
问题是通过值捕获ts...
(使用[=]
)它们变成const
值和printer()
(即接收auto&&...vars
的lambda)接收引用( &
或 &&
)。
你可以用下面的函数看到同样的问题
void bar (int &&)
void foo (int const & i)
bar(std::forward<decltype(i)>(i));
从 clang++ 我得到
tmp_003-14,gcc,clang.cpp:21:4: error: no matching function for call to 'bar'
bar(std::forward<decltype(i)>(i));
^~~
tmp_003-14,gcc,clang.cpp:17:6: note: candidate function not viable: 1st argument
('const int') would lose const qualifier
void bar (int &&)
^
解决问题的另一种方法是将ts...
捕获为引用(因此[&]
)而不是值。
【讨论】:
以上是关于c ++ lambdas如何从上层范围捕获可变参数包的主要内容,如果未能解决你的问题,请参考以下文章
如何在标准函数 lambda c++ 11 中正确捕获参数包
如何从 C++14 中的广义 lambda 捕获返回包含 std::unique_ptr 的 std::function?