Skip to content

Lambda expressions

[Syntax]
[Wrapping a lambda in a lambda]
[Use std::function as a polymorphic wrapper]
[Concatenate lambdas with recursion]
[Combine predicates with logical conjunction]
[Call multiple lambdas with same input]
[Use mapped lambdas for a jump table]
[Pass copy of object to lambda]
[Improvements for several C++ versions]

A lambda is an anonymous function. The following basic example shows a lambda without params and a second lambda which expects as param a lambda (or function) needing no params:

    const char* helloTxt = "Hello\n";
    auto greeting = [helloTxt] {return helloTxt; };
    auto write = [](auto genMsg) {std::cout << genMsg(); };
    write(greeting);
    write([] {return "Hi from anonymous lambda without name\n"; });

Output:

Hello
Hi from anonymous lambda without name

In the example above lambda greeting needs access to the variable helloTxt and gets access by defining the variable as a capture within square brackets. The ability to capture variables outside its own scope is what makes a lambda a closure (= function that allows the use of symbols outside its own lexical scope).

Syntax

You can define lambda greeting with more details:

auto greeting = []()-> const char * {return helloTxt;};

In most cases the compiler can determine the return type via automatic type deduction and you can omit the trailing return type syntax with “-> someType“.

A minimum lambda which captures nothing, has no params and does nothing is given by:

[]{}

More on captures

If you want to change some outside variable, you have to capture it by reference:

    int someCounter{ 42 };
    auto increment = [&someCounter] {std::cout << std::format("{}\n", ++someCounter); };
    increment();
    increment();
    std::cout << std::format("{} (within outer scope)\n", someCounter);

Output:

43
44
44 (within outer scope)

With [=] you capture all outside variables as copies. With [&] you capture all outside variables as references. To be able to access class members you have to capture this or *this.

With attribute mutable you can change even variables captured as copies within the lambda. But within outer scope no changes occur:

    int otherCounter{ 42 };
    auto increment2 = [otherCounter] () mutable {std::cout << std::format("{}\n", ++otherCounter); };
    increment2();
    increment2();
    std::cout << std::format("{} (within outer scope)\n", otherCounter);

Output:

43
44
42 (within outer scope)

You can also define a local capture variable that maintains its state. Here mutable must also be specified otherwise a change would not be possible:

    auto increment3 = [localCounter = 42]() mutable {std::cout << std::format("{}\n", ++localCounter); };
    increment3();
    increment3();

Output:

43
44

Wrapping a lambda in a lambda

You can parametrize a function / lambda to return a specific lambda for the given parameter:

    const std::vector<int> v{1, 7, 4, 9, 4, 8, 12, 10, 20};
    std::string foundNumbers;

    // Wrapping the lambda into an other lambda
    auto isDivBy = [&foundNumbers](int divisor)
    {
        return [&foundNumbers, divisor](int i) {
            if (i % divisor == 0)
            {
                foundNumbers += std::format("{} ", i);
                return true;
            };
            return false; };
    };

    for (int divisor : {3, 4, 5})
    {
        foundNumbers.clear();
        // Get the predicate / lambda function for the required divisor
        auto predicate = isDivBy(divisor);
        auto count = std::ranges::count_if(v, predicate);
        std::cout << std::format("{} numbers dividable by {}: {}\n", count, divisor, foundNumbers);
    }

Output:

2 numbers dividable by 3: 9 12
5 numbers dividable by 4: 4 4 8 12 20
2 numbers dividable by 5: 10 20

Use std::function as a polymorphic wrapper

You can store different specializations of a lambda within a vector using std::function.

The following example has 3 lambdas each storing values in a container of specific type:

    std::deque<int> d;
    std::list<int> l;
    std::vector<int> v;

    // a lambda to get a container specific anonymous lambda for storing
    auto storeInContainerFunc = [](auto& container)
    {
        return [&container](auto value) {container.push_back(value); };
    };

    const std::vector<std::function<void(int)>>
        storeFunctions {storeInContainerFunc(d), storeInContainerFunc(l), storeInContainerFunc(v)};

    for (auto& store : storeFunctions) // iterate over specific containers
        for (int i{ 0 }; i < 10; ++i)
            store(i);

    PrintContainer(d, "d");
    PrintContainer(l, "l");
    PrintContainer(v, "v");

Output:

d: 0 1 2 3 4 5 6 7 8 9
l: 0 1 2 3 4 5 6 7 8 9
v: 0 1 2 3 4 5 6 7 8 9

Concatenate lambdas with recursion

You can generically combine lambdas to a single lambda so that the output of one is the input of the next:

template <typename T, typename ...Ts>
auto concat(T t, Ts ...ts)
{
    if constexpr (sizeof...(ts) > 0) // number of elements in the parameter pack
    {
        return [&](auto ...parameters)
        {
            // recursively call concat again without first lambda t
            return t(concat(ts...)(parameters...));
        };
    }
    else
    {
        return t;
    }
}

The following example combines 3 calculating lamdas/funcs to a single lambda. Precondition for proper concatenation is, that the return value type of the preceding lambda matches to the required input value types of the next lambda:

    auto multiplyBy2 = [](auto i) {return i * 2; };
    auto multiplyBy3 = [](auto i) {return i * 3; };

    auto combinedCalc = concat(multiplyBy3, multiplyBy2, std::plus<int>{});

    // Calculation starts with std::plus which has two arguments
    std::cout << std::format("Calc result = {}", combinedCalc(2, 3));

