Search

7.16 — Lambda captures


Capture clauses and capture by value

In the previous lesson, 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::ref that allows us to pass a normal type as if it were a reference. By wrapping our lambda in a std::ref, 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 (6.18 -- 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


7.x -- Chapter 7 comprehensive quiz
Index
7.15 -- Introduction to lambdas (anonymous functions)

114 comments to 7.16 — Lambda captures

  • Quiz3

    Wold you please check my solution for quiz #3?

    • nascardriver

      If you have no intentions of modifying something, especially pointers and references, make it `const`.
      Use `myVector.empty()` to check if a vector is empty.
      A while(true) loop is easier to understand than a do-while(true) loop. A do-while loop makes the reader expect that there's a condition at the end.
      You don't need `std::tie`, you can use a structured binding

      However, tuples make your code harder to read, because there's no name for their values. You could make `checkInputValidation` return the input and call it in `main` directly.

      • Quiz3

        Thank you for all the valuable tips. I was going to use structure type for getting back the value of tuples but because of the following guideline, I used std::tie.

        "Using a struct is a better option than a tuple if you’re using the struct in multiple places. However, for cases where you’re just packaging up these values to return and there would be no reuse from defining a new struct, a tuple is a bit cleaner since it doesn’t introduce a new user-defined data type."

        Happy new avatar by the way ;)

  • Quiz3

    Would you please check my code for quiz? (I haven't looked at your solution. I wanted first to write on my own)

    Thank you.

  • question

    For more clarification:

    >> 32 50 72 98 128 162 200 242
    >76
    how '76' should be output? '76' is wrong? or '76' is close to '72'?

    >>4 16 36
    >4
    >16
    >17
    how '17' should be output? '17' is wrong? or '17 is close to 16'?

    • nascardriver

      The distance between 72 and 76 is 4. That's not more than 4. "'76' is close to '72'"
      The distance between 17 and 16 is 1. That's not more than 4. "'17 is close to 16'"

  • Rushhab

    "the second IN the amount OF numbers to generate."
    The second is the amount of numbers to generate.

  • Rushhab

    >>When std::function is created with a lambda, the std::function internally makes a copy of the lambda object.

    It seems that after the first invoke() when 'i' get increased, the increased value isn't saved in the copy version of lambda object that std::function made, right? That's why on the second and last call to 'invoke' the 'i' with value 0 is still used , although 'i'; had been increased on the first call to invoke, still 'i' with value 0 is kept. Is that because std::function is stateless?

    And, is " invoke(std::ref(count));" the same as "count();" because I tried to output a message inside 'invoke' function, but it never get to print that message. it seems that 'invoke' never get to be called.

    • nascardriver

      Each call to `invoke(count)` creates a temporary `std::function` which is discarded at the end of the call. If you create the `std::function` first and then pass the `std::function` variable to `invoke` (And `invoke` takes a `std::function` by reference), you'll see `i` being increased.

      `invoke(std::ref(count))` is not the same as `count()`. Please post your code.

  • Yahdan

    I am reading the book C++ Prime fifth edition along with this tutorial. There is something I would like to mention:

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

    >> As with a parameter passed by value, it must be possible to copy such variables. Unlike parameters, the value of a captured variable is copied when the lambda is created, not when it is called.  (C++ Prime fifth edition)

    You said "the members of the lambda are initialized at that point" while the book says "  the value of a captured variable is copied when the lambda is created, not when it is called. "

    They seems to be in contrast to each other, so Which one is correct?

    • nascardriver

      "instantiated" is the same as "created". The capture list doesn't do anything when the lambda gets called.

      • Yahdan

        Got it! Thanks
        I got another question to ask:

        >>For now, what’s useful to understand is that when we pass a lambda to a function, we are defining both a new type and an object of that type: The argument is an unnamed object of this compiler-generated class type. Similarly, when we use auto to define a variable initialized by a lambda, we are defining an object of the type generated from that lambda. (C++ Premier 5th edition)

        1) Do those sentence mean for each 'invoke(count)' a new object of class lambda is instantiated and passed to the 'invoke' function?

        2)I just didn't get the part says 'a new type'? Why a new type is generated each time the function 'invoke' gets called? because we just have one lambda definition here.

        • nascardriver

          1) `std::function` creates a copy of the callable object (The lambda) which it then stores. Nothing gets re-captured, but a copy of the lambda is created. The temporary `std::function` (Which stores a copy of the lambda) is passed to `invoke`.

          2) Without further context, I can only assume that the book isn't talking about passing the lambda like we did, but about creating the lambda in the call, ie.

          In your code example, there's only 1 lambda type.

  • Lambda(pass by value vs reference )

    >> 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.

    According to the statements above, what is the point of passing a lambda by 'reference using &'  and passing by 'value'  while the std::function always makes a copy of the lambda object?

    In other words, in the code below, which part of lambda will OR will not be modified/effected when the parameter of invoke function is passed by reference or value?

    • nascardriver

      `std::function` doesn't do anything special with lambdas. In both of your examples, for every call to `invoke`, a `std::function` is constructed and passed to `invoke`. The reference doesn't have any effect here, because you're constructing a temporary `std::function`.

      Your question is unrelated to the lambda, but it is about passing `std::function` in general and about `std::function` handling callable objects. If you change the type of `count` from a `auto` (ie. the lambda's real type) to `std::function`, you'll see a difference. Because if you do that, your pass-by-value example has to copy the `std::function` (And thus the stored lambda), whereas the pass-by-reference example doesn't create any addition copies.

  • Dark_Chocolate

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

    Need a clarification here: Considering the example below, when the compiler encounters line #4 "[i]() ..." then it creates a lambda object, and at the runtime the lambda object is instantiated, so does this run-time part mean on line #12 or again line #4? and Is this process the same for 'otherCount'?

    • nascardriver

      The lambda is instantiated (and `i` captured) in line 4. If you modify `i` in line 11, `count` will still have the old `i`.
      `otherCount` creates a copy of `count` as it is in line 13.

      • Dark_Chocolate

        Thx.
        Then what happened when 'count()' is encountered in the run-time?

        • nascardriver

          The lambda gets called, but nothing is copied.

          • Dark_Chocolate

            So, in the code above if we comment out the line #12, then we would just have a class or blueprint of lambda with data member 'i' was created but 'i' is not initialized yet and no object of class lambda is created, right?

            Considering the statement above, if we change the code above to the following, what is passed to the invoke? Because no object of class lambda is instantiated yet! Instantiating happens after fn() is called and then object of the class lambda is created and then data member 'i' is initialized and this is where instantiating taking place and this hasn't yet taken place on line 18. So what is exactly passing to the invoke? is it like passing name of a class instead of the object of that class?

            • m

              In this case instantiating happens at line 9 so the lambda object is passed to the invoke.

            • nascardriver

              @m is right. The call does not copy or instantiate anything apart from arguments. The lambda is instantiated (and variables captured) at its definition (Line 9).

              When the `std::function` is created in line 17, the existing lambda gets copied. Nothing gets re-captured, because everything has been captured already.

  • Sadra

    Hey guys,

    had a question on:  

    :
    Does that line above, behind the scenes translate to this?  

    Also, is [=] the same as calling a function by value( though constant value despite the function which is not constant) and [&] the same as calling a function by a reference?

    • nascardriver

      The variables in [] aren't function parameters, they're members of the lambda object (Like variable members of a struct). When you use [=], the variables will be copied into the lambda object. When you use [&], the members will be references to your variables (ie. no copy takes place).

  • Apple

    I have corrected the issues that you pointed out. While I was doing it I've noticed that changeAllValues() is not used anywhere! It was supposed to be used but my idea on how to solve a certain problem has changed and it became useless, so I removed it.

    Changes:
    - Changed the for loop into a for-each loop
    - removed 'changeAllValues'
    - Changed 'while(numbers.size() != 0)' into 'while( !(numbers.empty()) )'
    - Replaced std::function in line 50 (earlier line 63) with 'auto'

    Thank you for all the advice! Here's the corrected code:.

  • mutable along with return type

    If I want explicitly define the return type, where I should put that? I checked out https://en.cppreference.com/w/cpp/language/lambda, but couldn't find anything.
    The following gives compile-error!

    • nascardriver

      From cppreference

      captures = i
      <tparams> aren't used
      (params) = ()
      specifiers = mutable
      exception is empty
      attr is empty
      ret = int
      requires isn't used
      {body} = {return ++1;}

  • comparison with different result

    When I run the code below, I got 4 comparisons but yours is 2. I am using visual studio the latest version.  What is the reason?

    • nascardriver

      It's up to the standard library developers which sorting algorithm they use. Different algorithms require different numbers of comparisons.

Leave a Comment

Put all code inside code tags: [code]your code here[/code]