Do lambdas capture by copy at the point of declaration?
Asked Answered
B

4

36

The code below prints 0, but I expect to see a 1. My conclusion is that lambda expressions are not invoked by actually passing captured parameters to the functions, which is more intuitive. Am I right or am I missing something?

#include <iostream>
int main(int argc, char **argv){
  int value = 0;
  auto incr_value  = [&value]() { value++; };
  auto print_value = [ value]() { std::cout << value << std::endl; };
  incr_value();
  print_value();
  return 0;
}
Blais answered 22/7, 2012 at 10:22 Comment(0)
L
31

Lambda functions are invoked by actually passing captured parameters to the function.

value is equal to 0 at the point where the lambda is defined (and value is captured). Since you are capturing by value, it doesn't matter what you do to value after the capture.

If you had captured value by reference, then you would see a 1 printed because even though the point of capture is still the same (the lambda definition) you would be printing the current value of the captured object and not a copy of it created when it was captured.

Lumbard answered 22/7, 2012 at 10:41 Comment(2)
thanks. I was thinking by-value as in updates to value will not be visible outside the function. However, it sounds like it also means updates outside the function will not be visible inside the function.Blais
What might be misleading here is the wording: by value means "generate a copy". The lambda have a copy of the variable, it's value taken at the point of declaration of the lambda. As the lambda have a private copy, the original object isn't modified or read inside the lambda. That's why there is a capture by reference actually, to allow the case you want to see/modify the original.Dressing
R
18

Yes, the captures are done at the point the lambda is declared, not when it's called. Think of a lambda as a function object whose constructor takes the captured variables as parameters and assigns them to its corresponding member variables (either values or references, depending on the capture mode.) The actual call of the lambda has no magic, it's just a regular operator() call of the underlying function object.

Capturing things at the call point would not make much sense - what would be captured if the lambda was returned or passed as a parameter to another function and called there? There are actually languages that do behave this way - if you refer to a variable x in a function, it is assumed to refer to any variable called x currently in scope at the point of call. This is called dynamic scoping. The alternative, used by most languages because it makes reasoning about programs much simpler, is called lexical scoping.

http://en.wikipedia.org/wiki/Lexical_scoping#Lexical_scoping_and_dynamic_scoping

Rubstone answered 22/7, 2012 at 12:32 Comment(1)
"Think of a lambda as a function object whose constructor takes the captured variables as parameters and assigns them to its corresponding member variables." This is the best explanation I've read. Thanks.Lowboy
D
7

The problem is that your print function is capturing by value and not by reference.

#include <iostream>
int main(int argc, char **argv){
  int value = 0;
  auto incr_value  = [&value]() { value++; };
  auto print_value = [ value]() { std::cout << value << std::endl; };
  auto print_valueref = [ &value]() { std::cout << value << std::endl; };

  incr_value();
  print_value();
  print_valueref();
  return 0;
}

Outputs 0 and 1 as expected. The first one is captured by value and prints the value at the point of capture; the second one captures the reference and then prints its value.

Dichlorodifluoromethane answered 22/7, 2012 at 21:44 Comment(0)
L
0

My conclusion is that lambda expressions are not invoked by actually passing captured parameters to the functions [...]

That is correct. The captures work differently from the function parameters of the lambda.

As explained in [expr.prim.lambda.capture] p10, the capture [value] adds an int non-static data member to the closure type of the lambda. You can think of the compiler doing something along the lines of:

// auto print_value = [ value]() { std::cout << value << std::endl; };

struct __lambda {
  private:
    int value;
    void operator()() const { std::cout << value << std::endl; }
};

auto print_value = __lambda{value};

Note: this is not literally what the compiler does, only an approximation.

As seen in the code above, and as stated in [expr.prim.lambda.capture] p15:

When the lambda-expression is evaluated, the entities that are captured by copy are used to direct-initialize each corresponding non-static data member of the resulting closure object, and [...]

Therefore, value is copied during the initialization of print_value, before incr_value() is executed. incr_value() changes value in main, but by that point, print_value has its own copy with value 0, which is printed.

Lefty answered 8/6 at 14:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.