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