Output:

Calc result = 30

Combine predicates with logical conjunction

Build a needed predicate function to work with a std algorithm (std::copy_if). The predicate function is built by a logical combination of two lambdas both accepting the same param type:

template <typename F, typename A, typename B>
auto combine (F binaryFunc, A a, B b)
{
    return [=](auto param) { return binaryFunc(a(param), b(param)); };
}

The example uses logical AND for combination:

    auto beginsWith_a = [](const std::string& s) {return s.find("a") == 0; };
    auto endsWith_b = [](const std::string& s) {return s.rfind("b") == s.length() - 1; };
    auto bool_and = [](const auto& l, const auto& r) {return l && r; };

    // Find matching strings from std::cin and write them to std::cout
    std::copy_if(std::istream_iterator<std::string>{std::cin}, {},
        std::ostream_iterator<std::string>{std::cout, " "},
        combine(bool_and, beginsWith_a, endsWith_b));
    std::cout << '\n';

Command window:

>echo aabb bbaa foo abzb | MyTestApp.exe
aabb abzb

Call multiple lambdas with same input

You can create multiple instances of a lambda with different capture values with use of a generating lambda function. The example displays several integers enclosed in all types of braces. For each brace type a separate lambda version is used:

   auto braces = [](const char a, const char b)
    {
        return [a, b](int param) {std::cout << std::format("{}{}{} ", a, param, b); };
    };

    // Get different lambdas for each specific brace type
    auto a = braces('(', ')');
    auto b = braces('[', ']');
    auto c = braces('{', '}');
    auto d = braces('|', '|');

    for (int i : {1, 2, 3, 4, 5})
    {
        for (auto writeWithSpecificBrace : { a, b, c, d }) writeWithSpecificBrace(i);
        std::cout << '\n';
    }

Output:

(1) [1] {1} |1|
(2) [2] {2} |2|
(3) [3] {3} |3|
(4) [4] {4} |4|
(5) [5] {5} |5|

Use mapped lambdas for a jump table

To select a corrresponding action depending on some (user) input a jump table can be used. In the following example the jump table is realized by storing appropriate lambda functions within a map. The key of the map is the single allowed input character:

Helper function to get single allowed charcter from user input

const char PromptForSingleChar(const char* p)
{
    std::string r;
    std::cout << std::format("{} > ", p);
    std::getline(std::cin, r, '\n');

    if (r.size() < 1) return '\0';
    if (r.size() > 1) return '\0';
    return std::toupper(r[0]);
}

Main code with jump table:

    using JumpFunc = void(*)();
    std::map<const char, JumpFunc> jumpMap {
        { 'A', [] {std::cout << "func A\n"; } },
        { 'B', [] {std::cout << "func B\n"; } },
        { 'C', [] {std::cout << "func C\n"; } },
        { 'D', [] {std::cout << "func D\n"; } },
        { 'X', [] {std::cout << "Bye!\n"; } }
    };

    char selChar{};
    while (selChar != 'X')
    {
        if (selChar = PromptForSingleChar("Enter A/B/C/D/X"))
        {
            auto it = jumpMap.find(selChar);
            if (it != jumpMap.end())
            {
                // call stored lambda
                it->second();
            }
            else
                std::cout << "Invalid response\n";
        }
    }

Command window:

Enter A/B/C/D/X > c
func C
Enter A/B/C/D/X > D
func D
Enter A/B/C/D/X > f
Invalid response
Enter A/B/C/D/X > x
Bye!

Pass copy of object to lambda

Instead of simply passing “this” to the lambda function it is also possible to pass a copy of the current object instance. Motivation: The lambda may be executed later when the internal state of the object already has changed or when the object has been destroyed.

class SomeClass
{
public:
    int m_data;
    auto DoSomethingAsnycLater()
    {
        return std::thread([*this]{
            // wait for something or some long enduring action
            // then access member data (of copied instance)
            m_data += ... ;});
    }
};

std::thread t;
{
    SomeClass myInstance;
    t = myInstance.DoSomethingLater();
}
t.join(); // wait here until thread function has processed on the copy of myInstance

Improvements for several C++ versions

  • typed lambda
    auto sumInt = [](int x, int y) {return x+y;)
    C++11, accepts only types convertible to int
  • generic lambda
    auto sumGen = [](auto x, auto y) {return x+y;)
    C++14, accepts all types
  • generic lambda + decltype
    auto sumDec = [](auto x, decltype(x) y) {return x+y;)
    C++14, second type must be convertible to the first type
    Example: sunDec(true, 2000) = 2 (second param is converted to bool (true), when adding bools they are promoted to int which causes true+true = 1+1 = 2
  • template lambda
    auto sumTem = []<typename T>(T x, T, y) {return x+y;}
    C++20, first and second parameter must have the same type

Template lambda with container types

  • auto lambdaGen = [](const auto& container) {return container.size();}
    supports any type with member function “size()”
  • auto lambdaVec = []<typename T>(const std::vecor<T>& vec) {return vec.size();}
    supports only std::vector of any type (int, double,…)
  • auto lambdaVecInt= []<std::integral T>(const std::vecor<T>& vec) {..}
    supports only std::vector of integral type (int, long,..)