Comprehensive C++ Lambda Overview

This article is intended to be a comprehensive but still understandable and beginner-friendly overview over lambda functions in C++.

Any section marked as "advanced" can be safely skipped, although they should be readable enough to understand for intermediate C++ developers as well.

Please be aware that most of these features require at least C++11 support (g++ and clang++ activate it via the -std=c++11 argument) and generalized lambda captures even require C++14 support (g++ and clang++ activate it via the -std=c++14 argument).

What are Lambda Functions?

Lambda functions are derived from the functions used in λ-calculus, which is a formal model to define computability. λ-calculus is intentionally simple, to allow for easy reasoning. Functions in λ-calculus do not have a name, have zero or more parameters and can access surrounding variables.

Several programming languages have recognized the usefulness of such functions, beginning with those traditionally called "functional" programming language (e.g. Haskell). In recent past, more imperative languages have been acquiring concepts from functional programming, including lambda functions.

In the case of C++, lambda functions have been around explicitly since C++11, although it has been possible to write similar constructs since the beginning1. With C++14, lambda functions have been extended to support additional syntax.

Generally, C++ lambdas provide a way to concisely create an anonymous class-type, define and initialize its members and an overloaded function call operator. This means that you don't know the type of a lambda function, forcing you to use auto variables or templates (e.g., those in the standard library) when passing lambda functions around.

Basic Syntax

Lambda expressions are C++ expressions that create an instance of such a lambda function. This can then be used as a normal expression (e.g., assigned to a variable or passed to another function). The most general syntax of C++14 lambda expressions is:

[capture-list] (parameter-list) mutable exception attribute -> return_type { body }

However, everything except for the capture list and the body is optional – and both of them may be empty. This leads to the following minimal lambda function:

[]{} // Very boring: Does nothing when called

The most obvious thing that one can do with an anonymous function is call it. Since lambda expressions are expressions, we can immediately call them:

[]{}() // An elaborate way to do nothing

As a lambda expression is just an expression returning an object of some type, we can also do various other things with it, like binding it to a variable. The type of a lambda function is anonymous2, so we have to bind it to an auto variable, or use a polymorphic wrapper like ::std::function:

auto do_nothing = []{};  
::std::function<int()> return_4 = []{ return 4; };

We will now explore each element of the basic syntax step-by-step, starting with the body.

Body

Well, obviously we want a function – lambda or not – to do stuff. The body of a lambda function works just like the body of an ordinary function, and can contain a list of operations on surrounding static variables:

#include <iostream>  
int main() {  
  static int x = 0;
  []{
    ++x;
    ::std::cout << x << "\n";
    return; // return to main
  }(); // note: call it immediately
  ::std::cout << "still alive\n";
}

The output of this program would be:

1  
still alive

As you can see in the following "Advanced Semantics" info, the return type is by default auto, causing the compiler to figure it out by itself, and us to return stuff from lambda functions. This means we can also return something more interesting from a lambda function:

#include <iostream>  
int main() {  
  static int x = 0;
  auto f = []{ return ++x; }; // we don't know the type of a lambda
  ::std::cout << f() << "\n";
  ::std::cout << f() << "\n";
  ::std::cout << f() << "\n";
}

The output of this program would be:

1  
2  
3

Advanced Semantics

A lambda expression with body body is defined as creating an anonymous type like the following:

[]{body};  
// creates the type:
struct __anonymous { // the name is really anonymous not "__anonymous"...  
  auto operator()() const { // note the const!
    body
  }
};

And instantiating an object of that anonymous type (here called __anonymous for better understanding):

// and an object:  
__anonymous();  

Parameter List

Similar to ordinary functions we can also give it a list of parameters. Omitting the whole (parameter-list) clause is equivalent to specifying an empty parameter list (i.e. ()).

There is an additional wrinkle, though: Unlike for normal functions, it is legal to specify a parameter as auto, which works similar to auto variables. For example the following is a valid lambda expression to sum two whatevers3: [](auto a, auto b) { return a + b; }

Useful Example

We now have enough parts of the puzzle to build a working example. Remember: We have a parameter list, a body and the return type is (so far) deduced automatically by the compiler.

When working with the standard library, it is very helpful to know that you can pass lambda functions any time a functor is expected. For example as a comparison function to ::std::sort:

