c ++ lambdas如何从上层范围捕获可变参数包
c++ lambdas how to capture variadic parameter pack from the upper scope
我研究了通用的 lambdas,并稍微修改了示例,
所以我的 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"
}
The perfect forwarding is another question, I'm curious is it possible here at all?
嗯...在我看来完美转发是的问题
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++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
版本中包含&&
,因为我们必须通过引用访问内部元组中的值,而支持捕获的语言支持直接访问该值.
您可以使用 std::bind
将可变参数绑定到您的 lambda(对于 pre-C++20解):
template <typename... Args>
auto f(Args&&... args){
auto functional = [](auto&&... args) { /* lambda body */ };
return std::bind(std::move(functional), std::forward<Args>(args)...);
}
我研究了通用的 lambdas,并稍微修改了示例,
所以我的 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"
}
The perfect forwarding is another question, I'm curious is it possible here at all?
嗯...在我看来完美转发是的问题
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++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
版本中包含&&
,因为我们必须通过引用访问内部元组中的值,而支持捕获的语言支持直接访问该值.
您可以使用 std::bind
将可变参数绑定到您的 lambda(对于 pre-C++20解):
template <typename... Args>
auto f(Args&&... args){
auto functional = [](auto&&... args) { /* lambda body */ };
return std::bind(std::move(functional), std::forward<Args>(args)...);
}