Skillversitas

Lambda Expressions in C++

A lambda expression is an anonymous function (a function without a name) introduced in C++11. Lambdas let us define functions inline and concisely without creating a separate named function. This feature is very useful for callbacks, predicates for STL algorithms, and operations that are only used in one place.

Basic lambda syntax: [capture](parameters) -> return_type { body }

  • [capture] — A list of variables from the surrounding environment that are accessed inside the lambda. [=] captures everything by value, [&] captures everything by reference, and [this] captures the this pointer.
  • (parameters) — Formal parameters, similar to regular function parameters.
  • -> return_type — The return type (optional; usually can be deduced automatically).
  • { body } — The body of the lambda function.

Lambdas can also be made constexpr (C++17) so they can be evaluated at compile time, and generic (C++14) using auto parameters.

Code Example

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
#include <string>
using namespace std;

void printVector(const vector<int>& v, const string& label) {
    cout << label << ": ";
    for (int x : v) cout << x << " ";
    cout << endl;
}

int main() {
    // ==================== Basic Lambdas ====================
    cout << "=== Basic Lambda ===" << endl;

    // The simplest lambda
    auto greet = []() {
        cout << "Hello, world!" << endl;
    };
    greet();

    // Lambda with parameters
    auto add = [](int a, int b) -> int {
        return a + b;
    };
    cout << "add(3, 4) = " << add(3, 4) << endl;

    // Lambda with a deduced return type
    auto multiply = [](int a, int b) {
        return a * b;  // Return type deduced as int
    };
    cout << "multiply(3, 4) = " << multiply(3, 4) << endl;

    // ==================== Capture Modes ====================
    cout << "\n=== Capture Modes ===" << endl;

    int x = 10;
    int y = 20;
    string name = "John";

    // [=] captures all variables by value (copy)
    auto byValue = [=]() {
        cout << "byValue: x=" << x << ", y=" << y << ", name=" << name << endl;
    };
    byValue();

    // [&] captures all variables by reference
    auto byReference = [&]() {
        x += 5;
        y += 5;
        name += " And";
    };
    byReference();
    cout << "After byReference: x=" << x << ", y=" << y << ", name=" << name << endl;

    // Mixed capture
    auto mixed = [=, &y]() {
        // x and name are captured by value, y is captured by reference
        y += 100;
        cout << "mixed: x=" << x << ", y=" << y << ", name=" << name << endl;
    };
    mixed();

    // Specific capture
    auto specific = [x, &y]() {
        cout << "specific: x=" << x << ", y=" << y << endl;
    };
    specific();

    // ==================== Lambdas with the STL ====================
    cout << "\n=== Lambda with STL ===" << endl;

    vector<int> numbers = {5, 2, 8, 1, 9, 3, 7, 4, 6};
    printVector(numbers, "Original");

    // sort with a lambda comparator
    sort(numbers.begin(), numbers.end(), [](int a, int b) {
        return a > b;  // Sort descending
    });
    printVector(numbers, "Descending");

    sort(numbers.begin(), numbers.end(), [](int a, int b) {
        return a < b;  // Sort ascending
    });
    printVector(numbers, "Ascending");

    // for_each
    cout << "for_each: ";
    for_each(numbers.begin(), numbers.end(), [](int n) {
        cout << n * n << " ";  // Print the square
    });
    cout << endl;

    // find_if
    auto found = find_if(numbers.begin(), numbers.end(), [](int n) {
        return n > 6;
    });
    if (found != numbers.end()) {
        cout << "find_if (n > 6): " << *found << endl;
    }

    // count_if
    int count = count_if(numbers.begin(), numbers.end(), [](int n) {
        return n % 2 == 0;
    });
    cout << "count_if (even): " << count << endl;

    // ==================== Generic Lambdas (C++14) ====================
    cout << "\n=== Generic Lambda (C++14) ===" << endl;

    auto genericAdd = [](auto a, auto b) {
        return a + b;
    };
    cout << "genericAdd(3, 4) = " << genericAdd(3, 4) << endl;
    cout << "genericAdd(1.5, 2.5) = " << genericAdd(1.5, 2.5) << endl;
    cout << "genericAdd(string('Hi'), string('!')) = " << genericAdd(string("Hi"), string("!")) << endl;

    // ==================== std::function ====================
    cout << "\n=== std::function ===" << endl;

    // std::function as a generic type for callables
    function<int(int, int)> operation;

    operation = [](int a, int b) { return a + b; };
    cout << "operation(10, 5) = " << operation(10, 5) << endl;

    operation = [](int a, int b) { return a - b; };
    cout << "operation(10, 5) = " << operation(10, 5) << endl;

    // Lambda as a callback
    auto executeOperation = [](function<int(int, int)> op, int a, int b) {
        return op(a, b);
    };
    cout << "executeOperation(add, 7, 3) = " << executeOperation(
        [](int a, int b) { return a + b; }, 7, 3) << endl;

    // ==================== Mutable Lambdas ====================
    cout << "\n=== Mutable Lambda ===" << endl;

    int counter = 0;
    auto increment = [counter]() mutable {
        counter++;
        cout << "Counter inside lambda: " << counter << endl;
    };
    increment();
    increment();
    increment();
    cout << "Counter outside lambda: " << counter << endl;  // Still 0

    // ==================== Lambdas as Return Values ====================
    cout << "\n=== Lambda Return Value ===" << endl;

    auto makeAdder = [](int base) -> function<int(int)> {
        return [base](int x) {
            return base + x;
        };
    };

    auto add5 = makeAdder(5);
    auto add10 = makeAdder(10);
    cout << "add5(3) = " << add5(3) << endl;
    cout << "add10(3) = " << add10(3) << endl;

    // ==================== Constexpr Lambdas (C++17) ====================
    cout << "\n=== Constexpr Lambda ===" << endl;

    constexpr auto square = [](int n) {
        return n * n;
    };
    cout << "square(5) = " << square(5) << endl;

    // Arrays with a constexpr lambda
    constexpr auto factorial = [](int n) -> int {
        int result = 1;
        for (int i = 2; i <= n; i++) result *= i;
        return result;
    };
    cout << "factorial(5) = " << factorial(5) << endl;

    return 0;
}

