Why this matters
Many programs need a short function for one job only. Sorting a list by a rule is one example. Counting the items that pass a test is another. Writing a full named function for each of these small jobs makes the code longer and spreads the logic across the file. A lambda is a way to write a small function right where you need it, with no name.
The hard part of lambdas is not the syntax. It is the square brackets at the start, which decide what the lambda can see from the code around it. Beginners often get a compile error about a variable "not captured" or they change a variable and find that the lambda did not see the change. This lesson explains the brackets step by step so those errors make sense.
This lesson covers lambdas only. Named functions, parameters and return values are covered in earlier lessons of this module. Passing functions around as values is covered in the callbacks lesson.
The idea: a function without a name
Start with a plain function. You write its name, its parameters and its body.
int add(int a, int b) {
return a + b;
}A lambda does the same job without the name. It is written inside another function, at the place where you need it, and it is stored in a variable. The general shape is:
[capture list](parameters) { body }The capture list is the pair of square brackets. It is empty in the first examples. The next sections explain what goes inside it.
Here is the smallest useful lambda.
The output is:
Hello from a lambda
add(2, 3) = 5Read the first lambda. [] is the empty capture list, which means the lambda uses nothing from outside. () is the empty parameter list, so the lambda takes no inputs. The body prints a line. The whole lambda is stored in a variable named hello, and hello(); calls it like any function.
The keyword auto tells the compiler to work out the type of hello from the value on the right. Every lambda has its own unique type that the programmer cannot write down, so auto is the normal way to store one. Notice also the semicolon after the closing brace. A lambda is an expression that you assign to a variable, so it ends with a semicolon like any other assignment.
The second lambda takes two parameters, a and b, and returns their sum. The return type is worked out by the compiler from the return statement, so you do not write it.
Passing a lambda to a standard algorithm
The most common use of a lambda is to give a short rule to a function from the C++ standard library. The standard library has functions such as count_if and sort that take a rule as an argument. The rule is often a lambda.
Here is a list of exam scores. The first job is to count how many scores are a pass.
The output is:
passing: 4count_if walks through the list. For each score it calls the lambda, and it counts each score for which the lambda returns true. The lambda [](int s) { return s >= 60; } is the rule. It has an empty capture list because it needs nothing from outside: the number 60 is written directly inside it.
Now sort the scores from highest to lowest. The sort function also takes a rule, which says whether one item should come before another.
The output is:
95 88 72 61 40 The lambda [](int a, int b) { return a > b; } says that a should come before b when a is larger. The result is a list from largest to smallest. The output has a trailing space after the last number, because the loop prints a space after each number.
Capturing a variable by value
The square brackets can name variables from the code around the lambda. This is called capturing. The simplest capture is by value. It copies the variable into the lambda at the moment the lambda is created.
The output is:
timesFactor(4) = 12
factor now = 10The lambda was created when factor was 3, so the lambda holds its own copy of 3. The line factor = 10; changes the variable in main, but it does not change the copy inside the lambda. The lambda still multiplies by 3, so timesFactor(4) gives 12.
Think of a photocopy. You copy a recipe onto a card and give the card to a friend. If you cross out a number on your original recipe, the friend's card still shows the old number. The copy is fixed when it is made.
Capturing a variable by reference
Sometimes the lambda should change the original variable, not a copy. To do that, put an ampersand, &, in front of the name inside the brackets. This is capture by reference. The lambda then works with the real variable, not a copy.
The output is:
total = 12The lambda [&total] refers to the variable total in main. Each call adds to that same variable, so after two calls the value is 12. The lambda has no return statement, so its return type is void, which means it gives back no value.
A reference capture is useful with standard algorithms too. Here the lambda adds up the lengths of some names.
The output is:
total letters: 13for_each calls the lambda once for each name. The lambda adds each length to totalLength in main. The word const in const string& name means the lambda reads the name but does not change it. You will see more about const in the read-only parameters lesson.
Changing a captured copy with mutable
A lambda that captures by value cannot change its copy by default. The copy is treated as a constant, which means it cannot be changed. Here is a lambda that tries to count up.
int count = 0;
auto inc = [count]() {
count++;
};This does not compile. The compiler reports:
error: increment of read-only variable ‘count’The fix is the keyword mutable. It goes after the parameter list and says that the lambda may change its own copy. The copy changes between calls, but the original variable outside the lambda does not change.
The output is:
1 2 3
outer counter = 0Each call adds 1 to the lambda's own copy and returns it. The variable counter in main is still 0 at the end, because the lambda never touched it. The copy lives inside the lambda for as long as the lambda exists.
Value or reference: a side-by-side check
The difference between the two kinds of capture is easiest to see when the original changes after the lambda is made. This program makes one lambda with each kind of capture, changes the variable, and then calls both lambdas.
The output is:
copy sees 1
reference sees 3The copy was made when the lambda was created, so it still holds 1. The reference always looks at the variable, so it sees the new value 3. When you write a lambda, ask yourself whether it should see changes made later. If yes, capture by reference. If no, capture by value.
A lambda that outlives its variable
A reference capture has one danger. The lambda refers to the original variable, so the variable must still exist when the lambda is called. A common mistake is to return a lambda from a function whose local variables are captured by reference. When the function ends, its local variables are destroyed. The lambda is still alive, and it now refers to memory that no longer belongs to the variable.
Here is the broken version. The lambda captures offset by reference, but offset is a local variable inside makeAdder.
#include <iostream>
using namespace std;
auto makeAdder(int base) {
int offset = 100;
return [&offset, base](int n) { return base + offset + n; };
}
int main() {
auto add = makeAdder(5);
cout << add(1) << endl;
return 0;
}This program compiles without an error, and with -Wall it gives no warning. The behaviour is undefined, which means the program is wrong even if it seems to work. When the code was built with -g -fsanitize=address, which is a tool that checks memory use while the program runs, GCC 15 reported this error:
ERROR: AddressSanitizer: stack-use-after-return
READ of size 4
#0 ... in operator() .../l3.cpp:6
#1 ... in main .../l3.cpp:11
Address ... is located in stack of thread T0 at offset 32 in frame
#0 ... in makeAdder(int) .../l3.cpp:4The process ID, the file paths and some of the lines are shortened here. The message says that the lambda read a stack variable (a variable that lives on the function call stack, which is the memory used by function calls) after the function had returned.
The fix is to capture by value. A copy of offset then lives inside the lambda and does not depend on makeAdder still running.
The output is:
6A lambda that is returned from a function, or stored for later use, should capture by value unless you are sure the captured variable lives longer than the lambda.
Common mistakes
Using a variable that was not captured
A lambda can only use names listed in its brackets, or names that are globals or other things that do not need capturing. A local variable from the code around the lambda must be listed. Here limit is a local variable, and the lambda uses it without listing it.
#include <iostream>
using namespace std;
int main() {
int limit = 5;
auto check = [](int n) {
return n < limit;
};
cout << check(3) << endl;
return 0;
}GCC 15 reports:
error: ‘limit’ is not captured
note: the lambda has no capture-default
note: ‘int limit’ declared hereThe fix is to write [limit] in the brackets.
Changing a captured copy without mutable
Covered in the mutable section above. The error is increment of read-only variable ‘count’. The fix is either mutable or capturing by reference with [&count], depending on whether the change should reach the original variable.
Expecting a value copy to update
A value capture keeps the value that the variable had when the lambda was created. Changing the variable later does not change the lambda's copy. If the lambda must see the newest value, capture by reference.
Capturing by reference a variable that is about to be destroyed
This is the dangling reference problem in the last section. It compiles, and it may even print the expected number, but the behaviour is undefined. Capture by value when the lambda may outlive the variable.
Forgetting the semicolon
A lambda assigned to a variable ends with a semicolon, because it is an assignment. Without the semicolon, the next line is read as part of the same statement. In this program the closing brace of hello is followed directly by hello();:
auto hello = []() {
cout << "hi" << endl;
}
hello();GCC 15 reports:
error: expected ‘,’ or ‘;’ before ‘hello’Practice
Exercise 1
Write a lambda named isLong that takes a const string& and returns true when the string has more than 5 characters. Use it with count_if to count how many words in {"cat", "elephant", "tree", "computer"} are long. Print the count.
Show the answer
vector<string> words = {"cat", "elephant", "tree", "computer"};
auto isLong = [](const string& w) { return w.size() > 5; };
cout << count_if(words.begin(), words.end(), isLong) << endl;With the words in that order, the count is 2, because elephant and computer each have 8 letters.
Exercise 2
Predict the output of this program. Then check by running it.
Show the answer
The output is:
1 7byValue holds a copy made when x was 1. byRef looks at x, which is 7 when it is called.
Exercise 3
The following lambda does not compile because factor is not captured. Fix it by changing only the brackets.
int factor = 4;
auto scale = [](int n) { return n * factor; };Show the answer
int factor = 4;
auto scale = [factor](int n) { return n * factor; };Capturing by value is enough here, because scale only reads factor.
Exercise 4
Predict the output of this program, then run it. The lambda should count from 1.
Show the answer
The output is:
1 2 3
next outside: 0The lambda changes its own copy of next on each call. The original next in main is never changed, because it was captured by value.
Exercise 5
Write a lambda that adds every number in {4, 5, 6} to a variable total declared in main. Use for_each and capture total by reference. Print the total.
Show the answer
int total = 0;
vector<int> nums = {4, 5, 6};
for_each(nums.begin(), nums.end(), [&total](int n) { total += n; });
cout << total << endl;The printed value is 15.
Summary
- A lambda is a small unnamed function. Store it with
autoand call it like a function. - The square brackets at the start list the outside variables the lambda may use.
[x]copiesxwhen the lambda is made.[&x]refers to the realx, so changes are visible both ways.- A value copy is read-only unless the lambda is marked
mutable. The copy changes, but the original does not. - A reference capture is unsafe when the lambda outlives the variable. Capture by value for lambdas that are returned or stored.