Lambda Functions in C++

In this article, we will discuss what C++11 Lambda Functions are and how to use Lambda Functions as callbacks. Also, what new things were added in Lambda Functions in C++14 / 17.

What is a Lambda Function?

Imagine a lambda function as a regular function with a twist: it can accept arguments and return results but, unlike standard functions, it does not have a name. This feature is particularly useful for creating concise and temporary functions for specific, often short-lived, tasks such as callbacks to other functions or APIs.

Syntax of Lambda Expression in C++

General structure of a lambda expression is as follows:

[capture](parameters) mutable exception -> return_type {
    // function body
}

Let’s look at each part of the lambda syntax:

1. Capture Clause: [capture]

This is how you can define what from the outside scope is available inside the lambda, and how (by value, by reference, etc.). The capture clause can be one of the following:

  • []: Nothing is captured.
  • [x, &y]: x is captured by value and y is captured by reference.
  • [=]: All variables in scope are captured by value.
  • [&]: All variables in scope are captured by reference.
  • [this]: The current object (*this) is captured by reference.

2. Parameter List: (parameters)


Frequently Asked:


Just like regular functions, lambdas can have parameters. This part can be omitted if the lambda takes no parameters:

[] { /* ... */ } // Lambda with no parameters

3. Mutable Specification: mutable

If you want to modify the values captured by value, you can add the mutable keyword. By default, a lambda that captures variables by value cannot modify them:

[x]() mutable { x = 42; } // OK, x can be modified because of 'mutable'

4. Exception Specification

This part allows you to specify the exceptions that a lambda can throw. It’s often omitted in basic use cases.

[]() noexcept { /* ... */ } // Lambda that does not throw exceptions

5. Return Type: -> return_type

The return type of a lambda function can be explicitly specified after the parameters. If it’s not provided, the compiler will infer the return type based on the return statement in the lambda body:

[]() -> int { return 42; } // Lambda that explicitly specifies return type as int

However, if the body of the lambda consists of a single return statement (or a statement that could return), the return type can be omitted and the compiler will deduce it:

[] { return 42; } // Return type is deduced to be int

6. Lambda Function Body

Inside the curly braces {} is the body of the lambda function. It can contain any valid C++ statements, including variable declarations, loops, and other lambdas:

[](int x) {
    std::cout << "The value is: " << x << std::endl;
}

Putting It All Together to create a Lambda Function

Here’s a lambda function example that captures a local variable y by reference and takes one argument x. It’s marked as mutable, meaning it can modify the captured variable, and it specifies no exception and no return type:

int y = 10;
auto lambda = [&y](int x) mutable {
    y = x * y; // This is valid because y is captured by reference and the lambda is 'mutable'
    std::cout << y << std::endl;
};
lambda(5); // This will output 50

By properly using these small components of a lambda expression we can create a lambda functions in C++ more effectively.

Why Do We Need Lambda Functions?

To understand the utility of lambda functions, let’s consider a scenario where we have an array of integers that we wish to iterate over and print each value. Prior to C++11, you might use a function pointer with the standard template library (STL) algorithm std::for_each to achieve this.

Here’s how you could do it:

#include <iostream>
#include <algorithm>

void display(int a) {
    std::cout << a << " ";
}

int main() {
    int arr[] = { 1, 2, 3, 4, 5 };
    std::for_each(arr, arr + sizeof(arr) / sizeof(int), &display);
    std::cout << std::endl;
}

Output:

1 2 3 4 5

The example above creates a separate function just to display an integer. While it gets the job done, it’s a bit of an overhead for such a simple task. Could there be a more inline and concise way to handle this? Yes, with lambda functions!

The Rise of Lambda Functions

Lambda functions can significantly reduce the overhead of creating small, single-use functions. They can be used directly in-place where they are needed. Here’s the syntax of a lambda function:

[](int x) {
    std::cout << x << " ";
}

In this syntax:
[] is the capture clause used to define which outside variables are available inside the function scope.
(int x) is the parameter list, like in normal functions.

Using the same example with a lambda function, the code looks like this:

#include <iostream>
#include <algorithm>

int main()
{
    int arr[] = {1, 2, 3, 4, 5};
    std::for_each(arr, arr + sizeof(arr) / sizeof(int), [](int x)
                  { std::cout << x << " "; });
    std::cout << std::endl;
}

Output:

1 2 3 4 5

