C++ Lambda Expressions, Captures, std::function & Closures Masterclass
Welcome to Phase 16: Lambda Expressions! Introduced in C++11, lambdas are anonymous inline function objects. They are the modern replacement for hand-written functors, enabling concise callbacks, predicates, and closures that capture surrounding variables.
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
int main() {
// Basic lambda
auto greet = [](const std::string& name) {
std::cout << "Hello, " << name << "!
";
};
greet("World");
// Lambda with return type
auto add = [](double a, double b) -> double { return a + b; };
std::cout << "add: " << add(3.5, 4.2) << "
";
// Capture by value
int threshold = 10;
auto isAbove = [threshold](int x) { return x > threshold; };
std::cout << "15 above threshold: " << std::boolalpha << isAbove(15) << "
";
// Capture by reference (can modify outer variable)
int counter = 0;
auto increment = [&counter]() { ++counter; };
increment(); increment(); increment();
std::cout << "counter: " << counter << "
";
// Mutable lambda (can modify captured-by-value copy)
int x = 5;
auto mutLambda = [x]() mutable {
x += 10; // modifies the COPY, not original x
std::cout << "inside mutable: " << x << "
";
};
mutLambda();
std::cout << "original x: " << x << "
";
// Generic lambda (C++14) โ auto parameters
auto printType = [](auto val) {
std::cout << "value: " << val << "
";
};
printType(42);
printType(3.14);
printType(std::string("generic!"));
// Immediately Invoked Lambda Expression (IIFE)
int result = [](int a, int b){ return a * b; }(6, 7);
std::cout << "IIFE result: " << result << "
";
// Lambda with algorithms
std::vector<int> nums{10, 15, 20, 25, 30};
std::for_each(nums.begin(), nums.end(), [](int n){
if (n % 2 == 0) std::cout << n << " ";
});
std::cout << "
";
// sort with custom comparator lambda
std::vector<std::string> words{"banana", "apple", "cherry", "date"};
std::sort(words.begin(), words.end(), [](const std::string& a, const std::string& b){
return a.length() < b.length(); // sort by string length
});
for (const auto& w : words) std::cout << w << " ";
std::cout << "
";
return 0;
}
#include <iostream>
#include <functional>
#include <vector>
// Accept any callable matching (int)->bool signature
void filterAndPrint(const std::vector<int>& v, std::function<bool(int)> pred) {
for (int x : v) {
if (pred(x)) std::cout << x << " ";
}
std::cout << "
";
}
// Functor (class with operator())
struct Multiplier {
int factor;
explicit Multiplier(int f) : factor{f} {}
int operator()(int x) const { return x * factor; }
};
int main() {
std::vector<int> nums{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Pass lambda to std::function parameter
filterAndPrint(nums, [](int x){ return x % 3 == 0; }); // multiples of 3
// Store lambda in std::function variable
std::function<int(int, int)> op;
op = [](int a, int b) { return a + b; };
std::cout << "op(3,4): " << op(3, 4) << "
";
op = [](int a, int b) { return a * b; };
std::cout << "op(3,4): " << op(3, 4) << "
";
// Functor usage
Multiplier times3{3};
std::cout << "times3(7): " << times3(7) << "
";
// std::bind (older, lambdas preferred)
auto addFive = std::bind(std::plus<int>{}, std::placeholders::_1, 5);
std::cout << "addFive(10): " << addFive(10) << "
";
return 0;
}
Q1: What is the type of a lambda?
Each lambda has a unique, compiler-generated anonymous type (closure type). You cannot name it โ use auto to hold it, or std::function to store it in a type-erased container.
Q2: Capture by value vs reference โ when to use which?
Capture by value ([=]) for short-lived lambdas or when the lambda outlives the local variables. Capture by reference ([&]) when you need to modify outer variables or avoid copying large objects.
Q3: What is a dangling reference in a lambda?
If a lambda captures a local variable by reference, and the lambda outlives that variable (e.g., returned from function), dereferencing the captured reference is Undefined Behaviour.
Q4: Performance of std::function vs auto lambda?
std::function has type-erasure overhead (heap allocation + virtual dispatch). Storing a lambda in auto gives zero-overhead inlining. Prefer auto when the type doesn't need to be stored polymorphically.
Q5: What is a recursive lambda?
Lambdas cannot refer to themselves by name directly. Use std::function or (C++23) deducing this parameter: auto fib = [&fib](int n) -> int { ... };