Sunday, April 27, 2008

C++0x: The Lambda Expression Debate

he next C++ standard (C++0x) will have lambda expressions as part of the standard. N2550 introduces them. It's a short document, and it's not too painful to read. Go ahead and click it.

Like many new C++ standards, it's not clear yet how the new feature is going to be used. Michael Feathers has already decided not to use them. At least one other person seems to mostly agree. I, on the other hand, am with Herb Sutter who seems excited enough about the feature to imply that MSVC10 will have support for it. This is going to be a great feature. Incidentally, Sutter has mentioned an addition to C++/CLI in the past that would add less sophisticated lambda support for concurrency. I suspect he's serious about adding the support soon.

There have been many times when I've desperately wanted to avoid defining a one-off functor or function in my code. In fact, there have been times when I've been desperate enough to actually use Boost.Lambda! This standard is a clear win over Boost's attempts to deal with the limitations of C++03.

It's worth showing you what's possible. I'm going to steal code from other people's blogs since I don't have a compiler to test code. Here is Sutter's example:

find_if( w.begin(), w.end(),
[]( Widget& w ) { return w.Weight() > 100; } );

This has more punctuation than those earlier bloggers wanted to see. However, I'd call it extremely clean. It's the normal STL find_if algorithm with a lambda expression to create a functor. The initial [] declares the capture set for the lambda in case you wanted a closure. I'll talk about that later.

If you haven't been following the decltype/auto proposals in C++0x, you may be surprised to see that you don't have to declare the return type of the functor. As a side note, you will be able to use the auto keyword to allow the compiler to automatically determine the return type of a function. Expression template libraries are going to change a lot now that this becomes possible:

using namespace boost::xpressive;
auto crazyRegexToMatchDollars = '$' >> +_d >> '.' >> _d >> _d;

Suddenly, you can avoid placing your complicated types in container types that erase most of the type information.

That's another subject, though. The short version is that the compiler figures out the return type of your lambda expression based on the type of the expression that gets returned.

The capture list in the original lambda example is worth talking about. There are several options here. My favorite is the one Herb Sutter used: an empty list. This means that there are no captured variables in the closure, and this seems like a great default. In this case, the lambda expression cannot access any variables stored on the stack outside of the lambda declaration.

If the empty square brackets were replaced with a [&] or a [=], then any references to stack variables in the lambda expression will get something copied into the functor that the lambda expression creates. This level of automation may make some sense for extremely small lambda expressions.

It's great that the fully automatic behavior is optional. If the original [] is used, then you'll get a compiler error if you accidentally access a variable that may go out of scope before your functor is destroyed. The empty closure list appears to be a great safety mechanism. [=] is a good second choice, however. This makes all captures be copy constructed into the functor. Because everything gets copied, it should be safe from lifetime issues. [&] will copy non-const references into the functor and should probably be used with caution.

If you really need to write to a capture, you can list the variables that you want to use explicitly with a & in front of the variables. This means that, when reading the code, you'll realize that the variables listed in the capture list have to outlast the functor created by the lambda expression. Ironically, I'd go with the opposite of some of those earlier blogs and guess that [&] should be avoided as much as possible. (They seemed to think that storing references to unspecified captures should be done by everyone, all the time. C# does this, but it also has garbage collection to help out.)

So far, this extension looks great! I can imagine a lot of different ways to use different capture sets. It looks like a succinct way to make a variety of commonly needed functors, and I'm a fan of the safety that it allows. It should get people to make a lot more functors.

I'm really looking forward to n2550 going into compilers. I'll be watching GCC closely to see when they add reliable support for it, and I may stop using MSVC at home as soon as they do.

This originally appeared on PC-Doctor's blog.

No comments: