Search

10.16 — Lambda captures


Capture clauses and capture by value

In the previous lesson (10.15 -- Introduction to lambdas (anonymous functions)), we introduced this example:

Now, let’s modify the nut example and let the user pick a substring to search for. This isn’t as intuitive as you might expect.

This code won’t compile. Unlike nested blocks, where any identifier defined in an outer block is accessible in the scope of the nested block, lambdas can only access specific kinds of identifiers: global identifiers, entities that are known at compile time, and entities with static storage duration. search fulfills none of these requirements, so the lambda can’t see it. That’s what the capture clause is there for.

The capture clause

The capture clause is used to (indirectly) give a lambda access to variables available in the surrounding scope that it normally would not have access to. All we need to do is list the entities we want to access from within the lambda as part of the capture clause. In this case, we want to give our lambda access to the value of variable search, so we add it to the capture clause:

The user can now search for an element of our array.

Output

search for: nana
Found banana

So how do captures actually work?

While it might look like our lambda in the example above is directly accessing the value of main‘s search variable, this is not the case. Lambdas might look like nested blocks, but they work slightly differently (and the distinction is important).

When a lambda definition is executed, for each variable that the lambda captures, a clone of that variable is made (with an identical name) inside the lambda. These cloned variables are initialized from the outer scope variables of the same name at this point.

Thus, in the above example, when the lambda object is created, the lambda gets its own cloned variable named search. This cloned search has the same value as main‘s search, so it behaves like we’re accessing main‘s search, but we’re not.

While these cloned variable have the same name, they don’t necessarily have the same type as the original variable. We’ll explore this in the upcoming sections of this lesson.

Key insight

The captured variables of a lambda are clones of the outer scope variables, not the actual variables.

For advanced readers

Although lambdas look like functions, they’re actually objects that can be called like functions (these are called functors -- we’ll discuss how to create your own functors from scratch in a future lesson).

When the compiler encounters a lambda definition, it creates a custom object definition for the lambda. Each captured variable becomes a data member of the object.

At runtime, when the lambda definition is encountered, the lambda object is instantiated, and the members of the lambda are initialized at that point.

Captures default to const value

By default, variables are captured by const value. This means when the lambda is created, the lambda captures a constant copy of the outer scope variable, which means that the lambda is not allowed to modify them. In the following example, we capture the variable ammo and try to decrement it.

In the above example, when we capture ammo, a new const variable with the same name and value is created in the lambda. We can’t modify it, because it is const, which causes a compile error.

Mutable capture by value

To allow modifications of variables that were captured by value, we can mark the lambda as mutable. The mutable keyword in this context removes the const qualification from all variables captured by value.

Output:

Pew! 9 shot(s) left.
Pew! 8 shot(s) left.
10 shot(s) left

While this now compiles, there’s still a logic error. What happened? When the lambda was called, the lambda captured a copy of ammo. When the lambda decremented ammo from 10 to 9 to 8, it decremented its own copy, not the original value.

Note that the value of ammo is preserved across calls to the lambda!

Capture by reference

Much like functions can change the value of arguments passed by reference, we can also capture variables by reference to allow our lambda to affect the value of the argument.

To capture a variable by reference, we prepend an ampersand (&) to the variable name in the capture. Unlike variables that are captured by value, variables that are captured by reference are non-const, unless the variable they’re capturing is const. Capture by reference should be preferred over capture by value whenever you would normally prefer passing an argument to a function by reference (e.g. for non-fundamental types).

Here’s the above code with ammo captured by reference:

This produces the expected answer:

Pew! 9 shot(s) left.
9 shot(s) left

Now, let’s use a reference capture to count how many comparisons std::sort makes when it sorts an array.

Possible output

Comparisons: 2
Honda Civic
Toyota Corolla
Volkswagen Golf

Capturing multiple variables

Multiple variables can be captured by separating them with a comma. This can include a mix of variables captured by value or by reference:

Default captures

Having to explicitly list the variables you want to capture can be burdensome. If you modify your lambda, you may forget to add or remove captured variables. Fortunately, we can enlist the compiler’s help to auto-generate a list of variables we need to capture.

A default capture (also called a capture-default) captures all variables that are mentioned in the lambda. Variables not mentioned in the lambda are not captured if a default capture is used.

