lambda expressions (was: Re: error with d28016d16e9a ...)
"John W. Eaton" <[email protected]> Fri, 2 Oct 2020 08:23:33 -0400
| Newsgroups | gmane.comp.gnu.octave.maintainers |
|---|---|
| Message-ID | <[email protected]> |
On 10/1/20 8:13 PM, Rik wrote:
> On 10/01/2020 12:42 PM, John W. Eaton wrote:
>> We could also decide to always use "[=]" when we need to capture
>> something. Then all variables that are needed, including "this" will
>> be captured using the default rules for capture by value.
>
> I don't think this is recommended practice. The temporary anonymous
> struct that is created to represent the lambda expression will then have
> an argument in the constructor for every existing variable in the
> surrounding function. The compiler *might* then optimize out all of the
> additional unused captures, but I think it would be better to just
> capture what you need either by value "[=variable_name]" if small like a
> built-in type or a pointer or by reference "[&variable_name]" if it is
> something large like the instance of a class. For reference, I was
> using this
> https://dzone.com/articles/all-about-lambda-functions-in-cfrom-c11-to-c17.
As I understand it, the lambda expression only captures variables that
are used. Using a capture default specification actually seems better
to me because the variables are already listed in the lambda expression,
so explicitly listing them again is redundant. To me, this seems quite
similar to using "auto" to avoid writing out something that the compiler
can figure out. I don't know why the article you linked lists [=] and
[&] as not recommended. I didn't see an explanation and I'm not finding
other similar recommendations.
>> * when possible, capture variables by value directly in the lambda
>> expression instead of the unwind_action object
>
> I would modify this to only small values should be passed by value.
For unwind-protect to work to save and restore values, they must be
copied. You can't just store a reference to a value that you want to
restore later. See the attached example.
In Octave, most values are reference counted and relatively cheap to
copy anyway.
OTOH, if you mean to use references to objects that are expensive to
copy and that are needed in by the lambda expression but that aren't
there to be saved/restored, then I agree, it's OK to capture by
reference. In that case, it would be nice if you could capture by const
reference so you could explicitly say that you won't be modifying the
value inside the lambda expression, but I don't see a way to do that
without using C++14 features (see
https://en.cppreference.com/w/cpp/language/lambda and search for "This
also makes it possible to capture by const reference").
Note that
unwind_action ([] (const auto& x) { use (x); }, x);
is not capturing X by const reference. The unwind_action object only
captures by value (copy). Then, when the unwind_action destructor
executes, it passes the captured value to the function generated by the
lambda expression by const reference. But the unwind_action object
still grabbed a copy of X.
The following does capture X by reference
unwind_action ([&x] () { use (x); });
but it is not const, so it is possible to change to X in the lambda
expression function and affect the value of X in the parent scope as well.
jwe
lambda-tst.cc
(text/x-c++src, 2.5 KB)
#include <functional>
#include <iostream>
// In most cases, the following are preferred for efficiency. Some
// cases may require the flexibility of the general unwind_protect
// mechanism defined above.
// Perform action at end of the current scope when unwind_action
// object destructor is called.
//
// For example:
//
// void fcn (int val) { ... }
//
// ...
//
// {
// int val = 42;
//
// // template parameters, std::bind and std::function provide
// // flexibility in calling forms (function pointer or lambda):
//
// unwind_action act1 (fcn, val);
// unwind_action act2 ([val] (void) { fcn (val); });
// }
//
// NOTE: Don't forget to provide a name for the unwind_action
// variable. If you write
//
// unwind_action /* NO NAME! */ (...);
//
// then the destructor for the temporary anonymous object will be
// called immediately after the object is constructed instead of at
// the end of the current scope.
class unwind_action
{
public:
template <typename F, typename... Args>
unwind_action (F&& fcn, Args&&... args)
: m_fcn (std::bind (fcn, args...))
{ }
// No copying!
unwind_action (const unwind_action&) = delete;
unwind_action& operator = (const unwind_action&) = delete;
~unwind_action (void) { m_fcn (); }
private:
std::function<void (void)> m_fcn;
};
int
main (void)
{
int x = 13;
std::cerr << "initial value of X in MAIN: " << x << std::endl;
{
// Capture X by value in the lambda expression. The lambda
// expression in ACT1 has a copy of X so any later changes to X in
// MAIN won't affect that value.
unwind_action act1
([x] (void)
{ std::cerr << "X copied in ACT1 lambda: " << x << std::endl; });
// Capture X by reference in the lambda expression. The lambda
// expression in ACT2 doesn't have a local copy, so any later
// change to the referenced X in MAIN will be reflected in ACT2
// (the X in ACT2 is not a separate value).
unwind_action act2
([&x] ()
{ std::cerr << "X referenced in ACT2 lambda: " << x << std::endl; });
// Capture X by value in the unwind_action object ACT3. ACT3 has
// a copy of X so any later changes to X in MAIN won't affect that
// value.
unwind_action act3
([] (int x)
{ std::cerr << "X copied in ACT3 unwind_action object: " << x << std::endl; }, x);
// Change the value of X in the parent.
x = 42;
std::cerr << "new value of X in MAIN: " << x << std::endl;
std::cerr << "Check values when the unwind_actions run:" << std::endl;
}
return 0;
}