Further notes on C++ lambda (N2550)

Note: This entry has been restored from old archives.

Since my earlier post (content here should mostly make sense independent of the post) I’ve taken some time to explore the C++ lambda proposal further, specifically document N2550 [pdf] that fully defines the proposal.

The first note is that the “In a nutshell” example used to illustrate the use of the method is not reassuring. First they introduce the functor:

class between {
  double low, high;
  public:
    between(double l, double u) : low(l), high(u) { }
    bool operator()(const employee& e) {
      return e.salary() >= low && e.salary() < high;
    }
}
....
double min salary;
....
std::find if(employees.begin(), employees.end(),
                between(min salary, 1.1 * min_salary));

Then they provide the lambda equivalent:

double min salary = ....
double u limit = 1.1 * min_salary;
std::find if(employees.begin(), employees.end(),
                [&](const employee& e) { return e.salary() >= min salary && e.salary() < u limit; });

This seems to be the same example that the Beautiful Code blog picked up on.

I have to admit however, this is growing on me just a little. Maybe like mould growing on damp bread.

To dissect:

The [&] introduces the lambda expression, in this case the inclusion of a lone & indicates that the closure created will have reference access to all names in the creating (calling) scope. This is why the code can use min_salary, if the lambda was introduced with only [] the code would be erroneous. You can, and possibly should in most cases, specify exactly what the closure can access and in this case that would be done using [&min_closure]. The normal meaning of & is retained here, the variables the closure accesses are references to those in the enclosing scope — for better safety in this case it is maybe better to use [min_closure], this will ensure the closure will not modify the original value (consider is passed to the closure by value.)

I see a pitfall here, the non-& capture means the closure can access values by the enclosing name and modify them but the modifications will not propagate to the enclosure. It wouldn’t surprise me if, for clarity and readability, the & form (pass by reference) becomes standard and the default form (pass by value) becomes “not done.” I feel it may be preferable for the “pass by value” to actually be “pass by const reference.”

The (const employee& e) gives the “parameter declaration” of the lambda. Essentially we’re declaring a function, or functor, that takes a single const reference to an employee as an argument. Note that this mirrors the operator() of the between functor. There’s more to the declaration than is given here, it can also sport an optional exception-specification (um?) and a return type. Let’s skip the exception specification since I don’t feel up to the task of trying to explain that one. (I suspect it is because the result of a lambda is under the hood a functor and it isn’t insensible to export this part of the definition to the user rather than enforcing no specification.) The return parameter is likely to be more regularly interesting, in this case it is not given but is actually -> bool – why? Because of paragraph 6 of 5.1.1, which essentially says that the return type is defined by the type of the argument to the return statement in the lambda.

The lambda expression itself, or code of the closure if you prefer, is between the { and }. That part is clear enough I expect.

The return type is defined by the top-level expression of the return statement in the expression. This is &&, clearly enough the return type is a bool.

Read the document [pdf], it’s worth the time – it’s a short document.

So?

Um, still not sure, but seeing all that is specified by the document does open up the possibilities much further. I said earlier that I think this lambda syntax has the potential to make code clearer where the function-object (or binder) code replaced achieves a simply expressed end. On further consideration I think this is especially the case where the expression is not easily described with a reasonable functor name (i.e. the functor approach for “greater_than” is fine and possibly better than the lambda equivalent.) The question, for maintainability, becomes: is:

    between(min salary, 1.1 * min_salary))

better than:

    [&](const employee& e) { return e.salary() >= min salary && e.salary() < u limit; });

Personally I still don’t see enough of a win to the latter syntax to justify the extension. Saving the code that defines the functor doesn’t look like a big win and the latter code, while more explicit at place-of-use, doesn’t seem so stupendously more clear to justify the extension either.

What if…

What if the extension allowed for creation of closures more in keeping with their much-hyped use in other languages? Would I, monkey boy, be convinced then? Quite possibly. Scroll down to Chapter 20 in the document and peruse “Class template reference_closure“.

Looking interesting. As I see it the reference_closure definition will permit the creation of instances-of-lambdas (closures.) These can be returned from a class as “closures” giving very specific restricted write access to the internals of a class. Vague shadows of how this could make the design of code I’ve worked on in the past more straightforward are beginning to form. That’s far from an indication of usefulness however, the shadows aren’t any better than “deformed rabbit” right now. I’m still a bit confused as to the lifetime of variables local to the scope of the enclosing function, as opposed to member variables, in relation to an exported “closure.” Especially since the closure template defined seems to restricted to reference-only closures.

I don’t have time to explore deeper at the moment, but it’s certainly food for thought and brings this C++ lambda/closure extension more in line with the hype around it in other languages. (Kat’s work has recently jumped on the closure bandwagon (best thing since Agile?) and it seems that their picture of closures in Perl very much mirrors what would be achieved by this reference_closure. At the same time… it is also achievable with everyday functors, but maybe the new specification makes it all easier and more accessible? Perhaps that is the question.)

The point of all this text is: RTFM [pdf]. Herb Sutter’s post is a quick-n-dirty illustration of simple things that can be achieved using the specification — he refers to the specification itself for your further investigation. I’m envious that he had an implementation that will compile his lambda examples, presumably VC++.

[[I use “closure” and “lambda” almost interchangeably here, however I’m far from confident in my use of this language. Reading up on this online does not help much as there’s a gap between the CS definitions of the terms and many commonly blogged use-cases. In practise it seems a lambda is an in-place definition of function code, what we often refer to as an “anonymous (unnamed) function”, that can be passed down to called functions (and is most often seen defined in the place of a parameter interchangeable with a function reference.) Practice again seems to define a closure as inline function code that can access and modify values in the scope where it was defined but can be returned to and invoked by calling code, this matches fairly well with the wikipedia definition of the term. YMMV.]]