Lambda Expressions

Lambda expressions allow you to define anonymous functions in your C++ code. A good way to demonstrate lambda expressions in practice is to show how they can be used in conjunction with some of the algorithms in the standard library.

For example, here is how we would sort a list of integers in C++03:

bool compare(int i, int j)
{
  return i < j;
}

int main()
{
  std::vector<int> digits = { 3, 1, 4, 1, 5, 9 };

  // Sort elements using a named function.
  std::sort(digits.begin(), digits.end(), compare);
}

In this example, we had to create a named function calledcompare() se we could tell the sort() function what sort criterion to use.

In C++11, we can streamline the process by using a lambda expression:

  // Sort elements using a lambda expression. 
  std::sort(digits.begin(), digits.end(), [](int i, int j) {return i < j; });

In the above example, the compiler automatically deduces that the return type of the lambda expression should be bool.

However, we can also explicitly specify the return type with the following syntax:

  // Specify return type
  std::sort(digits.begin(), digits.end(), [](int i, int j) -> bool {return i < j; });
}

Captured variables

A lambda expression is able to reference variables in the enclosing scope. These are called captured variables.

Consider the following example which uses std::transform() offset (by 2) all the values in a collection of integers:

std::vector<int> digits = { 3, 1, 4, 1, 5, 9 };
std::transform(digits.begin(), digits.end(), digits.begin(), [](int x) {return x + 2; });

Instead of offsetting by 2, suppose we want to offset the entire array by the value of a variable called offset. We could try the following:

int offset = 100;
std::vector<int> digits = { 3, 1, 4, 1, 5, 9 };
std::transform(digits.begin(), digits.end(), digits.begin(), [](int x) {return x + offset; });

However, this code will lead to a compilation error. In this example, offset is a captured variable as it is defined in the enclosing scope. When using captured variables, you need to tell the compiler whether you intended to capture the value of the variable or its reference. Since we did not specify the type of capture, the compiler does not know our intention.

The key to this is the capture clause which in the above examples is the empty square brackets [] we placed before the anonymous function. If we are not capturing any variables, we can leave the capture clause empty. However, to specify the default type of capture, we would use one of the following capture clauses:

  • [=] : capture variables by value,
  • [&] : capture variables by reference.

Returning to our example, here are the two types of capture:

// Capture by value
std::transform(digits.begin(), digits.end(), digits.begin(), [=](int x) {return x + offset; });

// Capture by reference
std::transform(digits.begin(), digits.end(), digits.begin(), [&](int x) {return x + offset; });

Note that the = or & symbol defines the default capture type. If we have more than one captured variable, we might want to capture some of them by value and some of them by reference. This can by specified as a comma separated list after the default capture, e.g.:

  • [=, &x] : Capture all variables by value except for x.
  • [&, x] : Captures all variables by reference except for x.

Summary

In this post, we have shown how lambda expressions can be used in C++11 to define anonymous functions. Lambda expressions are particularly useful in conjunction with certain algorithms such as sort(), transform() and for_each() where you would typically need to specify a function pointer or functor.