Lambda Expressions & std::function

ল্যাম্বডা এক্সপ্রেশন

Read: ~30 min 16 practice problems

1. The Lambda

A lambda is a function defined inline. C++ generates an anonymous class with operator() for it.

lambda.cpp
#include <iostream>

int main() {
    auto add = [](int a, int b) { return a + b; };
    std::cout << add(3, 4) << "\n";

    // Lambda used in-place
    auto sq = [](int x) { return x * x; };
    std::cout << sq(5) << "\n";
}

2. Captures

The [ ] at the start says how to capture variables from the enclosing scope:

CaptureMeaning
[]No captures
[x]Copy of x
[&x]Reference to x
[=]Copy everything used (avoid)
[&]Reference to everything used (avoid)
[x, &y]Mix: copy x, ref to y
[this]Capture this pointer in member fn
capture.cpp
#include <iostream>

int main() {
    int x = 10;
    auto by_value = [x]()       { return x; };
    auto by_ref   = [&x]()      { ++x; };
    by_ref();
    std::cout << by_value() << " " << x << "\n"; // 10 11
}
Capture by reference dangers If the lambda outlives the captured variables (stored in a callback), & dangles. Prefer copy capture for stored lambdas.

3. Generic Lambdas (C++14)

Use auto for parameters → an implicit template:

generic.cpp
auto add = [](auto a, auto b) { return a + b; };
add(3, 4);             // int
add(1.5, 2.5);         // double
add(std::string{"hi"}, " world"); // string

4. std::function — Type-Erased Callable

Holds any callable (function pointer, lambda, functor) with a given signature:

function.cpp
#include <iostream>
#include <functional>
#include <vector>

int main() {
    std::vector<std::function<int(int)>> ops;
    ops.push_back([](int x) { return x * 2; });
    ops.push_back([](int x) { return x + 1; });
    ops.push_back([](int x) { return x * x; });

    int n = 5;
    for (auto& op : ops) std::cout << op(n) << " ";
    std::cout << "\n";
}
std::function has runtime cost (heap alloc for big lambdas, virtual dispatch). When the type is known at compile-time, prefer auto or template parameter.

5. Mutable Lambdas

mutable.cpp
auto counter = [n = 0]() mutable { return ++n; };
counter(); counter(); counter(); // returns 1, 2, 3

Lambdas are const by default. mutable allows modification of captured-by-value variables.

6. Practice Problems

  1. Write a lambda that returns the square of its argument.
    ✨ Show Answer
    auto sq = [](int x) { return x * x; };
  2. Use a lambda with std::sort to sort descending.
    ✨ Show Answer
    std::sort(v.begin(), v.end(), [](int a, int b){ return a > b; });
  3. Capture threshold by value in a count_if predicate.
    ✨ Show Answer
    int threshold = 10;
    int n = std::count_if(v.begin(), v.end(),
        [threshold](int x){ return x > threshold; });
  4. What is a closure?
    ✨ Show Answer

    An object that holds (encloses) some captured state plus a function. C++ lambdas with captures generate closure objects.

  5. Why use std::function?
    ✨ Show Answer

    To store callables of different concrete types in the same variable/container. Pays runtime cost.

  6. Define a lambda that increments a counter every call.
    ✨ Show Answer
    auto next = [n = 0]() mutable { return ++n; };
  7. When does capture-by-reference become dangerous?
    ✨ Show Answer

    When the lambda is stored or returned and outlives the referenced variable. The reference dangles.

  8. Use a lambda inside a class member function. How to access members?
    ✨ Show Answer
    auto f = [this]() { return member_; };
  9. Generic lambda summing two values.
    ✨ Show Answer
    auto sum = [](auto a, auto b) { return a + b; };
  10. Lambda with explicit return type.
    ✨ Show Answer
    auto div = [](int a, int b) -> double { return a / (double)b; };
  11. Why use auto for storing a lambda instead of std::function?
    ✨ Show Answer

    No type erasure cost; compiler can inline. std::function is for when you need a uniform type at runtime.

  12. A lambda that captures vector by reference and finds max.
    ✨ Show Answer
    std::vector<int> v = {3,1,4};
    auto maxer = [&v](){ return *std::max_element(v.begin(), v.end()); };
  13. Pass a lambda to std::for_each.
    ✨ Show Answer
    std::for_each(v.begin(), v.end(),
        [](int& x){ x *= 2; });
  14. What does operator() have to do with lambdas?
    ✨ Show Answer

    The compiler generates an anonymous class with operator() overloaded to your lambda body. The lambda is an instance of that class.

  15. Use a lambda as a comparator with std::map.
    ✨ Show Answer
    auto cmp = [](int a, int b){ return a > b; };
    std::map<int, std::string, decltype(cmp)> m(cmp);
  16. Recursive lambda — possible?
    ✨ Show Answer
    std::function<int(int)> fact = [&](int n) {
        return n < 2 ? 1 : n * fact(n - 1);
    };

    Or use a parameter trick (Y-combinator style).

Summary

Lambdas make functions first-class. Capture rules: prefer explicit captures over [=]/[&]. Use auto to store a lambda when type is local. Use std::function when you need a uniform callable type at runtime, but be aware of its cost.

Next Module → Move Semantics & Rvalue References.