Passing Parameters to Lambda Expressions in C++
Lambda expressions, introduced in C++11, provide a powerful mechanism for creating anonymous function objects. While parameter passing to lambdas resembles regular functions, the capture mechanism introduces additional flexibility.
Syntax Structure
[capture](parameters) -> return_type {
// function body
}
Parameter Passing Modes
Pass by Value
auto sum = [](int a, int b) {
return a + b;
};
inttotal = sum(3, 4); // total = 7
Pass by Reference
auto doubleValue = [](int& num) {
num *= 2;
};
int n = 5;
doubleValue(n); // n becomes 10
Const Reference Passing
auto displayArray = [](const std::vector<int>& data) {
for (const auto& element : data) {
std::cout << element << " ";
}
std::cout << std::endl;
};
std::vector<int> data = {1, 2, 3, 4, 5};
displayArray(data); // avoids copying the entire vector
Capture List Mechanisms
Lambdas can access variables from the enclosing scope through the capture list—a capability absent from regular functions:
Capture by Value
int count = 10;
auto show = [count]() {
std::cout << count << std::endl;
// count++; // Error: captured-by-value variables are const by default
};
show(); // prints 10
Capture by Reference
int count = 10;
auto increment = [&count]() {
std::cout << count << std::endl;
count++; // modifies the original variable
};
increment(); // prints 10
std::cout << count << std::endl; // prints 11
Mutable Keyword
int count = 10;
auto modifyLocal = [count]() mutable {
std::cout << count << std::endl;
count++; // modifies the local copy
std::cout << count << std::endl;
};
modifyLocal(); // prints 10, then 11
std::cout << count << std::endl; // prints 10 (original unchanged)
Generalized References and Perfect Forwarding
C++14 introduced initializing captures for more flexible parameter handling:
// Generic lambda in C++14
auto forwarder = [](auto&& arg) {
return std::forward<decltype(arg)>(arg);
};
int m = 5;
const int n = 10;
auto r1 = forwarder(m); // lvalue passed
auto r2 = forwarder(42); // rvalue passed
auto r3 = forwarder(n); // const lvalue passed
Capturing the this Pointer
When used as class member functions, lambdas can capture this to access member variables:
class Processor {
public:
int data = 42;
void showData() {
auto printer = [this]() {
std::cout << data << std::endl;
};
printer();
}
};
Default Capture Modes
int a = 10, b = 20;
// Capture all external variables by value
auto fn1 = [=]() {
std::cout << a + b << std::endl;
};
// Capture all external variables by reference
auto fn2 = [&]() {
a++; b++;
std::cout << a + b << std::endl;
};
// Mixed capture: a by value, b by reference
auto fn3 = [=, &b]() {
// a is captured by value, cannot be modified
b++; // b is captured by reference, can be modified
std::cout << a + b << std::endl;
};
// Mixed capture: b by reference, a by value
auto fn4 = [&, a]() {
// b is captured by reference, can be modified
// a is captured by value, cannot be modified
std::cout << a + b << std::endl;
};
Practical Implementation Example
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> values = {1, 2, 3, 4, 5};
int limit = 3;
// Using lambda as predicate with captured threshold
auto it = std::find_if(values.begin(), values.end(),
[limit](int item) {
return item > limit;
});
if (it != values.end()) {
std::cout << "First value exceeding " << limit
<< " is " << *it << std::endl;
}
// Modify vector elements in place
std::for_each(values.begin(), values.end(),
[](int& item) {
item *= 2;
});
// Display transformed results
for (const auto& v : values) {
std::cout << v << " ";
}
std::cout << std::endl;
return 0;
}
Critical Considerations
Capture by reference creates a dependency on the lifetime of the original variable. Using [&] default capture may inadvertently retain references to objects that have already been destroyed. The mutable keyword only applies to captured-by-value copies. When passing large objects, const reference parameters prevent unnecessary copying while maintaining read-only access to the original data.