Class helpers versus lambda functions for readabliity?

In several places the code base defines struct or class helpers which have just a constructor and an operator () function. These “helper” classes are then used as a customizable functions in, for example, calls to remove_if in the STL. This is a commonly accepted strategy for writing these functions, but I wonder if it wouldn’t be easier to understand if we just used lambda functions instead.

Probably best to just give an example.

Here is the original code.

  // Helper function to be called by child_list::remove
  class pid_equal
  {
  public:

    pid_equal (pid_t pid) : m_pid (pid) { }

    bool operator () (const child& oc) const { return oc.m_pid == m_pid; }

  private:

    pid_t m_pid;
  };

  void child_list::remove (pid_t pid)
  {
    m_list.remove_if (pid_equal (pid));
  }

Here is the same code using lambda expressions.

  void child_list::remove (pid_t pid)
  {
    auto pid_equal = [pid] (const child& oc) -> bool
    {
      return oc.m_pid == pid;
    };

    m_list.remove_if (pid_equal);
  }

Any thoughts on clarity or performance?

1 Like

In this particular example, I like your lambda expression approach. It puts together, what belongs together, without having to study another class for it’s sparsely documented purpose :slightly_smiling_face: :+1:

These helper classes probably only exist because the code was written before C++11 existed or at least before we could use it.

Instead of using a separate variable for the lambda, I would probably write

  void child_list::remove (pid_t pid)
  {
    m_list.remove_if ([=] (const child& oc) { return oc.m_pid == pid; });
  }

It might not be in the coding guidelines, but I think at some point we discussed the use of [list, of, variables] vs. [=] and decided to prefer using [=].

That’s my thought as well. There’s a lot of technical debt to re-pay, but eliminating these helper classes in favor of a convenient lambda function seems clearer.

Are we sure that the compiler will optimize out unused variables if [=] is used to capture everything? It makes no difference in this particular case, but if there were 10 local variables and some of them were classes like octave_value that would require a lot of unnecessary memory accesses for this pass-by-value syntax.

From what I see at cppreference and microsoft docs, only variables referenced in the lambda expression are captured with ‘[=]’.

I think we should add it to the coding guidelines. But shouldn’t the default depend on whether we are capturing POD or more complex types? If we’re passing an int then [=] makes sense because it is just 8 bytes on the stack. Passing by reference really just means passing a pointer and then de-referencing the pointer so [&] wouldn’t make sense here. But, for big objects I would be concerned about not using references and thereby overflowing the stack.

@rik: Passing by reference also introduces a lifetime dependency. In the example you showed in the original post, there is no problem because the function that executes the lambda executes during the lifetime of the enclosing function. Copying is also fine because we are copying an integer (pid_t). But if you pass a lambda with captured variables to some function queue that ultimately executes the lambda expression in another thread, you will likely cause trouble if capturing by reference. So my preference is to use [=] for simplicity unless there is a good reason to do otherwise, and to remember that capturing variables by reference or capturing this introduces issues with the lifetimes of the captured variables that need to be carefully thought out.

@jwe: I wonder if there is too much variety in the situations encountered to make a recommendation as to style. Unless the programmer is doing something utterly trivial (which really should be the point of a lambda function), then the programmer probably does need to consider 1) size of variable, 2) scope and lifetime of variable, 3) whether this pointer is required.