My question pertains to the following nested lambda expression, provided as an example under Lambda expressions
// 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 [=] { printer(ts...); }; // nullary lambda (takes no parameters)
};
};
auto p = vglambda([](auto v1, auto v2, auto v3) { std::cout << v1 << v2 << v3; });
auto q = p(1, 'a', 3.14); // outputs 1a3.14
q(); // outputs 1a3.14
The following is the way I interpret the above expression:
In the expression
auto p = vglambda([](auto v1, auto v2, auto v3) { std::cout << v1 << v2 << v3; });
the closure object vglambda
is initialized with the closure object printer
whose type corresponds to the lambda expression
[](auto v1, auto v2, auto v3) { std::cout << v1 << v2 << v3; }
Inside printer
, the nested (anonymous) lambda expression
return [=](auto&&... ts){}
captures printer
by copy and its parameter pack as rvalue
reference.
Inside the body of the (anonymous) lambda expression, the expression
printer(std::forward<decltype(ts)>(ts)...);
forwards the parameter pack to printer
[in what essentially appears to be an invocation of printer
using operator ()
]
In the final expression inside the body of the (anonymous) lambda expression, the (anonymous) nullary lambda expression appears to capture the printer
closure object from the enclosing scope by copy, along with the parameter pack, and invokes the printer
closure object with its forwarded parameter pack.
return [=] { printer(ts...); };
Now, it is very evident that I am not getting something right here. Essentially, why are two distinct lines of invoking the printer
closure object provided within the body of the (anonymous) lambda expression, one without the (anonymous) nullary lambda expression, and one within?
Can any of the experts throw more light?
In the expression
auto p = vglambda([](auto v1, auto v2, auto v3) { std::cout << v1 << v2 << v3; });
the closure object
vglambda
is initialized with the closure object printer whose type corresponds to the lambda expression[](auto v1, auto v2, auto v3) { std::cout << v1 << v2 << v3; }
It seems to me more correct say that vglamba
is invoked with the lambda that uses std::cout
. vglambda
is initialized with a generic lambda function that receive a generic (auto
) value (printer
).
Inside
printer
, the nested (anonymous) lambda expressionreturn [=](auto&&... ts){}
captures printer by copy and its parameter pack as rvalue reference.
Not inside printer
(that is only the argument of the lambda) but inside the lambda that is saved in the vglambda
variable.
Yes, the anonymous nested generic and variadic function captures printer
by value but isn't precise that the ... ts
are captured (are arguments) and by rvalue reference.
The nested lambda is almost equivalent to a template function (well... to a struct with a template operator()
inside it... but to make it simpler...)
template <typename ... Ts>
auto func (Ts && ... ts)
{ /*...*/ }
In this case the &&
aren't rvalue references but forwarding references (see this page for more information), as you can see from the inside use of std::forward
.
This is an important point, but see next.
Inside the body of the (anonymous) lambda expression, the expression
printer(std::forward<decltype(ts)>(ts)...);
forwards the parameter pack to
printer
[in what essentially appears to be an invocation of printer usingoperator ()
]
This seems to me correct.
In the final expression inside the body of the (anonymous) lambda expression, the (anonymous) nullary lambda expression appears to capture the
printer
closure object from the enclosing scope by copy, along with the parameter pack, and invokes theprinter
closure object with its forwarded parameter pack.return [=] { printer(ts...); };
This seems to me correct but you should see a problem here (together with the preceding printer()
invocation.
why are two distinct lines of invoking the printer closure object provided within the body of the (anonymous) lambda expression, one without the (anonymous) nullary lambda expression, and one within?
See how p
is used
auto p = vglambda([](auto v1, auto v2, auto v3) { std::cout << v1 << v2 << v3; });
auto q = p(1, 'a', 3.14);
q();
How is initialized p
?
p
is initialized with the generic and variadic lambda defined inside the lambda that initialize vglambda
. So when is called
auto q = p(1, 'a', 3.14);
you have that the generic variadic lambda is called with the variadic pack ts...
that is expanded as 1
, 'a'
and 3.14
.
So, calling p(1, 'a', 3.14)
, you have that is invoked (ignoring the forwarding part)
printer(1, 'a', 3.14);
(where printer()
is the lambda that std::cout
a
, b
and c
) and that is returned [=] { printer(1, 'a', 3.14); }
.
So q
is initialized with [=] { printer(1, 'a', 3.14); }
and, calling
q();
printer(1, 'a', 3.14)
is called again.
So the idea of the generic and variadic lambda is to call print()
, with the variadic argument received, a first time and return another lambda that print()
again when invoked.
So from p(1, 'a', 3.14)
you activate the first print()
(the one with std::forward
) and every time you call the returned value (q
, in your example) you activate the second print()
(the one without std::forward
).
But there is a big defect in your code. A defect that doesn't give problems invoking p()
with primitive types as int
, char
and double
. But a defect that is dangerous using complex objects that support move semantic.
The problem is that, using std::forward
, you could activate move semantics.
So in this code
return [=](auto&&... ts) // generic lambda, ts is a parameter pack
{
printer(std::forward<decltype(ts)>(ts)...);
return [=] { printer(ts...); }; // <- DANGER: unsafe use of `ts...`
};
the first printer()
call is safe and correct (using std::forward
) but the second call to printer()
is dangerous because we don't know if the ts...
are still usable.
Using ts...
two times, I suggest to rewrite the lambda as follows
return [=](auto const & ... ts)
{
printer(ts...);
return [=] { printer(ts...); };
};