Capturing Perfectly-Forwarded Variable in Lambda

Capturing perfectly-forwarded variable in lambda

Is it correct to capture the perfectly-forwarded mStuff variable with
the &mStuff syntax?

Yes, assuming that you don't use this lambda outside doSomething. Your code captures mStuff per reference and will correctly forward it inside the lambda.

For mStuff being a parameter pack it suffices to use a simple-capture with a pack-expansion:

template <typename... T> void doSomething(T&&... mStuff)
{
auto lambda = [&mStuff...]{ doStuff(std::forward<T>(mStuff)...); };
}

The lambda captures every element of mStuff per reference. The closure-object saves an lvalue reference for to each argument, regardless of its value category. Perfect forwarding still works; In fact, there isn't even a difference because named rvalue references would be lvalues anyway.

c++ - capturing perfectly forwarded vars in a lambda

Don't try to avoid a copy when you actually need one. In your case, you try to preserve the value categories of the arguments by std::forward them. But when you return a factory function std::function<T*()>, this closure must own the data it uses to perform the delayed construction. Otherwise, you end up with dangling references, as the arguments passed to AsMinimalAsItGets only outlive the scope of the function call.

The fix is easy:

template<class T, typename...TArgs> 
std::function<T*()> AsMinimalAsItGets(TArgs&&...args)
{
return [args...]() mutable -> T*
// ^^^^^^^ (1) Copy the arguments into the closure
{
return new T(args...);
// ^^^^^^^ (2) Pass them as is to the ctor
};
}

Note that as @HolyBlackCat pointed out, this does not perfectly forward the arguments into the lambda capture. As shown in this answer, in C++20, you can

return [...args = std::forward<TArgs>(args)]() mutable -> T* 
{
return new T(args...);
};

while in C++17, you need this workaround:

return [args = std::make_tuple(std::forward<TArgs>(args)...)]() mutable -> T* 
{
return std::apply([](auto&&... args){ return new T(args...); },
std::move(args));
};

Perfectly capturing a perfect forwarder (universal reference) in a lambda

You may use the following:

[test = std::conditional_t<
std::is_lvalue_reference<T>::value,
std::reference_wrapper<std::remove_reference_t<T>>,
T>{std::forward<T>(t)}]

Live Demo

but providing helper function seems more readable

Passing forwarding reference as lambda capture

As far as I am concerned v in (X) is a forwarding reference.

Correct.



Is v also a forwarding reference in (Y) if it was captured by reference?

No, v inside the body of the lambda refers to the anonymous closure instance's member variable v. Read

some_func (std::forward<T> (v));

as

some_func (std::forward<T> (this_closure->v));

Regardless, your use of std::forward here is correct - it will propagate temporariness if the v passed to foo was an rvalue.

This is because std::forward<X>(x) does this:

  • If X is a value type or an rvalue reference type, it casts x to X&&.

  • If X is an lvalue reference type, it casts x to X&.

v inside the lambda body is always an lvalue, but std::forward<T> depends on the type of the original type passed to foo.

If you are sure that the lambda will be invoked once in the same call stack as the foo invocation, it is safe to capture by reference and forward in the body.

Otherwise, you might want to "capture by perfect-forwarding".

Perfect forwarding in a lambda?

The canonical way to forward a lambda argument that was bound to a forwarding reference is indeed with decltype:

auto f = [](auto&& x){
myfunction(std::forward<decltype(x)>(x));
} // ^^^^^^^^^^^

variadic lambda with perfect forwarding

The 1st implementation is a variable template, for example:

template <typename... Args>
size_t x = sizeof...(Args);

The lambda itself is not generic, there’s a different lambda for each template instantiation.

c++ lambdas how to capture variadic parameter pack from the upper scope

Perfect capture in C++20

template <typename ... Args>
auto f(Args&& ... args){
return [... args = std::forward<Args>(args)]{
// use args
};
}

C++17 and C++14 workaround

In C++17 we can use a workaround with tuples:

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));
};
}

Unfortunately std::apply is C++17, in C++14 you can implement it yourself or do something similar with 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
});
};
}

It might be usefull to simplify the workaround by a function 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))
));
};
}

Use it like this:

#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
}

Here is a C++14 implementation of capture_call:

#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 captures variables by value. The perfect means that the move constructor is used if possible. Here is a C++17 code example for better understanding:

#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";
}

Output:

  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()

The type of the capture value contains && in the capture_call version because we have to access the value in the internal tuple via reference, while a language supported capture supports direct access to the value.

Perfect Forwarding for Lambda Chaining

auto argsTpl = boost::hana::make_basic_tuple(std::forward<decltype(args)>(args)...); does copy (move) of arguments, so you only mutate the copy.

You need std::forward_as_tuple equivalent for boost:

constexpr auto chain_lambdas = [](auto... lambdas) {
constexpr auto lambdasTpl = boost::hana::make_basic_tuple(lambdas...);

return [lambdas(lambdasTpl)](auto&&... args) {
auto argsTpl = boost::hana::basic_tuple<decltype(args)...>(std::forward<decltype(args)>(args)...);

boost::hana::for_each(lambdas, [args(argsTpl)](auto lambda) mutable {
boost::hana::unpack(args, [lambda(lambda)](auto&&... args) {
lambda(std::forward<decltype(args)>(args)...);
});
});
};
};

Demo

On my side, only with std (C++17), you might do:

constexpr auto chain_lambdas = [](auto... lambdas) {
return [=](auto&&... args) {
return std::apply([&](auto... f){ (f(args...), ...); }, std::tie(lambdas...));
};
};

Demo.

I drop std::forward for args as calling function with moved object might be undesirable, but you can enable it if wanted.



Related Topics



Leave a reply



Submit