To capture all used variables by value, use a capture value of =.
To capture all used variables by reference, use a capture value of &.

Here’s an example of using a default capture by value:

Default captures can be mixed with normal captures. We can capture some variables by value and others by reference, but each variable can only be captured once.

Defining new variables in the lambda-capture

Sometimes we want to capture a variable with a slight modification or declare a new variable that is only visible in the scope of the lambda. We can do so by defining a variable in the lambda-capture without specifying its type.

userArea will only be calculated once when the lambda is defined. The calculated area is stored in the lambda object and is the same for every call. If a lambda is mutable and modifies a variable that was defined in the capture, the original value will be overridden.

Best practice

Only initialize variables in the capture if their value is short and their type is obvious. Otherwise it’s best to define the variable outside of the lambda and capture it.

Dangling captured variables

Variables are captured at the point where the lambda is defined. If a variable captured by reference dies before the lambda, the lambda will be left holding a dangling reference.

For example:

The call to makeWalrus creates a temporary std::string from the string literal “Roofus”. The lambda in makeWalrus captures the temporary string by reference. The temporary string dies when makeWalrus returns, but the lambda still references it. Then when we call sayName, the dangling reference is accessed, causing undefined behavior.

Note that this also happens if name is passed to makeWalrus by value. The variable name still dies at the end of makeWalrus, and the lambda is left holding a dangling reference.

Warning

Be extra careful when you capture variables by reference, especially with a default reference capture. The captured variables must outlive the lambda.

If we want the captured name to be valid when the lambda is used, we need to capture it by value instead (either explicitly or using a default-capture by value).

Unintended copies of mutable lambdas

Because lambdas are objects, they can be copied. In some cases, this can cause problems. Consider the following code:

Output

1
2
2

Rather than printing 1, 2, 3, the code prints 2 twice. When we created otherCount as a copy of count, we created a copy of count in its current state. count‘s i was 1, so otherCount‘s i is 1 as well. Since otherCount is a copy of count, they each have their own i.

Now let’s take a look at a slightly less obvious example:

Output:

1
1
1

This exhibits the same problem as the prior example in a more obscure form. When std::function is created with a lambda, the std::function internally makes a copy of the lambda object. Thus, our call to fn() is actually being executed on the copy of our lambda, not the actual lambda.

If we need to pass a mutable lambda, and want to avoid the possibility of inadvertent copies being made, there are two options. One option is to use a non-capturing lambda instead -- in the above case, we could remove the capture and track our state using a static local variable instead. But static local variables can be difficult to keep track of and make our code less readable. A better option is to prevent copies of our lambda from being made in the first place. But since we can’t affect how std::function (or other standard library functions or objects) are implemented, how can we do this?

Fortunately, C++ provides a convenient type (as part of the <functional> header) called std::reference_wrapper that allows us to pass a normal type as if it were a reference. For even more convenience, a std::reference_wrapper can be created by using the std::ref() function. By wrapping our lambda in a std::reference_wrapper, whenever anybody tries to make a copy of our lambda, they’ll make a copy of the reference instead, which will copy the reference rather than the actual object.

Here’s our updated code using std::ref:

Our output is now as expected:

1
2
3

Note that the output doesn’t change even if invoke takes fn by value. std::function doesn’t create a copy of the lambda if we create it with std::ref.

Rule

Standard library functions may copy function objects (reminder: lambdas are function objects). If you want to provide lambdas with mutable captured variables, pass them by reference using std::ref.

Best practice

Try to avoid lambdas with states altogether. Stateless lambdas are easier to understand and don’t suffer from the above issues, as well as more dangerous issues that arise when you add parallel execution.

Quiz time


Question #1


Which of the following variables can be used by the lambda in main without explicitly capturing them?

Show Solution

Question #2

What does the following code print? Don’t run the code, work it out in your head.

Show Solution

Question #3


We’re going to write a little game with square numbers (numbers which can be created by multiplying an integer with itself (1, 4, 9, 16, 25, …)).

Ask the user to input 2 numbers, the first is the square root of the number to start at, the second is the amount of numbers to generate. Generate a random integer from 2 to 4, and square numbers in the range that was chosen by the user. Multiply each square number by the random number. You can assume that the user enters valid numbers.

