Are static variables used in a lambda retained across calls of the function wherein the lambda is used? Or is the function object "created" again each function call?
Useless Example:
#include <iostream> #include <vector> #include <algorithm> using std::cout; void some_function() { std::vector<int> v = {0,1,2,3,4,5}; std::for_each( v.begin(), v.end(), [](const int &i) { static int calls_to_cout = 0; cout << "cout has been called " << calls_to_cout << " times.\n" << "\tCurrent int: " << i << "\n"; ++calls_to_cout; } ); } int main() { some_function(); some_function(); }
What is the correct output for this program? Is it dependent on the fact if the lambda captures local variables or not? (it will certainly change the underlying implementation of the function object, so it might have an influence) Is it an allowed behavioural inconsistency?
I'm not looking for: "My compiler outputs ...", this is too new a feature to trust current implementations IMHO. I know asking for Standard quotes seems to be popular since the world discovered such a thing exists, but still, I would like a decent source.
Static Variables: When a variable is declared as static, then a single copy of the variable is created and shared among all objects at a class level. Static variables are, essentially, global variables. All instances of the class share the same static variable.
A lambda or anonymous method may have a static modifier. The static modifier indicates that the lambda or anonymous method is a static anonymous function. A static anonymous function cannot capture state from the enclosing scope.
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.
The [=] you're referring to is part of the capture list for the lambda expression. This tells C++ that the code inside the lambda expression is initialized so that the lambda gets a copy of all the local variables it uses when it's created.
tl;dr version at the bottom.
§5.1.2 [expr.prim.lambda]
p1 lambda-expression:
lambda-introducer lambda-declaratoropt compound-statementp3 The type of the lambda-expression (which is also the type of the closure object) is a unique, unnamed nonunion class type — called the closure type — whose properties are described below. This class type is not an aggregate (8.5.1). The closure type is declared in the smallest block scope, class scope, or namespace scope that contains the corresponding lambda-expression. (My note: Functions have a block scope.)
p5 The closure type for a lambda-expression has a public
inline
function call operator [...]p7 The lambda-expression’s compound-statement yields the function-body (8.4) of the function call operator [...]
Since the compound-statement is directly taken as the function call operator's body, and the closure type is defined in the smallest (innermost) scope, it's the same as writing the following:
void some_function() { struct /*unnamed unique*/{ inline void operator()(int const& i) const{ static int calls_to_cout = 0; cout << "cout has been called " << calls_to_cout << " times.\n" << "\tCurrent int: " << i << "\n"; ++calls_to_cout; } } lambda; std::vector<int> v = {0,1,2,3,4,5}; std::for_each( v.begin(), v.end(), lambda); }
Which is legal C++, functions are allowed to have static
local variables.
§3.7.1 [basic.stc.static]
p1 All variables which do not have dynamic storage duration, do not have thread storage duration, and are not local have static storage duration. The storage for these entities shall last for the duration of the program.
p3 The keyword
static
can be used to declare a local variable with static storage duration. [...]
§6.7 [stmt.dcl] p4
(This deals with initialization of variables with static storage duration in a block scope.)
[...] Otherwise such a variable is initialized the first time control passes through its declaration; [...]
To reiterate:
this
is different), since it is a non-union class type. Now that we have assured that for every function call, the closure type is the same, we can safely say that the static local variable is also the same; it's initialized the first time the function call operator is invoked and lives until the end of the program.
The static variable should behave just like it would in a function body. However there's little reason to use one, since a lambda object can have member variables.
In the following, calls_to_cout
is captured by value, which gives the lambda a member variable with the same name, initialized to the current value of calls_to_cout
. This member variable retains its value across calls but is local to the lambda object, so any copies of the lambda will get their own calls_to_cout member variable instead of all sharing one static variable. This is much safer and better.
(and since lambdas are const by default and this lambda modifies calls_to_cout
it must be declared as mutable.)
void some_function() { vector<int> v = {0,1,2,3,4,5}; int calls_to_cout = 0; for_each(v.begin(), v.end(),[calls_to_cout](const int &i) mutable { cout << "cout has been called " << calls_to_cout << " times.\n" << "\tCurrent int: " << i << "\n"; ++calls_to_cout; }); }
If you do want a single variable to be shared between instances of the lambda you're still better off using captures. Just capture some kind of reference to the variable. For example here's a function that returns a pair of functions which share a reference to a single variable, and each function performs its own operation on that shared variable when called.
std::tuple<std::function<int()>,std::function<void()>> make_incr_reset_pair() { std::shared_ptr<int> i = std::make_shared<int>(0); return std::make_tuple( [=]() { return ++*i; }, [=]() { *i = 0; }); } int main() { std::function<int()> increment; std::function<void()> reset; std::tie(increment,reset) = make_incr_reset_pair(); std::cout << increment() << '\n'; std::cout << increment() << '\n'; std::cout << increment() << '\n'; reset(); std::cout << increment() << '\n';
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With