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 thethispointer.(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
-
auto greet = []() { ... }— The simplest lambda with no parameters and no captured outer variables. It is stored in anautovariable (the type is inferred by the compiler). -
[](int a, int b) -> int { return a + b; }— A lambda with parameters and an explicit return type.-> intspecifies the return type. -
[=]— Capture by value. All variables from the surrounding environment are copied into the lambda. Changes inside the lambda do not affect the original variables. -
[&]— Capture by reference. Variables are accessed directly (by reference). Changes inside the lambda affect the original variables. -
[=, &y]— Mixed capture. All variables are captured by value excepty, which is captured by reference. -
[](int a, int b) { return a > b; }— A lambda used as a comparator insort. It produces a descending sort. -
[](auto a, auto b)— A generic lambda (C++14). The parameters useauto, so they can accept any data type. -
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. -
mutable— A keyword that allows a lambda to modify variables captured by value. Withoutmutable, the lambda is const. -
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.