Wiki
Lambda expression
Existing English translations were reused. 5 current source blocks remain in Simplified Chinese, without triggering paid translation.
1.What problem does this section solve?
Many places require passing in a small piece of logic: sorting rules, filtering conditions, button callbacks, timer callbacks. The old way of writing typically had three approaches:
- 写一个普通函数。
- 写一个函数指针。
- 写一个函数对象,也就是带
operator()的类或结构体。
These approaches all work, but small logic gets forced to move far away, or an extra type has to be written. Lambda expressions let you write a small function directly at the point of use, and they can capture external variables.
2.What is this feature?
A lambda is an anonymous callable object. It looks like a function, but it is essentially a class object generated by the compiler.
The basic structure is:
| Part | Example | Meaning |
|---|---|---|
| Capture list | [x, &y] |
Which variables to take from the outer scope |
| Parameter list | (int a, int b) |
What to pass in when calling the lambda |
| Return value | -> int |
Return type, which can usually be omitted |
| Function body | { return a + b; } |
The code that is actually executed |
The complete form can be written as [x](int n) -> int { return x + n; }; in common cases, the return type can be omitted.
3.C++ Standard Edition
- C++11: Basic Lambda.
- C++14: Generic Lambda, that is, parameters can be written as
auto. - C++17:
constexprLambda,[*this]captures.
Lambda is a language feature and does not require an additional header file. Only when used with STL algorithms,std::function and other library utilities is it necessary to include the corresponding header file.
4.Capture List Quick Reference
| Syntax | Meaning | Applicable scenario |
|---|---|---|
[] |
No capture of external variables | Only use parameters or local temporary variables |
[x] |
Capture by valuex |
Saves a copy, suitable for storing callbacks |
[&x] |
Capture by referencex |
Needs to modify external variables and can guarantee lifetime |
[x, &y] |
Mixed capture | Explicitly express which are copies and which are references |
[=] |
Default capture by value of used variables | Convenient for small examples, not recommended for abuse in engineering |
[&] |
Default capture by reference of used variables | Especially dangerous in asynchronous or stored callbacks |
[this] |
Capture the current object pointer | The object must live longer than the lambda |
[*this] |
Capture a copy of the current object | C++17 available since, suitable for avoiding danglingthis |
5.Example code
5.1.Example 1: Difference between the old callback style and lambda
Both ordinary functions and function objects can serve as algorithm conditions, but Lambda is more suitable for writing short local logic.
#include <algorithm>
#include <iostream>
#include <vector>
bool is_even(int n)
{
return n % 2 == 0;
}
struct GreaterThan
{
int limit;
bool operator()(int n) const
{
return n > limit;
}
};
int main()
{
// 程序从 main 函数开始执行,下面的语句会按顺序运行。
// vector 是动态数组,元素数量可以在运行时变化。
std::vector<int> numbers = {1, 2, 3, 4, 5, 6};
int even_count = std::count_if(numbers.begin(), numbers.end(), is_even);
std::cout << "even count = " << even_count << "\n";
int greater_count1 = std::count_if(numbers.begin(), numbers.end(), GreaterThan{3});
std::cout << "> 3 count (functor) = " << greater_count1 << "\n";
int limit = 3;
int greater_count2 = std::count_if(numbers.begin(), numbers.end(),
[limit](int n) {
return n > limit;
});
std::cout << "> 3 count (lambda) = " << greater_count2 << "\n";
return 0;
}
Running result:
even count = 3
> 3 count (functor) = 3
> 3 count (lambda) = 3
5.1.1.count_ifWhat is the third parameter of ?
First, look at this sentence:
int even_count = std::count_if(numbers.begin(), numbers.end(), is_even);
std::count_ifIt can be simply understood as:
从指定范围中逐个取出元素,调用你提供的“判断规则”,统计其中返回
true的元素有多少个。
Its basic form is:
std::count_if(起点, 终点, 判断规则);
The third parameter is this "judgment rule." It does not necessarily have to be an ordinary function; any object that can be called like a function will do, including ordinary functions, function objects, and lambdas. These things are collectively calledcallable objects (Callables).
Here, the third parameter passed in is:
is_even
while is_even is:
bool is_even(int n)
{
return n % 2 == 0;
}
count_if will pass the elements in numbers one by one to it; the process can be approximately understood as:
is_even(1); // false,不计数
is_even(2); // true,计数 +1
is_even(3); // false,不计数
is_even(4); // true,计数 +1
is_even(5); // false,不计数
is_even(6); // true,计数 +1
Thus, we finally obtain 3.
You can approximate the internal workings of count_if as follows:
int count = 0;
for (每一个元素)
{
if (判断规则(当前元素))
{
++count;
}
}
In other words,count_if responsible for oneself:
- How to iterate over the container;
- How to count the quantity.
The third parameter tells it:
What kind of elements qualify as meeting the conditions?
A function object can also be passed at the same position:
std::count_if(numbers.begin(), numbers.end(), GreaterThan{3});
GreaterThan{3}Can be called like a function because the struct defines:
bool operator()(int n) const
{
return n > limit;
}
The question it answers is:
Is the current
ngreater than3?
You can also pass a Lambda directly:
std::count_if(numbers.begin(), numbers.end(),
[limit](int n) {
return n > limit;
});
count_if It will also repeatedly invoke this Lambda:
lambda(1); // false
lambda(2); // false
lambda(3); // false
lambda(4); // true
lambda(5); // true
lambda(6); // true
So although these three ways of writing differ in form, they are essentially all providing a judgment rule for count_if:
is_even
GreaterThan{3}
[limit](int n) { return n > limit; }
The advantage of a Lambda is that if this judgment rule is used only once here, it can be written directly next to the algorithm, without the need to define an additional function or struct.
5.2.Example 2: The most basic lambda syntax, parameters and return value
#include <iostream>
#include <string>
int main()
{
// 程序从 main 函数开始执行,下面的语句会按顺序运行。
auto add = [](int a, int b) {
return a + b;
};
auto describe_score = [](int score) -> std::string {
if (score >= 60)
{
return "pass";
}
return "fail";
};
std::cout << "add(3, 5) = " << add(3, 5) << "\n";
std::cout << "score 80 is " << describe_score(80) << "\n";
std::cout << "score 40 is " << describe_score(40) << "\n";
// 返回 0 表示程序正常结束。
return 0;
}
Running result:
add(3, 5) = 8
score 80 is pass
score 40 is fail
5.3.Example 3: Capture by Value and Capture by Reference
Capture by value saves a copy at the time the lambda is defined; capture by reference accesses the outer variable itself.
#include <iostream>
int main()
{
// 程序从 main 函数开始执行,下面的语句会按顺序运行。
int score = 10;
auto add_by_value = [score](int bonus) {
return score + bonus;
};
auto add_by_ref = [&score](int bonus) {
score += bonus;
return score;
};
score = 20;
std::cout << "value capture result = " << add_by_value(5) << "\n";
std::cout << "ref capture result = " << add_by_ref(5) << "\n";
std::cout << "score after ref capture = " << score << "\n";
// 返回 0 表示程序正常结束。
return 0;
}
Running result:
value capture result = 15
ref capture result = 25
score after ref capture = 25
5.4.Example 4: mutable allows modification of the copy captured by value
A variable captured by value is read-only inside the lambda by default. After adding mutable, you can modify the copy stored by the lambda itself, but it will not modify the external variable.
#include <iostream>
int main()
{
// 程序从 main 函数开始执行,下面的语句会按顺序运行。
int start = 0;
auto counter = [start]() mutable {
++start;
return start;
};
std::cout << "counter() = " << counter() << "\n";
std::cout << "counter() = " << counter() << "\n";
std::cout << "outside start = " << start << "\n";
// 返回 0 表示程序正常结束。
return 0;
}
Running result:
counter() = 1
counter() = 2
outside start = 0
5.4.1.Why does calling counter() consecutively continue to accumulate?
This is not because the scope of the external start has become larger, but rather because the Lambda object counter itself has saved a copy of start.
int start = 0;
auto counter = [start]() mutable {
++start;
return start;
};
[start] is captured by value. It can be approximately understood as the compiler generating an object like this:
struct Counter
{
int start;
int operator()()
{
++start;
return start;
}
};
Counter counter{start};
Therefore, there are actually two different start in the program:
- External
start: always still0. counterInternally storedstart: the first call becomes1; the second call continues to become2.
counter(); // 内部 start:0 -> 1
counter(); // 内部 start:1 -> 2
The key is:
auto counter = [start]() mutable { ... };
Here only one Lambda object is created. The following two counter() are both callingthe same object, and will not re-capture the external start each time they are called.
mutable The role of is to allow modification of the copy captured by value inside the Lambda; it does not cause the external start to change together.
5.5.Example 5: lambda with STL algorithms
This is most common when Lambda is used with STL algorithms: sorting, searching, counting, and transforming can all have their local logic written directly at the call site.
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
int main()
{
// 程序从 main 函数开始执行,下面的语句会按顺序运行。
// vector 是动态数组,元素数量可以在运行时变化。
std::vector<std::string> names = {"Bob", "Alice", "Charlie", "David"};
std::sort(names.begin(), names.end(),
[](const std::string& a, const std::string& b) {
if (a.size() == b.size())
{
return a < b;
}
return a.size() < b.size();
});
std::cout << "sort by length: ";
for (const auto& name : names)
{
std::cout << name << " ";
}
std::cout << "\n";
int min_length = 6;
auto it = std::find_if(names.begin(), names.end(),
[min_length](const std::string& name) {
return name.size() >= static_cast<std::size_t>(min_length);
});
if (it != names.end())
{
std::cout << "first long name = " << *it << "\n";
}
return 0;
}
Running result:
sort by length: Bob Alice David Charlie
first long name = Charlie
5.5.1.APIs and syntax used in the examples
In addition to Lambda, this example also uses some common STL APIs.
| 写法 | 简单说明 |
|---|---|
std::vector<std::string> |
动态数组,这里用于保存多个字符串 |
names.begin() |
返回指向第一个元素的迭代器 |
names.end() |
返回最后一个元素后一个位置的迭代器 |
std::sort(begin, end, rule) |
对指定范围排序,第三个参数指定比较规则 |
string.size() |
返回字符串长度 |
const std::string& |
只读引用字符串,避免额外复制 |
for (const auto& x : container) |
范围 for,依次访问容器中的每个元素 |
auto |
让编译器自动推导变量类型 |
std::find_if(begin, end, rule) |
查找第一个满足条件的元素 |
static_cast<T>(value) |
将 value 显式转换为类型 T |
std::size_t |
STL 中常用于表示大小、数量和下标的无符号整数类型 |
*it |
取得迭代器 it 当前指向的元素 |
Among them, begin() and end()often appear together:
names.begin(), names.end()
represents the entire traversal range of names. Note that end() does not point to the last element, but to the end position after the last element:
Bob Alice David Charlie [结束位置]
↑ ↑
begin() end()
5.5.2.Why do STL algorithms take a "rule function"?
std::count_if,std::find_if,std::sort have one thing in common:
The algorithm itself is responsible for "how to do it," while the function you provide is responsible for "according to what rules to do it."
For example, std::sort it knows how a sorting algorithm should move and compare elements, but it does not know whether you want to sort by numeric value, string length, or some other rule.
Similarly,std::find_if it knows how to search from front to back, but it does not know "what kind of element counts as the element you are looking for".
Therefore, these algorithms accept a callable object and repeatedly invoke it during execution.
As already seen in Example 1:
std::count_if(begin, end, rule);
It calls the following on each element:
rule(element);
The third parameter answers:
Does this element count?
find_if and sort use the same design philosophy; they simply pose different questions to the rule function.
5.5.3.std::find_if: Is this element the one I'm looking for?
The basic form is:
std::find_if(起点, 终点, 判断规则);
In the example:
auto it = std::find_if(names.begin(), names.end(),
[min_length](const std::string& name) {
return name.size() >= static_cast<std::size_t>(min_length);
});
[min_length] Capture minimum length by value 6. This Lambda receives a string and determines whether its length is at least 6.
find_if will call this Lambda in sequence, which can be approximately understood as:
rule("Bob"); // false
rule("Alice"); // false
rule("David"); // false
rule("Charlie"); // true
Once the first true is found, find_if stop searching and return the position of the current element.
Therefore, find_if the third parameter of answers:
Is the current element the one I am looking for?
It is very similar in form to the rule function of count_if, and usually both are:
bool rule(一个元素);
The difference lies in what the algorithm does after obtaining true:
| Algorithm | Returned by rule true behavior after |
|---|---|
count_if |
Increment the count by one, then continue checking the following elements |
find_if |
Already found; stop immediately and return the current position |
5.5.4.std::sort:a Should it be placed before b?
std::sort Slightly different:
std::sort(起点, 终点, 比较规则);
Its third parameter receivestwo elementsat a time:
[](const std::string& a, const std::string& b) {
if (a.size() == b.size())
{
return a < b;
}
return a.size() < b.size();
}
sort will be continuously called during the sorting process:
rule(a, b);
This function should answer:
According to the current sorting rule, should
abe placed beforeb?
For example:
rule("Bob", "Charlie");
Because:
Bob 长度 3
Charlie 长度 7
The final judgment is:
3 < 7
The result is true, which indicates that according to the current rules, Bob should be ranked before Charlie.
If the two strings have the same length:
if (a.size() == b.size())
{
return a < b;
}
then use the lexicographic order of the strings to determine which comes first. For example, Alice and David both have length 5therefore compare:
"Alice" < "David"
The result is true, so Alice comes before David.
These three algorithms can be remembered as follows:
| API | What does the third parameter answer? |
|---|---|
count_if |
Does this element count? |
find_if |
Is this element the one I am looking for? |
sort |
aShould it be sorted before b? |
This is exactly a very common use of Lambda: the STL algorithm handles the general flow, while the Lambda describes the specific rule.
5.5.5.const std::string& and .size()
In Lambda:
const std::string& a
Can be broken down into:
std::string: the parameter is a string;&: use a reference, without additionally copying the entire string;const: only read this string, do not modify it.
Therefore, const std::string& is often used to receive larger objects such as string and vector in a read-only manner.
For strings:
a.size()
Returns the string length. For example:
"Bob" -> 3
"Alice" -> 5
"Charlie" -> 7
"David" -> 5
So the example's sorting result is:
Bob Alice David Charlie
5.5.6.Range for
for (const auto& name : names)
{
std::cout << name << " ";
}
It can be directly understood as:
Take each element from
namesin order, and name the current elementname.
Here:
const auto& name
The compiler automatically infers based on the element type of names, approximately equivalent to:
const std::string& name
5.5.7.static_cast<std::size_t>
Here:
static_cast<std::size_t>(min_length)
is explicit type conversion, which can be simply understood as:
Convert
min_lengthtostd::size_ttype.
Because name.size() returns std::size_t whereas:
int min_length = 6;
where min_length is int. After conversion, both sides are compared using suitable integer types of the same kind.
std::size_tIt is often used to represent non-negative quantities such as container size, string length, and array index.
5.5.8.it and iterator
auto it = std::find_if(...);
find_ifWhat is returned is not the string itself, but an iterator.
An iterator can be temporarily understood as a pointer-like object used to represent a certain position in a container.
The sorted data is:
Bob Alice David Charlie
↑
it
Therefore:
*it
What is obtained is:
Charlie
If find_if has not found an element satisfying the condition, it returns:
names.end()
So the common way of writing it is:
if (it != names.end())
{
std::cout << *it << "\n";
}
That is to say:
If the element is indeed found, then use it.
5.5.9.The execution process of the entire code segment
The program first creates:
std::vector<std::string> names = {
"Bob",
"Alice",
"Charlie",
"David"
};
Then std::sort Use Lambda as the sorting rule:
- Sort by length; shorter ones come first.
- If the lengths are the same, sort in lexicographical order of the strings.
So we obtain:
Bob Alice David Charlie
Next:
int min_length = 6;
It is specified to find names with a length of at least 6.
std::find_if Lambda will be called sequentially in the current order:
Bob -> 3 >= 6 -> false
Alice -> 5 >= 6 -> false
David -> 5 >= 6 -> false
Charlie -> 7 >= 6 -> true
Stop when true is obtained for the first time, so it ultimately points to Charlie:
*it
Obtained:
Charlie
Final output:
sort by length: Bob Alice David Charlie
first long name = Charlie
This example demonstrates a very typical use of Lambda:
The STL algorithm handles the general process, while the Lambda is responsible for describing the specific rules.
5.6.Example 6: Pay attention to capture lifetimes when saving callbacks.
If a lambda is only called immediately, reference captures usually appear fine; if it is stored in a container, thread, timer, or asynchronous callback, the lambda may execute later than the local variables. When storing callbacks, prefer capturing the required data by value.
#include <functional>
#include <iostream>
#include <string>
#include <vector>
int main()
{
// 程序从 main 函数开始执行,下面的语句会按顺序运行。
// std::function 可以保存普通函数、lambda 或函数对象。
// vector 是动态数组,元素数量可以在运行时变化。
std::vector<std::function<void()>> callbacks;
{
std::string name = "Alice";
int score = 95;
auto print_now = [&name, &score]() {
std::cout << "now: " << name << " " << score << "\n";
};
print_now();
callbacks.push_back([name, score]() {
std::cout << "saved: " << name << " " << score << "\n";
});
}
for (const auto& callback : callbacks)
{
callback();
}
return 0;
}
Running result:
now: Alice 95
saved: Alice 95
Here print_now is called immediately, so capture by reference is fine. The lambda stored in callbacks captures by value because after leaving the inner scope, name and score have already been destroyed.
5.7.Example 7: Capturing this to access the current object
In a member function of a class, a lambda can use [this] to capture the pointer to the current object. The lambda still accesses the original object, so changes to the object's members that occur later are also visible.
#include <iostream>
class Robot
{
public:
Robot(int speed) : speed_(speed)
{
}
void demo()
{
auto print_speed = [this]() {
std::cout << "lambda speed = " << speed_ << "\n";
};
speed_ = 20;
print_speed();
}
private:
int speed_;
};
int main()
{
Robot robot(10);
robot.demo();
return 0;
}
Running result:
lambda speed = 20
Here:
[this]() {
std::cout << speed_;
}
It can be approximately understood as Lambda saving the this pointer of the current object, therefore:
speed_
What is actually accessed is:
this->speed_
When creating the Lambda, speed_ is 10; afterward, the original object changes speed_ to 20. Because [this] still points to the original object, when the Lambda is called, what is read is 20.
Pay special attention:[this] stores anobject pointer, not a copy of the object. If a lambda is stored and the original object has already been destroyed, accessing members through this lambda may cause dangling pointer issues. Therefore, when using [this] in stored callbacks, asynchronous tasks, and thread callbacks, special attention must be paid to object lifetime.
5.8.Example 8: Using [*this] to capture a copy of the current object
C++17 can be used starting from [*this]. It does not store a pointer to the current object; instead, it copies the current object when the Lambda is created.
#include <iostream>
class Robot
{
public:
Robot(int speed) : speed_(speed)
{
}
void demo()
{
auto print_speed = [*this]() {
std::cout << "lambda copy speed = " << speed_ << "\n";
};
speed_ = 20;
print_speed();
std::cout << "outside speed = " << speed_ << "\n";
}
private:
int speed_;
};
int main()
{
Robot robot(10);
robot.demo();
return 0;
}
Running result:
lambda copy speed = 10
outside speed = 20
Here, when Lambda is created:
[*this]
will copy the Robot object at that time and save it inside the Lambda. At this point, in the copy:
speed_ = 10
Afterwards:
speed_ = 20;
修改的是外面的原对象,不会修改 Lambda 已经保存的对象副本,因此 Lambda 仍然输出 10。
The two approaches can be compared as follows:
| Notation | What lambdas store | After the original object later modifies a member |
|---|---|---|
[this] |
A pointer to the current object | The lambda can see the new member value |
[*this] |
A copy of the current object | The lambda retains the member value at capture time |
[*this]This can avoid directly leaving a dangling this pointer due to the destruction of the original object, but it copies the entire object, so it is also necessary to consider whether the object is suitable for copying and the copying cost.
If you need to modify this copy of the object inside the Lambda, similar to ordinary capture by value, you can additionally combine it with mutable:
auto callback = [*this]() mutable {
++speed_;
};
What is modified here is still only the copy of the object stored inside the Lambda, and the original object outside will not be modified.
6.Key syntax explanation
| Example | Key point | Note |
|---|---|---|
| Example 1 | Comparison with old callback style | Ordinary functions and function objects can both serve as callbacks, but lambdas are better suited for short local logic |
| Example 2 | Parameters and return value | The return type can usually be deduced; when branches return different types, it must be written explicitly |
| Example 3 | Capture list | [x] copy,[&x] reference |
| Example 4 | mutable |
What is modified is the lambda's internal copy and does not affect the external variable |
| Example 5 | STL algorithms | sort、find_if、count_if are often used with lambdas |
| Example 6 | Lifetime | Do not casually capture local variables by reference in stored callbacks, asynchronous callbacks, or thread callbacks |
| Example 7 | [this] |
Captures the current object pointer; the lambda accesses the original object |
| Example 8 | [*this] |
C++17 captures a copy of the current object starting from 17, separated from the original object's state |
7.Common Errors
- Using default reference capture when saving a callback
[&], causing local variables to still be accessed after they are destroyed. - Assuming that capture by value will change along with the external variable. Capture by value stores a copy from when the lambda was defined.
- Wanting to modify a by-value capture copy, but forgetting to add
mutable. - Using a lambda with captures as a function pointer. A lambda with captures needs to be saved using a template parameter, a
autovariable, orstd::function. - After capturing
thisin an asynchronous scenario, the object is destroyed first. It is necessary to ensure the object's lifetime, or use a smart pointer,[*this]or other clearer approaches.
8.Usage Recommendations
- For small, localized logic, prefer lambdas.
- Make the capture list as explicit as possible
[x, &y], and avoid the default[=]and[&]. - When storing callbacks, threads, timers, or asynchronous operations, prefer capturing the needed data by value.
- For callbacks that are called only once and do not need to be stored, you can pass the lambda directly to an algorithm or function template.
- When using
[this]for callbacks in class member functions, ensure the object's lifetime; if you need to independently preserve the object's state, you can consider C++17's[*this]. - When you need to store different lambdas uniformly, then use the
std::functionfrom the next section.
9.Summary
- Lambda 是匿名可调用对象,写法是
[捕获](参数) { 函数体 }。 - 捕获列表决定 lambda 如何使用外部变量。
[x]是按值捕获,[&x]是按引用捕获。mutable允许修改按值捕获的内部副本。[this]捕获当前对象指针,[*this]从 C++17 起可以捕获当前对象副本。- Lambda 最常用于 STL 算法、回调和异步任务。
- 生命周期是 Lambda 最容易出错的地方,尤其是保存回调和异步回调。