// ...  
int main() {  
  int array[] = {1, 3, 2, 4};
  auto begin = ::std::begin(array);
  auto end = ::std::end(array);

  ::std::sort(begin, end);
  print_array(::std::cout, array) << "\n";

  ::std::sort(begin, end, [](int lhs, int rhs) { return lhs > rhs; });
  print_array(::std::cout, array) << "\n";

  ::std::sort(begin, end, [](int lhs, int rhs) {
    if(lhs%2 != rhs%2) return lhs%2 < rhs%2;
    else return lhs > rhs;
  });
  print_array(::std::cout, array) << "\n";
}

Live version at ideone

Advanced Semantics

A lambda expression with body body and parameter list parameter-list is defined as creating a type like the following:

[](parameter-list){body};  
// creates the type:
struct __anonymous {  
  auto operator()(parameter-list) const {
    body
  }
};
// and an object:
__anonymous();  

For each parameter type that is auto, a template parameter is added to the function call operator, like so:

[](auto a, auto const& b){body};  
// creates the type:
struct __anonymous {  
  template
  auto operator()(T a, U const& b) const {
    body
  }
};
// and an object:
__anonymous();  

Of course, you do not have to worry about the template names clashing with anything else.

Explicit Return Types

By default (that is, when the -> return_type specification is omitted), the return type of a lambda function is automatically deduced by the compiler. If you use several return statements in one lambda expression, make sure that their types match, or the deduction might do something unexpected or fail completely.

If an return type specification is given, everything does what you would expect, for example []() -> int { return 'a'; } defines a lambda function that returns an int. Note that adding an explicit return type requires having a parameter list, although it may be empty.

Advanced Semantics

A lambda expression with a return type specifier that gives the return type as return_type is defined as creating a type like the following:

[](parameter-list) -> return_type {body};  
// creates the type:
struct __anonymous {  
  auto operator()(parameter-list) const -> return_type {
    body
  }
};
// and an object:
__anonymous();  

Please note the usage of a trailing return type specifier, as the parameters are in scope for usage with decltype.

Capture Lists

So far, we can only use static or global variables, but one of the main features of lambda functions is that to use any surrounding variable. This is achieved in C++ by the capture mechanism with which surrounding variables may be captured for use in the lambda expression. Generally speaking, capturing can be done by reference to the original variable or by value, which copies the original value.

To capture x by reference and y by value, we would simply add &x, y to the capture list. Capturing by value makes the object implicitly const (see "Advanced Semantics" below).

Additionally, it is possible to give a "capture everything" specifier that will capture everything used in the lambda expression. Use & to capture everything by value or = to capture everything by value. An example:

int x, y; // something to capture  
[&]{ x = 0; y = 0; }(); // capture everything by ref and initialize x and y
auto add1 = [x]{int a}{ return x + a; }; // capture x by value  
[&x]{ x = 4; }(); // capture x by ref
add1(1); // returns 2, as x was captured by value  
// [&x]{ x = 0; y = 0; }(); // illegal: y not known

Finally, there is a bit of additional fun when capturing variables in member functions. Capturing the scope of the member function can be done easily by capturing this or capturing everything, but individual members cannot be easily captured without resorting to the general capture syntax described in the next section:

struct S {  
  int x = 0;

  // OK: Just let the compiler deal with it
  auto f() { return [&] { return x; }; }

  // OK: Capture the object context
  auto g() { return [this] { return x; }; }

  // Won't work: Tries to capture "x" instead of "this->x"
  //auto h() { return [&x] { return x; }; }

  // Won't work: Syntax error
  //auto i() { return [&this->x] { return x; }; }
};

Note that, as this is a pointer, it is enough to capture it by value (and in fact impossible to capture it by reference).

Generalized Captures

A new feature with C++14, it is now possible to capture any kind of expression. Why would you want that? Well, in the previous example we tried to capture this->x — and failed. The syntax is very simple: We simply use the syntax we already know and assign it a value. For example, [x = 42]{ return x; } is a legal lambda expression.

There really is nothing more to say except that capturing by reference us still possible, allowing is to e.g. write [&x = some_array[compute_an_index()]]{ return x; }.

Some examples:

[x = 42] { return x; }; // capture 42 by value  
[&x = this->x] { return x; }; // capture member by ref
::std::ofstream file("output.txt");
[file = ::std::move(file)]{}; // move-capture file

Advanced Semantics

Perhaps the most important semantic with respect to captures is that if the capture list is empty, a lambda may be bound to a function pointer of appropriate type:

