Back

Lambda Operator in C++

Today we are going to talk about a concept introduced in C++11 called lambda expressions (sometimes also referred to as closures).

Many programming languages support the concept of anonymous functions - functions that have a body, but do not have a name.  A lambda expression is a programming technique that is related to anonymous functions - you can write a lambda function inline in your source code.  We can think of a lambda expression as an anonymous function that maintains state and that can access the variables that are available to the enclosing scope.  The very concept of a lambda function originates in the lambda calculus and functional programming.

Let's see what we need to define a lambda function:

  • The capture list
    The capture list is a list of captures: it defines what should be available in the lambda function from the outside.
    A capture can either be a capture by value or a capture by reference:
    1. [x] - a value
    2. [&x] - a reference
    3. [&] - any variable currently in scope by reference
    4. [=] - any variable currently in scope by value

  • The argument list
    The argument list is the same as with any other function in C++.

  • The body
    This is the part of the code that will actually be executed when we call the lambda function.

 

Let's see some examples:
First, in order to use lambda expressions, we have to write

#include <functional>
Lambda expression:
auto f = [](int a, int b){return a + b;};

The auto keyword specifies that the type of the variable that is being declared will be automatically deduced from its initializer.  For functions, it specifies that the return type is a trailing return type or will be deduced from its return statements.

In our example, the compiler will deduce that we have declared a function: precisely, a function with the following signature:

std::function<int(int, int)>
This enables us to write things like:
auto x = [](int a, int b){return a + b;}(2, 3); // int x = 5
We can also specify the return type like this:
auto x = [](int a, int b) -> double {return a + b;}(2,3);

/*If we check the type of x, we see that it's, in fact, a double*/
std::cout << typeid(x).name(); //d
An example with capturing:
int main()
{
  int a = 2;
  auto f = [&](int x){return x + a;}; //capturing everything in scope by reference; we could have also written [&a] to capture only a
  int y = f(1);
  std::cout << y; //3
}

 

Of course, these examples are trivial and it might be difficult to see the advantage of using lambda expressions.  For the end of this article, consider this next example which should give you an idea of how lambdas can be used.

int array[] = { ... };

std::sort(array, array + sizeof(array), [](int a, int b) {return a > b;}); //the lambda function determines the criteria on which the array will be sorted

/*or if we have an array of points...*/
std::sort(array, array + sizeof(array), [](Point a, Point b) {return a.getX() < b.getX();});

Informally speaking, the main advantage of an anonymous function is that it is a full scale object that can be taken and sent somewhere.  Anonymous functions are popular now because of their suitability for big data applications.  One function can be run over a list to get another list, then another function can be run, then another, then finally grouping the elements with last function and so on – essentially, feeding a list with functions, rather than feeding a function with lists (as in more traditional programming).