views:

1035

answers:

5

I wonder how people are using C++0x lambdas, in terms of coding style. The most interesting question is how thorough to be when writing the capture list. On one hand, the language allows to list captured variables explicitly, and by the "explicit is better than implicit rule", it would therefore make sense to do an exhaustive listing to clearly state the intetion. E.g.:

 int sum;
 std::for_each(xs.begin(), xs.end(), [&sum](int x) { sum += x });

Another argument for this is that, since lifetime of ref-captured locals doesn't change just because they're captured (and so a lambda can easily end up referencing a local whose lifetime has long ended), making capture explicit helps reduce such bugs and track them down.

On the other hand, the language also deliberately provides a shortcut for auto-capturing all referenced locals, so clearly it's intended to be used. And one could claim that for an example such as one above, it is very clear what happens even with auto-capture, and lifetime of lambda is such that it won't outlive the surrounding scope, so there's no reason not to use it:

 int sum;
 std::for_each(xs.begin(), xs.end(), [&](int x) { sum += x });

Obviously this doesn't have to be all-or-nothing, but there has to be some rationale to decide when to auto-capture, and when to do capture explicitly. Any thoughts?

Another question in the same vein is when to use capture-by-copy - [=], and when to use capture-by-reference - [&]. Capture-by-copy is obviously safer because there are no lifetime issues, so one could argue that it should be used by default whenever there's no need to mutate the captured value (or see the changes done to it from elsewhere), and capture-by-reference should be treated as (potentially premature) optimization in such cases, to be applied only where it clearly makes a difference.

On the other hand, capture-by-reference is almost always faster (especially as it can often be optimized down to a copy, if the latter is actually faster, for small types and inlineable template functions such as most STL algorithms), and is safe if lambda never outlives its scope (which is also the case for all STL algorithms), so defaulting to capture-by-reference in this case is a trivial and harmless optimization which doesn't hurt.

What are your thoughts?

+2  A: 

My initial instinct was that capture by value offers more or less the same as Java's anonymous inner classes, which are a known quantity. But rather than using the array-of-size-1 trick when you want the enclosing scope to be mutable, you can capture by reference instead. It's then your responsibility to confine the duration of the lambda within the scope of the referand.

In practice I agree with you that capture by reference should be the default when dealing with algorithms, which I expect will be the majority of uses. A common use for anonymous inner classes in Java is listeners. There are fewer listener-style interfaces to be seen in C++ to start with, so it's a lesser need, but still there. It might be best to strictly stick to capture by value in that kind of case, to avoid the opportunity for error. Will capture-by-value of a shared_ptr be a big idiom, maybe?

However, I haven't used lambdas yet, so I may well have missed something huge.

Steve Jessop
It should be noted that the absence of listener pattern in C++ standard library is mostly due to its smallish size (most notably no UI). Virtually any UI toolkit has listeners/events in some form, and some of them (Gtk--, IIRC) allow you to register arbitrary function objects as listeners, and not just functions or methods (as e.g. Qt does). This will probably grow even more popular with adoption of `std::tr1::function` (which is `std::function` in C++0x) as a standard way to do this. So the issue will be there.
Pavel Minaev
Regarding capture-by-value of a `shared_ptr` for captured state that should be able outlive the scope (such as listeners) - good point, and, in fact we have some of that in our code already.
Pavel Minaev
+4  A: 

I've never heard of the "explicit is better than implicit rule" rule, and I don't agree with it. There are cases where it's true, of course, but also plenty of cases where it isn't. That's why 0x is adding type inference with the auto keyword after all. (and why function template parameters are already inferred when possible) There are plenty of cases where implicit is preferable.

I haven't really used C++ lambdas yet (other than poking around with the VC10 beta), but I'd go with the latter most of the time

std::for_each(xs.begin(), xs.end(), [&](int x) { sum += x });

My reasoning? Why not do it? It's convenient. It works. And it's easier to maintain. I don't have to update the capture list when I modify the body of the lambda. Why should I be explicit about something the compiler knows better than me? The compiler can figure out the capture list based on what's actually used.

As for capture by reference vs value? I'd apply the same rules as I do for regular functions. If you need reference semantics, capture by reference. If you need copy semantics, do that. If either will do, prefer value for smallish types, and reference if copying is expensive.