using T = double(*)(int, char);  
T t = [](int x, char y) -> double { return x + y; };  

The actual transformation mandated by the standard works along the following lines: For each capture a member of appropriate type (semantics as with auto variables) is added. The capture expressions are then passed to the implicit constructor to initialize the variables:

[capture-list](parameter-list) -> return_type {body};  
// creates the type:
struct __anonymous {  
  capture-members
  __anonymous(capture-list) : capture-init { }

  // Note the const on the operator below,
  // it forbids modifying the actual members:
  auto operator()(parameter-list) const -> return_type {
    body
  }
};
// and an object:
__anonymous{/* capture expressions */};  

To give a more specific example:

[&x = some_array[42]]{ return x; };  
// creates the type:
struct __anonymous {  
  int& x;
  __anonymous(int& x) : x(x) { }

  auto operator()() const {
    return x;
  }
};
// and an object:
__anonymous{some_array[42]};  

Mutability

To explain the mutable keyword for lambda expressions, let us take a little detour to an invalid lambda. When compiling [x]{ ++x; }, your compiler should reject your program while telling you that it cannot modify x. In fact, my compiler already tells me a bit more:

test.cpp:3:7: error: cannot assign to a variable captured by copy in a non-mutable lambda
        [x]{ ++x; };
        ^~~~~~~~~~~~~~~~~~~~~~

Without resorting to advanced semantics, we can simply say that it is not legal to modify values captured by value without making the lambda mutable:

int main() {  
  int x = 0;
  // [x]{ ++x; }; // Forbidden!
  [x]() mutable {++x}; // OK

One final wrinkle: Just as explicit return type specifications, exception specifiers and attributes it requires a (possibly empty) parameter list to be specified as well.

Advanced Semantics

The mutable keyword controls whether the function application operator is marked const, or not. It does not mark the member variables as mutable! To see the implications of this, consider the following program that passes a functor via const& and then attempts to call it:

template<typename F>  
void apply(F const& f) {  
  f();
}

struct A {  
  mutable int x;
  A(int x) : x(x) { }
  auto operator()() const {
    ++x;
  }
};

int main() {  
  int x;
  auto f = [x]() mutable { ++x; };
  S g = {x};
  //apply(f); // Error: No matching function to call
  apply(g); // OK: operator()() can be called on a const object
}

Exception Specification

The final part is easy, because it works just as expected. Similar to the mutable specification above, a parameter list has to exist, but then the exception specifier just does what one would expect:

[]() noexcept { }; // guaranteed to never throw  

More complex exception specifiers are possible, and the arguments may be used (e.g., to create a noexcept specification that depends on an auto argument being noexcept-copyable). Just as explicit return type specifications, mutable and attributes it requires a (possibly empty) parameter list to be specified.

Attributes

It is also possible to add attributes, but they are applied to the type of the lambda expression, not the function they seem to be attributing. It is not possible therefore to annotate a lambda function as e.g. [[noreturn]] by writing something like:

//[]() [[noreturn]] { for(;;) { } }; // wrong, a type cannot be noreturn  

You are probably not going to use this anytime soon. Just as explicit return type specifications, mutable and exception specifiers it requires a (possibly empty) parameter list to be specified.

Summary

This article explored lambda functions, which are anonymous functions that can capture variables as they exist when they are declared, including objects on the stack4. It also explored C++14 generalized lambda captures by way of which we can capture the result of arbitrary expressions. In the "Advanced Semantics" sections, it also provides a more exact version of how the compiler understands lambda functions.

All together, it provides you with everything you need to know to start using lambda functions in your projects right now!

Footnotes:

  1. An anonymous class-type with an overload for the function application operator. Example.

  2. In fact, lambda expressions cannot be used in some contexts where other expressions can be used to prevent their type to be used directly, especially in function signatures.

  3. Yes, "whatevers" is definitely a technical term. Note also that the two whatevers in the example do not have to be the same type of whatever.

  4. Formally, object with automatic storage duration. By the way: Objects with dynamic storage duration can be captured via generalized lambda captures as well.

Daniel Schemmel

is currently employed at the Chair of Communication and Distributed Systems at RWTH Aachen University, where he researchs the testability of distributed systems. He can be reached at blog(at)gha.st.

Aachen, Germany, Terra, Sol, Milky Way, Laniakea SC https://gha.st/about/