Skip to content
Laviyx Learn

Try

C++ course menu

C++ Lambdas: Syntax, Captures, mutable and std::function

Learn C++ lambda expressions: the [](){} syntax, using lambdas with STL algorithms, capturing variables by value and by reference, [=] and [&], mutable lambdas, generic lambdas with auto, storing lambdas in std::function, and dangling captures.

Advanced10 min readUpdated
On this page

A lambda is a small, unnamed function written right where you need it. Lambdas are everywhere in modern C++: as comparison rules for std::sort, conditions for std::count_if, callbacks, and tasks for threads.

Syntax#

C++
[captures](parameters) -> return_type { body }

The return type is usually left out; the compiler deduces it from the return statement:

C++
#include <iostream>

int main() {
    auto square = [](int x) { return x * x; };
    auto greet = []() { std::cout << "Hello from a lambda\n"; };

    std::cout << square(7) << '\n';
    greet();
}
Output
49
Hello from a lambda

auto square = ... stores the lambda in a variable, which you can then call like a function.

Lambdas with algorithms#

Most often, a lambda is passed straight to an algorithm:

C++
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>

int main() {
    std::vector<std::string> names = {"Meera", "Asha", "Ravindra", "Jo"};

    std::sort(names.begin(), names.end(),
              [](const std::string& a, const std::string& b) { return a.size() < b.size(); });

    for (const auto& n : names) std::cout << n << ' ';
    std::cout << '\n';

    auto long_names = std::count_if(names.begin(), names.end(),
                                    [](const std::string& n) { return n.size() > 4; });
    std::cout << long_names << '\n';
}
Output
Jo Asha Meera Ravindra
2

Captures: using outside variables#

A lambda cannot use local variables from the surrounding function unless it captures them in the square brackets:

C++
#include <algorithm>
#include <iostream>
#include <vector>

int main() {
    std::vector<int> marks = {56, 91, 78, 34, 88};
    int pass_mark = 60;

    auto passed = std::count_if(marks.begin(), marks.end(),
                                [pass_mark](int m) { return m >= pass_mark; });
    std::cout << passed << " passed\n";
}
Output
3 passed
CaptureMeaning
[]Capture nothing
[x]Copy x into the lambda
[&x]Refer to x (changes affect the original)
[=]Copy every variable used
[&]Refer to every variable used
[this]Access the current object's members (inside a member function)
[x, &total]Mix: copy x, refer to total

By value vs by reference#

A copy is taken when the lambda is created; a reference sees the current value when the lambda runs:

C++
#include <iostream>

int main() {
    int rate = 10;
    auto by_value = [rate](int x) { return x * rate; };
    auto by_ref = [&rate](int x) { return x * rate; };

    rate = 20;
    std::cout << by_value(5) << ' ' << by_ref(5) << '\n';
}
Output
50 100

Capturing by reference is how a lambda updates an outside variable:

C++
#include <iostream>
#include <vector>

int main() {
    std::vector<int> v = {3, 8, 2};
    int total = 0;

    for (int x : v) {
        [&total](int value) { total += value; }(x);   // define and call immediately
    }
    std::cout << total << '\n';
}
Output
13

Prefer listing captures explicitly ([pass_mark], [&total]) over [=] and [&]: it makes clear what the lambda depends on.

mutable: changing a captured copy#

Copies captured by value are const inside the lambda. mutable allows changing the lambda's own copy, which persists between calls to that lambda:

C++
#include <iostream>

int main() {
    auto counter = [count = 0]() mutable { return ++count; };

    std::cout << counter() << ' ' << counter() << ' ' << counter() << '\n';
}
Output
1 2 3

[count = 0] is an init capture (C++14): it creates a new variable inside the lambda. It is also how you move a value into a lambda: [data = std::move(data)].

Generic lambdas#

Using auto for parameters makes a lambda work with any type (a short way to write a template):

C++
#include <iostream>
#include <string>

int main() {
    auto twice = [](const auto& x) { return x + x; };

    std::cout << twice(21) << ' ' << twice(1.5) << ' ' << twice(std::string("ab")) << '\n';
}
Output
42 3 abab

Storing lambdas: std::function#

Every lambda has its own unique type, so to store different lambdas in the same variable or container, use std::function (from <functional>), which can hold any callable with a matching signature:

C++
#include <functional>
#include <iostream>
#include <map>
#include <string>

int main() {
    std::map<std::string, std::function<double(double, double)>> ops = {
        {"add", [](double a, double b) { return a + b; }},
        {"sub", [](double a, double b) { return a - b; }},
        {"mul", [](double a, double b) { return a * b; }},
    };

    for (const auto& [name, op] : ops) {
        std::cout << name << ": " << op(8, 2) << '\n';
    }
}
Output
add: 10
mul: 16
sub: 6

std::function<double(double, double)> means "a callable that takes two doubles and returns a double". It has a small overhead, so when a function just accepts a callable, a template or auto parameter is faster.

Returning a lambda#

A function can create and return a lambda that remembers a captured value:

C++
#include <iostream>

auto make_multiplier(int factor) {
    return [factor](int x) { return x * factor; };
}

int main() {
    auto triple = make_multiplier(3);
    auto times10 = make_multiplier(10);
    std::cout << triple(7) << ' ' << times10(7) << '\n';
}
Output
21 70

Here factor must be captured by value: a reference to the parameter would dangle once make_multiplier returns.

Common mistakes#

Practice#

Practice exercise

Given std::vector<int> nums = {4, 11, 7, 20, 15, 2}; and a threshold int limit = 10;, use lambdas to: count numbers above limit, double every number in place with std::for_each, and print the result.

Show solution
C++
#include <algorithm>
#include <iostream>
#include <vector>

int main() {
    std::vector<int> nums = {4, 11, 7, 20, 15, 2};
    int limit = 10;

    auto above = std::count_if(nums.begin(), nums.end(), [limit](int n) { return n > limit; });
    std::cout << above << " above " << limit << '\n';

    std::for_each(nums.begin(), nums.end(), [](int& n) { n *= 2; });
    for (int n : nums) std::cout << n << ' ';
    std::cout << '\n';
}
Output
3 above 10
8 22 14 40 30 4

Key takeaway#

Finished this lesson?