Code Explanation

  1. auto greet = []() { ... } — The simplest lambda with no parameters and no captured outer variables. It is stored in an auto variable (the type is inferred by the compiler).

  2. [](int a, int b) -> int { return a + b; } — A lambda with parameters and an explicit return type. -> int specifies the return type.

  3. [=] — Capture by value. All variables from the surrounding environment are copied into the lambda. Changes inside the lambda do not affect the original variables.

  4. [&] — Capture by reference. Variables are accessed directly (by reference). Changes inside the lambda affect the original variables.

  5. [=, &y] — Mixed capture. All variables are captured by value except y, which is captured by reference.

  6. [](int a, int b) { return a > b; } — A lambda used as a comparator in sort. It produces a descending sort.

  7. [](auto a, auto b) — A generic lambda (C++14). The parameters use auto, so they can accept any data type.

  8. std::function<int(int, int)> — A generic wrapper for storing lambdas with a specific signature. Useful when a lambda needs to be stored in a variable with a known type.

  9. mutable — A keyword that allows a lambda to modify variables captured by value. Without mutable, the lambda is const.

  10. constexpr auto square = [](int n) { ... } — A constexpr lambda (C++17). It can be evaluated at compile time if all inputs are constexpr.

Everyday Use Cases

Imagine you are building an e-commerce application. Lambdas are very useful for various operations:

// Filter products by price
vector<Product> products = getProducts();
auto expensive = find_if(products.begin(), products.end(),
    [](const Product& p) { return p.price > 1000000; });

// Sort products by rating
sort(products.begin(), products.end(),
    [](const Product& a, const Product& b) { return a.rating > b.rating; });

// Callback for the "Add to Cart" button
button.onClick([]() {
    cout << "Product added to cart!" << endl;
});

// Form input validation
auto validate = [](const string& email) -> bool {
    return email.find('@') != string::npos;
};

Conclusion

Lambda expressions are a modern C++ feature that makes it easy to write concise anonymous functions. The capture mode determines how outer variables are accessed ([=] by value, [&] by reference). Lambdas are very useful with STL algorithms, as callbacks, and for simple operations that do not need a separate function. Generic lambdas (C++14) and constexpr lambdas (C++17) extend lambda capabilities even further.