C++ Lambda Functions

Published: September 15, 2025 | Tags: #c++ #cpp #programming #lambdas

Have you ever needed a quick function, but you think that declaring it somewhere else can be a bit of an overkill? In modern C++, the solution can be found from the world of functional programing. Lambda functions must it be.

Lambdas are anonymous, inline functions that you can define anywhere quickly. For short tasks it can be useful to improve the readability.

Let’s have some fun!

A cat on a laptop.


So… why do we need Lambdas?

Before lambdas were introduced in C++11, passing a simple operation to an algorithm was kinda messy.

You either had to define a full, named function somewhere else or create a functor (a struct with an overloaded operator()).

// Functor example, we can make an article for this.

struct myFunctor
{
    int operator() (int x, int y) const
    {
        return x + y;
    }
};

To talk about this, let’s sort a vector of custom objects. For example, imagine you have a Person struct and you want to sort a vector of people by age.

Using a function - the old way.

This works, but compareByAge is a single-use function that pretty much polutes the global namespace.

It’s also defined far away from where it’s actually used.

#include <iostream>
#include <vector>
#include <algorithm>  // For the C++ newbies, this is needed for std::sort

struct Person
{
    std::string name;
    int age;
};

// The function for the comparison.
bool compareByAge(const Person& a, const Person& b)
{
    return a.age < b.age;
}

int main()
{
    std::vector<Person> people = {
        {"Alice", 30},
        {"Bob", 25},
        {"Charlie", 35}
    };

    std::sort(people.begin(), people.end(), compareByAge);

    for (const auto& p : people)
    {
        std::cout << p.name << " (" << p.age << ")\n";
    }

    return 0;
}

Using a Lambda - the modern way.

Much cleaner! And is located exactly where we need it.

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

struct Person
{
    std::string name;
    int age;
};

int main()
{
    std::vector<Person> people = {
        {"Alice", 30},
        {"Bob", 25},
        {"Charlie", 35}
    };

    std::sort(people.begin(), people.end(), [](const Person& a, const Person& b)
        {
            return a.age < b.age;
        });

    for (const auto& p : people)
    {
        std::cout << "    " << p.name << " (" << p.age << ")\n";
    }

    return 0;
}

Anatomy of a Lambda (dissection time)

A lambda function has a few key parts.

[captures] (parameters) -> return_type { function_body }

  • [] Capture clause: it specifies which variables from the surrounding scope the lambda czn use. An empty [] means it captures nothing.

  • () Parameter list: just like a normal function’s parameter list. In our std::sort example, it was (const Person& a, const Person& b).

  • -> return_type (optional): the return type. The compiler can deduce this many times, therefore, it’s not that necessary.

  • {} Function body: the actual code you want the lambda to execute.


Magic of captures - Creating a closure

A lambda that captures variables from its environment is called a closure.

This is what makes them more powerful than a simple function pointer.

Let’s try to find all people older than a certain age_limit.

int age_limit = 30;
std::vector<Person> people = {
    {"Alice", 30},
    {"Bob", 25},
    {"Charlie", 35}
};

auto it = std::find_if(people.begin(), people.end(), [age_limit](const Person& p)
    {
        return p.age > age_limit;
    });

Capture modes

Here’s how the different capture modes work:

  • [] (No capture): no access to any variables from the outside scope.

  • [=] (capture by value): lambda gets its own copy of all variables used from the surrounding scope.

int x = 10;
auto my_lambda = [=]() { std::cout << x; }; // Makes copy
  • [&] (capture by reference): lambda gets a reference to all variables used (IMPORTANT: if the original variable is destroyed before, you’ll have a dangling reference).
int x = 10;
auto my_lambda = [&]() { x++; }; // Accesses the original x
my_lambda();
  • [var] (specific capture by value): captures only the variable var by value. ([var1, var2])

  • [&var] (specific Capture by Reference): captures only var by reference. ([&var1, &var2])

  • [this] (capture this pointer): lambda can access member variables of the containing class.


Lab Time!


Conclusion

Lambda functions are not just a cute tool (although cute might not be the best expression), but a powerful feature that provides a concise way to define inline, anonymous functions.

And, we all know what they say about C++; not readable at all. Well, lambda functions can clearly help.