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 castsx
toX&&
.If
X
is an lvalue reference type, it castsx
toX&
.
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
Scope VS Life of Variable in C
Replacing Ld with Gold - Any Experience
What Is Data Alignment? Why and When Should I Be Worried When Typecasting Pointers in C
Inheritance or Composition: Rely on "Is-A" and "Has-A"
Why Doesn't Reference-To-Member Exist in C++
Why Does Std::Map Operator[] Create an Object If the Key Doesn't Exist
Are Compound Literals Standard C++
How to Use the Non-Default Constructor for a Member
How to Read Files in Sequence from a Directory in Opencv
Differencebetween Wm_Quit, Wm_Close, and Wm_Destroy in a Windows Program
How to Make This C++ Object Non-Copyable
C++ Data Alignment /Member Order & Inheritance
(Partially) Specializing a Non-Type Template Parameter of Dependent Type
"Inline" Keyword VS "Inlining" Concept