The user has to calculate which numbers have been generated. The program checks if the user guessed correctly and removes the guessed number from the list. If the user guessed wrong, the game is over and the program prints the number that was closest to the user’s final guess, but only if the final guess was not off by more than 4.

Here are a couple of sample sessions to give you a better understanding of how the game works:

Start where? 4
How many? 8
I generated 8 square numbers. Do you know what each number is after multiplying it by 2?
> 32
Nice! 7 number(s) left.
> 72
Nice! 6 number(s) left.
> 50
Nice! 5 number(s) left.
> 126
126 is wrong! Try 128 next time.
  • The user chose to start at 4 and wants to play with 8 numbers.
  • Each square number will be multiplied by 2. 2 was randomly chosen by the program.
  • The program generates 8 square number, starting with 4 as a base:
  • 16 25 36 49 64 81 100 121
  • But each number is multiplied by 2, so we get:
  • 32 50 72 98 128 162 200 242
  • Now the user starts to guess. The order in which in guesses are entered doesn’t matter.
  • 32 is in the list.
  • 72 is in the list.
  • 126 is not in the list, the user loses. There is a number in the list (128) that is not more then 4 away from the user’s guess, so that number is printed.
Start where? 1
How many? 3
I generated 3 square numbers. Do you know what each number is after multiplying it by 4?
> 4
Nice! 2 numbers left.
> 16
Nice! 1 numbers left.
> 36
Nice! You found all numbers, good job!
  • The user chose to start at 1 and wants to play with 3 numbers.
  • Each square number will be multiplied by 4.
  • The program generates these square numbers:
  • 1 4 9
  • Multiplied by 4
  • 4 16 36
  • The user guesses all numbers correctly and wins the game.
Start where? 2
How many? 2
I generated 2 square numbers. Do you know what each number is after multiplying it by 4?
> 21
21 is wrong!
  • The user chose to start at 2 and wants to play with 2 numbers.
  • Each square number will be multiplied by 4.
  • The program generates these numbers:
  • 16 36
  • The user guesses 21 and loses. 21 is not close enough to any of the remaining numbers, so no number is printed.

Use std::find (9.25 -- Introduction to standard library algorithms) to search for a number in the list.
Use std::vector::erase to remove an element, e.g.

Use std::min_element and a lambda to find the number closest to the user’s guess. std::min_element works analogous to std::max_element from the previous quiz.

Show Hint

Show Solution


10.x -- Chapter 10 comprehensive quiz
Index
10.15 -- Introduction to lambdas (anonymous functions)

