Introduction
When using standard algorithms like std::sort or std::find_if in C++, defining a separate function just to specify a condition can be tedious.
Lambda expressions, introduced in C++11, answer the need for “disposable, simple functions right on the spot.” Using lambda expressions allows you to write the processing logic directly inline where the function is defined.
In this article, I will explain everything from the basic syntax of lambda expressions, which dramatically simplify C++ code, to their powerful capture features.
1. Basic Syntax of Lambda Expressions
A lambda expression consists of the following elements:
[Capture](Parameter List) -> Return Type { Function Body }
- [Capture]: Specifies how to access variables outside the lambda expression (explained later).
- (Parameter List): Defines arguments just like a normal function.
- -> Return Type: Explicitly states the return type. Can be omitted if the compiler can infer it.
- { Function Body }: Describes the processing you want to execute.
Sample Code
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
// 1. The simplest lambda expression
auto greet = [](){ cout << "Hello, Lambda!" << endl; };
greet();
// 2. Lambda expression with arguments and return value
auto add = [](int a, int b) -> int {
return a + b;
};
cout << "add(10, 20): " << add(10, 20) << endl;
return 0;
}
2. Capture: Accessing Outer Variables
The most powerful feature of lambda expressions is “Capture.” By writing specifications inside [], you can import variables within the scope where the lambda is defined and use them inside the function body.
Sample Code
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<int> numbers = {1, 2, 3, 4, 5, 6};
int threshold = 3;
// 1. Value Capture [=] or [threshold]
// Import a "copy" of threshold into the lambda
int count_gt_copy = count_if(numbers.begin(), numbers.end(),
[threshold](int n){ return n > threshold; }
);
cout << "Count elements > 3 (Value Capture): " << count_gt_copy << endl;
// 2. Reference Capture [&] or [&threshold]
// Import a "reference" to threshold into the lambda
for_each(numbers.begin(), numbers.end(),
[&](int& n){ n *= threshold; } // Rewrite original elements using the reference-captured threshold
);
cout << "After multiplying each element by 3: ";
for(int n : numbers) cout << n << " ";
cout << endl;
return 0;
}
Explanation
| Capture | Description |
[] | Captures nothing. |
[=] | Captures all automatic variables by value (copy). |
[&] | Captures all automatic variables by reference. |
[x, &y] | Captures x by value and y by reference individually. |
3. Generic Lambdas (C++14)
By using auto for the argument types, you can define template-like lambda expressions (Generic Lambdas) that can handle various types.
Sample Code
#include <iostream>
#include <string>
using namespace std;
int main() {
// With the auto keyword, it can accept arguments of any type
auto generic_add = [](auto a, auto b) {
return a + b;
};
cout << generic_add(10, 20) << endl; // int + int
cout << generic_add(3.14, 2.71) << endl; // double + double
cout << generic_add(string("a"), string("b")) << endl; // string + string
return 0;
}
4. Initialization Capture (C++14)
You can declare and initialize new variables inside the capture clause. This is particularly useful when you want to move ownership of a variable into the lambda using move.
Sample Code
#include <iostream>
#include <memory>
#include <utility> // for std::move
using namespace std;
int main() {
auto p = make_unique<int>(100);
// Move ownership of p to a new variable ptr inside the lambda
auto lambda_with_move = [ptr = move(p)]() {
cout << "Value inside lambda: " << *ptr << endl;
};
lambda_with_move();
// Afterwards, p is empty (p == nullptr)
if (!p) {
cout << "p is now empty." << endl;
}
return 0;
}
Summary
In this article, I explained everything from the basics to the applications of C++ lambda expressions. Lambda expressions are active in every aspect of modern C++ programming, including combinations with STL algorithms and cooperation with std::function.
[](){}is the basic syntax.- Use Capture
[]to access outer variables. Distinguish between value capture and reference capture. - Advanced features like Generic Lambdas and Initialization Captures exist.
Mastering lambda expressions will make your code more concise and expressive.
