Using Lambda Expressions for Shorter, More Readable C++ Code

This month, I'll focus on lambda expressions in C++. Lambdas are lightweight anonymous functions usually defined where they're used.

Why C++ Needs Lambda Expressions
Lambdas were a natural evolution. Even for C++'s ancestor, C, one of its most valued features was the ability to declare functions as parameters for other functions or procedures (similar to C# delegates). An example can be seen in Listing 1.

C++ introduced function objects, or functors. Functors are classes that overload the operator(). Since they're classes, functors can leverage object-oriented features like inheritance, polymorphism and so on. Listing 2 shows a typical functor.

Unlike C pointers to functions, functors can keep state (i.e., in private member variables, as you'll see in Listing 3). The downside compared with C comes from all those extra lines beyond the operator() definition, necessary to declare the function as a class. Both share the same eventual drawback: neither allows you to define the function just where you need to use it.

Other programming languages like Haskell, C#, Erlang or F# enable function definitions right where they're used. These are known as lambda expressions because its syntax is inspired in lambda calculus.
Lambda expressions came to C++ in its ISO C++11 standard specification.

A lambda definition starts with context-variable capture between brackets. In the two lambdas in Listing 4 the brackets are empty, so I'm not capturing anything. But I could have defined the second lambda in this way:

Thus, the lambda can access a variable from the context in which it's defined and even modify it. Capture by reference is how it's done in C#, but it's not the only way to do it in C++; if the last example had been [even_counter] (that is to say, without the ampersand), even_counter would have been captured by value.

We can capture more than one variable, separating them with commas:

// this lambda captures i by value and j and k by reference.
[i, &j, &k] (...) { ... };

Context variables captured by value can't be modified in the body of the lambda unless we add the "mutable" modifier to the lambda definition:

Capturing by value happens at lambda definition. If I modified p in the above example after the lambda is defined but before it's executed, the inner p would end with the same value (6, in the example).

Lambdas are similar to functors: the variables captured by reference are like private pointers, and the ones captured by value are like private variables.

There are some shortcuts: "[&]" means "capture all variables accessible in scope by reference". "[=]" does the same, but by value. If the lambda is defined inside a class, by capturing "[this]" we get access to any class member, including private ones.

Is it acceptable to break class encapsulation from inside a lambda expression? Yes, as long as it makes more sense to define the function within the context of a class function, rather than on its own.

The returned type of a lambda is inferred when the body consists only of a single return statement (similar to the repurposed auto declarator in C++11). It's assumed to be void otherwise, so the body can't contain any return statement. If that's not the case, it must be specified. For example:

Lambda Benefits
Lambdas are lightweight, nameless functions that can be defined just-in-place where they are used. Lambdas didn't come to C++ as a specific-purpose feature (i.e., functional programming): the existing STL components, like the for_each algorithm, and its new ones like <thread> or <future>, allow you to specify function arguments as lambdas beside C-like functions and functors. Unless you need a function available in more than one place, you'll feel more comfortable using lambdas as arguments of STL components, similar to the simplification gained in Listing 4 compared with Listing 3.

In a future article, I'll show how lambdas can be easily used to implement high-order functions. These are functions that either receive or return functions, and eventually both. Stay tuned!

About the Author

Diego Dagum is a software architect and developer with more than 20 years of experience. He can be reached at email@diegodagum.com.