181 comments to 10.16 — Lambda captures

  • SlayerSavin

    Thanks for the Tutorial.Can you please clarify my doubt.

    >When std::function is created with a lambda, the std::function internally makes a copy of the lambda object. Thus, our call to fn() is actually being executed on the copy of our lambda, not the actual lambda.

    doesn't std::function create a function pointer to lambda function,why does it make a copy of lambda object instead of copying lambda function address?.

    Thanks in advance.

    • nascardriver

      In order for this lambda to work, it has to have `i`. By capturing `i`, we copied it into the lambda object.
      If `std::function` only held a pointer to the function inside the lambda, the lambda couldn't work. It wouldn't have any captured variables.

  • Pablo

    Hi, this is my solution to question #3, I would appreciate any feedback.

  • Sam

    Here is my solution to Question #3:

    • nascardriver

      - Identifiers in the global namespace starting in an underscore are reserved.
      - `rand_t` is only used in `get_random_number`, it shouldn't be a global alias.
      - Initialize variables with list-initialization for higher type-safety.
      - `size_t` is C, `std::size_t` is C++
      - Line 73: The lambda should return a `bool`.
      - If you swap line 93 and 95, you don't need the -1
      - Don't use `std::exit` unless an error is non-recoverable. Your variables won't be cleaned up properly. Instead, let control flow off the end of `main`, eg. by returning from `play_game`.

  • yeokaiwei

    1. Feedback on Quiz 3
    Quiz 3 <4 part was quite confusing with the std::min_element lambda limitation. The logic was to implement compare the absolute difference between your guess with the numbers above and below it. The logic is simple. Coding the logic into C++ with lambda and std::min_element, hard. I had to refer to the solution.

    Thank you for the tutorial, though I think it should be somewhere else.

    I hope this is acceptable.

    • yeokaiwei

      2. Queries about std::min_element
      Does std::min_element and std::max_element change into a generic std::sort when you add a Comparator?

      3. Problems with <4 with std::min_element lambda
      a. How to get specifically the 2 numbers above and below my guess with lambda.
      b. What do I do if my value is greater than the last value in my vector?
      ie  16 25 36 49 64 81 100 121. I enter 999.
      c. Getting the absolute value.

      a. Until now, I'm still not sure how the lamda managed to find the 2 nearest values to the guess.

      b. After looking at the solution, I guessed that when I entered 999, it would compare 999-100 and 999-121 and just return 121.

      c. Small issue. I tried using abs() naturally. A quick google search found std::abs and #include <cmath>.

    • nascardriver

      - `getRandomMT` isn't random. Don't re-seed random number generators unless you want to reset them.
      - Use more functions to achieve shorter functions
      - `functor` is an iterator
      - `std::min_element` and `std::max_element` loop through the entire container and return an iterator to the min/max element. They don't modify the container. `std::sort` sorts the container. Try implementing them yourself if you want to understand them better.
      - `howmany` is redundant as it's always equivalent to `sqnum.size()`. Use `sqnum.empty()` to check if the vector is empty.

      2. `std::min_element` and `std::max_element` don't sort the container.
      3.
      a. Assuming the container is sorted (Which it is in this game), use `std::upper_bound` to find the first element that is greater than the user's guess. Then use `std::prev` on that element to get the previous element (Which is the element below the user's guess).
      b. Are you still referring to (a)? If yes, then `std::upper_bound` returns the end, and you know that there is no greater value. Therefore, your container's last element is the element below the user's guess.
      c. What's the question here?

      a/b again. The lambda doesn't find anything, it only tells `std::min_element` how to compare 2 elements. Try implementing `std::min_element`.
      c again. `std::abs` was covered in chapter 5. It's normal not to remember everything. If you use a standard function without "std::", you're probably using the C version.

  • yeokaiwei

    1. Feedback on Disjoint
    There are topics like mutable and std::reference. However, there is no reference to them at all in the quiz. It's like garbage collection. Mutable and std::reference is initialized but not referenced in the Quiz.

    It's like spaghetti code since you have to jump backwards to use std::min_element.

  • yeokaiwei

    This is my code for Q3.

    I've yet to figure out the <4 method.

    So, I'll post it later.

    I hope this is acceptable for now.

  • yeokaiwei

    1. Feedback
    You don't explain "npos" which means "not a position".

    Does "return (str.find(search) != std::string_view::npos);" mean

    For the array str,
    find the user input "search",
    not-not equals its position ie equals its position.
    return its position

    • yeokaiwei

      2. Query on Quiz 2
      The lambda catches and copy the value BEFORE it and not AFTER it.
      I assume this is the default case.
      How does a user get the lambda to take values AFTER it?

      • nascardriver

        Capture the variable by reference

        • yeokaiwei

          1. First mistake
          When you said capture by reference, I thought of

          2. auto printFavoriteFruit{
                [=]() {
                  std::cout << "I like " << &favoriteFruit << '\n';
                }
              };
          This gets me "I like 00BCF9BC"

          Sorry, how am I supposed to capture by reference again?

          • nascardriver

            See section "Capture by reference"

            • yeokaiwei

              Got it, thanks nascar.

              Either by value [=]
              or by reference [&]

              [=, &favouritefruit] means first by value, then by reference.
              [&favouritefruit, =] means first by reference, then by value.

              Am I correct?

              • nascardriver

                Your second example doesn't work, because "the default capture has to be the first element in the capture group."

                Your lambda only uses `favoriteFruit`, so you only need to capture `favoriteFruit`

    • nascardriver

      As stated in the previous lesson, "std::string_view::find returns std::string_view::npos if it doesn't find the substring". That's all you need to know to use `std::string_view::npos` with `find()`. I've updated the sentence in the previous lesson to give `std::string_view::npos` more meaning.

      If you desire more information about something, you can look it up on cppreference as was shown in lesson S.4.4c, or any other reference. The tutorials should contain all the information you need to understand the rest of the tutorials, even without external references.

  • Rishi

    So the & operator in line 4 of the below code gets overrided and the function copies the lambda. Right?

    • nascardriver

      Parameters cannot be overridden. If you defined your function to take a reference it will always only accept a reference.

      What happens is that `invoke` wants a `std::function` but you're giving it a lambda, so the a new `std::function` will implicitly be created from your lambda in the call to `invoke`. The same is happening here

      `fn` wants a `double`, we giving it an `int`, so a new temporary `double` is created in the call to `fn`. That temporary `double` is passed to `fn` and destroyed at the end of line 9.

  • kile

    Thank you for your great tutorial.
    when i tested 2nd example in "Unintended copies of mutable lambdas"
    where i am very confused.
    Even more, I got something very weird.
    as i commented below, with "using namespace std" i got output 1,2,3
    but with "using std::function;using std::cout", i got output 1,1,1.
    (compiled by gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0 with flags
    -std=c++17 -Wall -Weffc++ -Wextra -Wsign-conversion -pedantic-errors)

    would you tell me what's wrong with the code?
      

  • srt1104

    I have a lot of confusion in the last section.
    To clear my confusion, I wrote this program but it confused me further.

    It's output on my machine:

    I know this is asking a lot, but can you explain what's happening in the above code?
    If not that, then can you tell me what's the structure of std::function data type(?) and how it creates copy of a lambda.
    Also, what's the difference between the following two function signatures? (I ask this because the last line states the output is the same with or without using &)

    and

    • nascardriver

      > can you tell me what's the structure of std::function data type(?) and how it creates copy of a lambda.
      `std::function` makes heavy use a templates, you cannot understand its implementation yet.
      To keep it simple, `std::function` creates a copy of whatever type you construct it with. If you construct it with a function pointer, it stores a function pointer. If you construct it with a lambda type, it stores a lambda type.

      Because you didn't say in specific what you don't understand, I'll pick some things that I think might be cause for your confusion.

      When you capture variables by value, they're copied. `main::i` and `main::lamba::i` are different variables.

      `fn` is a local variable of `byValue`. `byValue::fn` has the same address every time `byValue` is called, but it's a new object every time. The memory is being reused.

      `i`'s address changed, because when you use `std::ref`, `byValue::fn` internally refers to `main::count`, rather than to the copied lambda.

      The addresses change, because every time you call `byReference` with a lambda, a new `std::function` has to be constructed by `main`. Each `std::function` is constructed in a new memory location.

      `i`'s address stays the same, because the `std::function` (Which is still being constructed for every call) internally refers to `main::count`. Note that `i`'s address is the same as when you called `byValue(std::ref(count))`, because both times the lambda was not copied, the existing lambda was referenced.

      • srt1104

        Thanks. That cleared many things. Although I still don't understand how new std::function's are being created but when called with std::ref, they somehow internally refer to count in main(). Hopefully I will be able to understand it in future lessons.
        One last thing I just noticed is that count's address (009EF844) and i's address when called with std::ref are the same. How so?

        • nascardriver

          Lambdas are class-types (Like `struct`). Captured variables are members of the type. The address of the first member of a struct is (almost always) the same as the address of the instance of the struct.

  • Misbah Ahmed

    Thank you guys for your great tutorials. I would like to recommend to bring the comment box on the top of all comments.

  • Tony

    Hi again :) This is my solution for Quiz 3. I'd appreciate input or suggestions if you have some spare time!

    Note that I didn't come up with "return (std::abs(userGuess-a) < (userGuess - b))" myself, rather I needed to ask for help. My attempt returned an int (userGuess-a), but only later I realized that the lamda should've returned a bool! Using std::min_element was really hard for me in this case. In particular, I didn't realize that if std::min_element evaluates to true, it returns an iterator to the first element (in this case 'a'), meanwhile if the boolean in std::min_element evaluates to false, it returns the second element (in this case 'b'). It all makes much more sense now that I know how it actually works!

    • Tony

      I noticed that I forgot std::abs on (userGuess-b). New code:

      • nascardriver

        Line 58: You're checking if `*found != 0`. That's unnecessary. All you need to know is that an element was found, and you know that from line 47. Use `numbers.empty()` to check if a vector is empty. The rest looks good. Use an auto-formatter if you don't do so already. Be careful when using `std::tuple`. Unless it's obvious what the tuple elements mean, it's better to use a `struct`.

  • Lucky Abby

    I didn't know or remember we can do if statement this way:

    and these function, why not use push_back, any reason behind?

  • Henrique

    Hey guys, NASKARDRIVER, Thank you for the tutorial, it has helped me a lot. It's my first programming language, and I have read everything in this tutorial since the first page, sometimes I get confused, annnoyed, angry, sleepy, but I think I am doing well.
    For the Quiz #3, I did very well in my opinion, just had to look in your code for finding the nearest number with lambda. Anyway, do you mind giving me any advice about my code. Thank you!

    • nascardriver

      Hey!

      - `getOtherRandomNumber` isn't random. Don't seed random number generators more than once.
      - Disable precompiled headers. Don't include "pch.h". Your code only works in visual studio.
      - Don't use an indexed for-loop if you can use a range-based for-loop.
      - Use `std::empty` or `list.empty` to check if a container is empty. Getting the size can be more expensive.

      Otherwise you did very well :)

  • Sergey

    Thank you very much for the tutorials! I would be grateful for any feedback on my code

    • nascardriver

      `generateRandomNumber` isn't random. Don't seed random number generators more than once.
      Name variables descriptively. "fNumber" and "sNumber" help no one. The rest looks good though

  • Gustaw

    So, here is my piece of work, and I would be very thankful for every feedback from You.

  • James

    Hi Guys, many thanks for keeping this site maintained! I'm learning so much! Was just wondering if I could get some feedback on my code for question 2 (see below)? I also had a question regarding the lambda we needed for std::min_element; why is it not possible to chain conditional statements together in this lambda when using it with std::min_element? I thought I could kill two birds with one stone by doing something like this;

    This didn't seem to work at all and I tore my hair out trying to understand why. Then I had a brief peek at the solution and realised that this might not be possible. But I'm still left wondering why not?! Can you explain why this doesn't work? Thanks in advance.

    PS, I like to comment in my code, A LOT!

    • nascardriver

      Hi James!

      Your `getNearest` doesn't work, because it doesn't satisfy the "Compare" requirement ( https://en.cppreference.com/w/cpp/named_req/Compare ). In particular, if `a < b` is false, then `b < a` has to be true. Makes sense right? But for your `getNearest`, you have getNearest(9000, 9001) = false getNearest(9001, 9000) = false To your code, - Avoid abbreviations, name variables descriptively. No one except you knows what you mean by "mtrt" and in 2 weeks you won't know either. - Some of your comments are redundant. I can see that there are type aliases and an enum class, it doesn't help to state it in a comment. Only use comments to describe why you do something or what the meaning of something is. For example the use of `num1` and `num2` int `printPromt`. If you chose good names, you need very few comments. Every comment is a distraction from the code.

      - Avoid all caps names, they can collide with macros. - `NULL` is a pointer value. Use 0 for integers and `nullptr` for pointers. Don't use `NULL` for anything. - Pass fundamental types by value. Passing them by reference is slower. `enum class` is a fundamental type. - If you add an enumerator to `PhaseType`, your code will still compile but `printPromt` will fail. If you remove the `default` case and instead move that code after the `switch`-statement, your compiler will warn you if you forgot to implement something. (Add an empty case for MAX_PHASES). - Use 'single quotes' for characters. "Double quotes" are for strings. Strings are slow. - Line 110, 111: Duplicate comparison. - You know how many elements `getArray::nums` will have. Use `reserve` to prevent the vector from resizing. - As with variables, place type aliases in the smallest scope possible. `mt64_t` is only used in `getRandomInt`. - Every time you use `getInitInput`, you have to read the comment or definition to figure out what the tuple elements mean. If a tuple isn't immediately obvious, use a `struct` instead. - Line 221-224: You never need an if-statement for this. `return (nums.size() != 0);`, or better `return !nums.empty();` Lots of little issues, but nice structure overall :)

  • Yolo

    1) Hello guys, can you please check my answer as well? I want to better my programming.
    2) When i try to reference my vector(like in the quiz's solution)in a function parameter, i keep getting different errors.
    Do you know how can i change that?