perreal
perreal

Reputation: 97938

Do lambdas capture by copy at the point of declaration?

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;
}

Upvotes: 36

Views: 17501

Answers (4)

Jan Schultke
Jan Schultke

Reputation: 39443

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.

Upvotes: 0

VSOverFlow
VSOverFlow

Reputation: 1035

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.

Upvotes: 7

JohannesD
JohannesD

Reputation: 14421

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

Upvotes: 17

Ferruccio
Ferruccio

Reputation: 100648

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.

Upvotes: 31

Related Questions