What is a Lambda Expression?
A lambda expression, often referred to as an anonymous function, provides a compact way to define function objects directly at the point where they are needed. Named after the lambda calculus in mathematics, this feature enables developers to create inline functions without declaring a separate named function. Lambda expressions are particularly useful for short-lived operations, callback mechanisms, and as arguments to higher-order functions.
Basic Syntax
The general structure of a lambda expression follows this pattern:
[capture_clause](parameters) -> return_type { function_body }The components include:
- Capture Clause: Specifies how variables from the enclosing scope are accessed within the lambda body. An empty
[]captures nothing.[=]captures all external variables by value (read-only within the lambda).[&]captures all external variables by reference, allowing modification of the original values. - Parameters: A standard parameter list, similar to regular functions.
- Return Type: The return type can often be omitted, allowing the compiler to deduce it automatically.
Practical Examples
Assigning Lambda to a Variable
A lambda can be stored in a variable using the auto keyword for later invocation:
#include <iostream>
int main() {
// Define a lambda that performs addition
auto addNumbers = [](int x, int y) -> int {
return x + y;
};
int total = addNumbers(10, 20);
std::cout << "Sum: " << total << std::endl;
return 0;
}Output: Sum: 30
Using Lambda with STL Algorithms
Lambda expressions shine when paired with Standard Template Library algorithms. Here, a lambda is passed as a binary operation to std::accumulate:
#include <iostream>
#include <vector>
#include <numeric>
int main() {
std::vector<int> data = {1, 2, 3, 4, 5};
// Calculate sum using lambda with accumulate
int total = std::accumulate(data.begin(), data.end(), 0,
[](int accumulator, int current) {
return accumulator + current;
});
std::cout << "Total: " << total << std::endl;
return 0;
}Output: Total: 15
Lambda as Callback for STL Operations
Lambdas are frequently used as predicates or callbacks for algorithms like std::for_each and std::sort:
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> numbers = {5, 2, 8, 1, 9};
// Print each element
std::for_each(numbers.begin(), numbers.end(), [](int n) {
std::cout << n << " ";
});
std::cout << std::endl;
// Sort in descending order
std::sort(numbers.begin(), numbers.end(), [](int a, int b) {
return a > b;
});
// Display sorted result
std::for_each(numbers.begin(), numbers.end(), [](int n) {
std::cout << n << " ";
});
std::cout << std::endl;
return 0;
}Output:
5 2 8 1 9
9 8 5 2 1Capturing External Variables
Lambda expressions can capture and use variables from their surrounding scope:
#include <iostream>
int main() {
int multiplier = 5;
// Capture multiplier by reference
auto scale = [&multiplier](int value) {
return value * multiplier;
};
int outcome = scale(10);
std::cout << "Scaled result: " << outcome << std::endl;
return 0;
}Output: Scaled result: 50
Factory Function Returning a Lambda
Lambdas can be returned from functions, enabling powerful factory patterns and strategy implementations:
#include <iostream>
#include <functional>
std::function<void(int)> createScaler(int factor) {
// Return a lambda that captures factor by value
return [factor](int input) {
std::cout << "Result: " << (input * factor) << std::endl;
};
}
int main() {
// Create a tripling function
auto tripleIt = createScaler(3);
tripleIt(7);
// Create a quadrupling function
auto quadrupleIt = createScaler(4);
quadrupleIt(5);
return 0;
}Output:
Result: 21
Result: 20Wrapping Lambda in a Custom Functor
While less common in modern C++, lambdas can be encapsulated within custom function objects:
#include <iostream>
#include <functional>
class FunctorWrapper {
public:
explicit FunctorWrapper(std::function<void(int)> fn) : callback(fn) {}
void operator()(int arg) const {
callback(arg);
}
private:
std::function<void(int)> callback;
};
int main() {
FunctorWrapper handler([](int data) {
std::cout << "Received: " << data << std::endl;
});
handler(42);
return 0;
}Output: Received: 42
The std::accumulate Algorithm
The std::accumulate function computes a cumulative value across a range of elements. Its signatures are:
std::accumulate(begin, end, initial_value)
std::accumulate(begin, end, initial_value, binary_operation)The optional fourth parameter accepts a binary function or function object that defines how elements are combined.
Mixed Capture Modes
Capture clauses can combine different modes for specific variables:
#include <iostream>
#include <string>
int main() {
std::string message = "Counter: ";
int counter = 0;
// Capture message by value, counter by reference
auto incrementAndPrint = [message, &counter]() {
counter++;
std::cout << message << counter << std::endl;
};
incrementAndPrint();
incrementAndPrint();
return 0;
}Output:
Counter: 1
Counter: 2Additional Notes
When specifying an explicit return type, the trailing return type syntax (the -> arrow) is optional in certain contexts. Lambda expressions form the foundation for writing concise, functional-style code in C++, enabling higher-order functions that accept or return callable objects.