Further notes on C++ lambda (N2550)
Tue 2008-04-01 00:00
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.]]
Recent Entries
Categories
- Entries - 260
- Beer - 1
- Cycling - 2
- Food - 53
- Cooking - 26
- Hare - 5
- Soup - 1
- Eating - 5
- England - 5
- London - 4
- Rickmansworth - 1
- Produce - 14
- Ristretto - 8
- Health - 3
- Money - 2
- Random - 74
- Technology - 93
- Code - 22
- General - 46
- Security - 23
- Work - 2
- Wanderings - 32
- Australia - 2
- Barcelona - 2
- Belgium - 2
- England - 15
- Cambridge_Easter - 3
- Lakes - 9
- Finland - 4
- France - 1
- Germany - 1
- Ramble - 3
- Wales - 1




No Responses