It doesn't seem different from the choice you have to make when designing a regular function.

I should probably read up on the specs for lambdas, but isn't the main reason for explicit capture lists so that you can capture some variables by value and others by reference?

jalf
There's no explicit rationale for it, but yes, it is something that's only possible with a capture list.
Pavel Minaev
Pavel Minaev
"Explicit is better than implicit" is from The Zen of Python (http://www.python.org/dev/peps/pep-0020/), but that thing isn't really specific to Python.
Pavel Minaev
Ah, thought I'd heard it somewhere before. Never heard it as an independent rule though. But like pretty much any rule, you can take it too literally. There's plenty in Python that's implicit too. (and in some cases it contradicts the following line too - 'simple is better than complex'.) I think the rule is useful in resolving ambiguity (if there's ambiguity, don't rely on obscure implicit rules to resolve it, be explicit about what you want), but when it's clear what you mean, there's no point in stating the obvious explicitly.
jalf
A: 

I can see a new coding standard rule here! ;)

This is a bit contrived but just to highlight an "advantage" to being explicit, consider the following:

void foo (std::vector<int> v, int x1)
{
  int sum = 0;
  std::for_each (v.begin ()
    , v.end ()
    , [&](int xl) { sum += x1; } 
}

Now, I've purposely chosen poor names etc for this, but it's just to illustrate the point. If we used an explicit capture list then the above code wouldn't compile, but currently it will.

In a very strict environment (safety critical) I can see a rule like this being part of the coding standard.

Richard Corden
Although what you say is true, you could make exactly the same mistake if you wrote a for loop, and did int xl = *iterator; sum += x1;. Nobody's demanding explicit capture in that situation. In cases where the lifetime of the lambda is confined to the local scope, I think that implicit capture is no more (nor less) dangerous than the fact that simple braces inherit the surrounding automatic scope. It's when the lambda is going to live on that I think you want to be explicit what's captured (and deduce that what is not captured can safely die at end of lexical scope).
Steve Jessop
It is not important that the error can exist in other constructs. This is an additional advantage of lambda's, in that you can reduce the visibility of names more than is possible for a "for loop". There are many ways that name hiding/visibility can result in subtle errors. One goal of a coding standard is to reduce the cases that can cause a problem and you'll find several rules regarding name hiding and reduction of name visibility in standards such as JSF++, MISRA C/C++. I can see this being another.
Richard Corden
But it doesn't actually reduce the visibility compared with the equivalent functor, it just prevents it being extended. That's not an advantage, it's mitigation. So on this point I think lambdas are (very slightly) worse than functors, in that if you think default capture is bad, you now have to ban it and police the ban. But yes, perhaps better than structured programming.
Steve Jessop
In fact, if we see `auto nextblock = [ do_something(somevar,tmpvar); }; nextblock();` in preference to `{thing tmpvar; do_something(somevar,tmpvar);}`, then I will be fully convinced that lambdas offer a genuine advantage over existing constructs by virtue of limiting visibility of all locals except somevar. But I believe their advantages are elsewhere...
Steve Jessop
"...reduce the visibility compared with the equivalent functor". I never implied that it would, however, implicit capture increases the number of names that are unnecessarily visible within the lambda body (compared to the functor case)- I believe this might be considered a "bad thing (tm)" and I can imagine such a rule about implicit capture, especially in a safety critical environment where implicit behaviours in general are frowned upon. Maybe we should just agree to disagree! :)
Richard Corden
I think I just misread what you meant by "advantage to being explicit". It's an advantage compared with lambdas that don't use explicit capture, rather than an advantage compared with the current way of minimising visibility (functors)? So I do now see the point of the coding standard rule you propose, since it's to stop people "abusing" lambdas to accidentally increase visibility. Thanks :-)
Steve Jessop
A: 

I would go with explicit capture lists when at all convenient, when you want to capture a lot of variables then (you're probably doing something wrong and) you can use the grab all [&] capture list.

My take of the matter is that explicit capture lists are the ideal and the implicit variants should be avoided and are only there so people won't have to type out reams of code when they are actually needed.

Motti
A: 

I am reading the following link to get a better understanding of C++ lambda. The coding style used in the examples are quite neat and I am able to follow: http://uint32t.blogspot.com/2009/05/using-c0x-lambda-to-replace-boost-bind.html

hackworks