Understanding Lambda Expressions in C++11

Lambda expressions were introduced in C++11 to simplify the use of function objects, especially when passing custom logic to standard algorithms like std::sort.

Motivation for Lambda Expressions

In C++98, defining custom comparison logic required creating a separate functor class:

struct Product {
    std::string name;
    double price;
    int rating;
};

struct CompareByPriceAsc {
    bool operator()(const Product& a, const Product& b) const {
        return a.price < b.price;
    }
};

// Usage
std::vector<Product> items;
std::sort(items.begin(), items.end(), CompareByPriceAsc{});

This approach becomes cumbersome when multiple comparison strategies are needed, as each requires a new struct with repetitive boilerplate.

Introducing Lambda Expressions

C++11 allows inline anonymous functions through lambdda syntax:

std::sort(items.begin(), items.end(), [](const Product& x, const Product& y) {
    return x.price < y.price;
});

This improves readability by placing the comparison logic directly at the call site.

Lambda Syntax Breakdown

The general form is:

[capture](parameters) mutable -> return_type { body }
  • Capture list ([...]): Specifies which variables from the enclosing scope are accessible and how (by value or reference)
  • Parameters ((...)): Same as regular functions; can be omitted if empty
  • Mutable: Allows modification of captured values (by default, lambdas are const)
  • Return type (-> ...): Optional when compiler can deduce it
  • Body ({...}): Function implementation

Capture List Variasions

  • [x] – capture x by value
  • [&x] – capture x by reference
  • [=] – capture all visible variables by value
  • [&] – capture all visible variables by reference
  • [=, &y] – capture all by value except y by reference

Note: Mixing explicit captures with = or & is allowed, but duplicate captures cause compilation errors.

Examples

// Simplest lambda
[] {};

// Capture by value, return inferred as int
int p = 5, q = 7;
auto sum = [=] { return p + q; };

// Capture by reference, modify captured variable
auto update = [&](int val) { q = p + val; };
update(10); // q becomes 15

// Full specification with mutable
int factor = 3;
auto multiplier = [factor](int input) mutable {
    factor *= 2;
    return input * factor;
};
// Returns input * 6 (factor modified inside)

Key Constraints

  • Lambdas defined outside block scope must have empty capture lists
  • Two lambdas—even with identical bodies—have distinct, incompatible types
  • Assignment between lambdas is not allowed, though copy construction works
  • Lambdas with no captures can be converted to function poitners

Lambdas vs Function Objects

Both provide callable behavior, but lambdas offer more concise syntax:

// Traditional functor
class InterestCalculator {
    double rate;
public:
    InterestCalculator(double r) : rate(r) {}
    double operator()(double principal, int years) const {
        return principal * rate * years;
    }
};

// Equivalent lambda
double annual_rate = 0.05;
auto calc_interest = [=](double principal, int years) {
    return principal * annual_rate * years;
};

Under the hood, the compiler generates a unique class with an operator() for each lambda, making them syntactic sugar for functors.

Tags: C++ C++11 Lambda Expressions functors closures

Posted on Sun, 02 Aug 2026 17:01:16 +0000 by $SuperString