How to Use Outer Scope Elements in Lambda Functions

Sometimes, you might want your lambda function to access variables from its enclosing scope. This is where the capture clause comes into play. There are two main ways to capture variables:

Case 1: By Value [=]

When you use [=], you’re capturing all the variables available in the scope by value.

[=](int &x) {
    // All outer scope elements are captured by value
}

Case 2: By Reference [&]

Alternatively, [&] captures all the variables by reference.

[&](int &x) {
    // All outer scope elements are captured by reference
}

Let’s see some example demonstrating both capture methods:

Capture by Reference in Lambda

We use a reference symbol in the capture clause, i.e., [&], to enable the access of all outside variables in the lambda function scope. We can even modify the value of captured variables; changes made in them will be permanent and will be visible even after the lambda function ends. It’s because we are making them available as a reference in the lambda function.

Let’s see an example where we have an int array and an int value discount, and in the lambda function, we will capture them as a reference, so any changes made in them will be available to the outer scope. Let’s see the example:

#include <iostream>
#include <algorithm>

int main()
{
    int arr[] = {1, 2, 3, 4, 5};
    int discount = 50;

    // Capture by reference
    std::for_each(arr,
                  arr + sizeof(arr) / sizeof(int),
                  [&](int x) {
                        std::cout << x << " ";
                        // We can modify 'discount' here since it's captured by reference.
                        discount = 20;
                  });

    std::cout << std::endl;

    std::cout << "discount Vaue: " << discount << std::endl;
    return 0;
}

Output:

1 2 3 4 5 
discount Vaue: 20

Capture by Value in Lambda

We use an ‘=’ symbol in the capture clause, i.e., [=], to enable access to outside variables in the lambda function scope by value. This will expose the outside variables in the lambda function scope as read-only; you can read them, but you cannot modify them. If you use the mutable keyword, then you can modify the value, but it will have no effect on the actual variable outside the lambda function because variables are copied into the lambda function scope by value.

Let’s see an example where we have an int array and an int value discount, and in the lambda function, we will capture them as a value and with the mutable keyword, so any changes made to them will have no effect on the actual variables outside the lambda function.

#include <iostream>
#include <algorithm>

int main()
{
    int arr[] = {1, 2, 3, 4, 5};
    int discount = 50;

    // Capture by Value
    std::for_each(arr,
                  arr + sizeof(arr) / sizeof(int),
                  [=](int x) mutable {
                        std::cout << x << " ";
                        // Cannot modify 'discount' here since it's captured by value,
                        // unless 'mutable' is used.
                        discount = 20;
                  });

    std::cout << std::endl;

    std::cout << "discount Vaue: " << discount << std::endl;
    return 0;
}

Output:

1 2 3 4 5 
discount Vaue: 50

In this example, notice that mutable is used in the capture by value example. The mutable keyword allows us to modify the copies of the captured items, which otherwise would be read-only.

Capture Specific Variables by Reference in Lambda

We use a reference symbol in the capture clause, i.e., [&], to enable the access of all outside variable in the lambda function scope, but if you want to have a specific variable only then you can use the variable name along with ampersend like, [&discount], in this case only one variable discount will be captured in lambda function by reference.

For example

#include <iostream>
#include <algorithm>

int main()
{
    int arr[] = {1, 2, 3, 4, 5};
    int discount = 50;

    // Capture by reference
    std::for_each(arr,
                  arr + sizeof(arr) / sizeof(int),
                  [&discount](int x) {
                        std::cout << x << " ";
                        // We can modify 'discount' here since it's captured by reference.
                        discount = 20;
                  });

    std::cout << std::endl;

    std::cout << "discount Vaue: " << discount << std::endl;
    return 0;
}

Output:

1 2 3 4 5 
discount Vaue: 20

Summary

Lambda functions in C++11 offer a concise and flexible way to write inline anonymous functions. They are especially useful when you need a simple function

9 thoughts on “Lambda Functions in C++”

  1. Excellent tutorial, no words to say. Thank you so much for writing this article with such ease. I was struggling to understand lambda functions before going through this Article.Thank you sooooooooo much, you made by job very easy

  2. Simple amazing!

    I’ve been struggling to learn Lambda, watched videos on YouTube / read articles but nothing seemed to make sense. Then I came across this site and baam! everything was crystal clear.

    Thank you so much taking out the time do the tutorials!

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Scroll to Top