How Do Static Variables in Lambda Function Objects Work

How do static variables in lambda function objects work?

tl;dr version at the bottom.


§5.1.2 [expr.prim.lambda]

p1 lambda-expression:

lambda-introducer lambda-declaratoropt compound-statement

p3 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:

  • The type of a lambda expression is created in the innermost scope.
  • It is not created anew for each function call (that wouldn't make sense, since the enclosing function body would be as my example above).
  • It obeys (nearly) all the rules of normal classes / structs (just some stuff about 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.

How does lambda capture local static variable?

You don't need to capture global or static variables. Only automatic variables have to be captured if you want to use them.

Because of that, yes, you can convert such lambda to a function pointer.


Cppreference:

The identifier in any capture without an initializer (other than the this-capture) is looked up using usual unqualified name lookup in the reaching scope of the lambda. The result of the lookup must be a variable with automatic storage duration declared in the reaching scope.

(emphasis mine)

"To capture" means to put a copy or a reference to a variable into a lambda object itself.

Global and static variables have fixed memory location. You don't need to store copies or references to them to use them since their location is known at compile time and doesn't change.

(If you really want to have a copy of a global or static variable, you can create it using named capture from C++14, which lets you create a custom member variable in a lambda object. But such capture is not necessary if you just want to use that global / static variable.)

Automatic variables, on the other hand, don't have a fixed location. Multiple lambdas created at the exact same place could refer to different automatic variables. Because of that, a lambda object has to capture such variable - which means to contain either a reference to used automatic variable or a copy of it.

Lambda state behavior differences between an internal static and a captured mutable

static variables inside a lambda behave just the same as ones declared elsewhere.

Variables captured by value behave as nonstatic data members of the lambda closure type. By default these members are treated as const in the lambda body; the effect of mutable is to remove that const. But in any case, they behave as members such as in a user-defined class.

So, captures and static locals are simply different things. The latter are not affected by mutable.

Static variables in C++ lambdas

C++ allows it because C++ is not a safe language. And while this case may be "trivial" to check for (and personally, I don't agree that it's trivial, but I'm not a compiler writer), there are plenty of other cases that are not trivial to check for.

C++ is not in the business of fixing your broken code for you. It does exactly and only what you tell it to, even if it's massively unwise.

Furthermore, it's not entirely clear exactly what it is that you were intending this code to do. For example, these are two completely different things:

std::function<int (void)> get_incrementer() {
return []() {
static int count = 0;
return count++;
};
}

std::function<int (void)> get_incrementer() {
int count = 0;
return [count]() mutable {
return count++;
};
}

In the first case, every instance of the returned function will share the same increment count. In the second case, each time you call get_incrementer, you will get a separate object with it's own increment count.

Which one did the user want? It's not clear. So you can't just willy-nilly "correct" it.

Assign a lambda function to static member variable (c++)

A core constant expression may contain a lambda only starting from C++17 (see cppreference point 8). This was proposal N24487 and got into C++17 as P0170R0.

If you have to use a static lambda, you can make use of the construct at first use idiom:

#include <iostream>

template <typename T>
class Test {
public:
std::function<T(T)> createLambda() {
static const std::function<T(T)> returnLambda = [](T val) -> T {
return val;
};
return returnLambda;
}
};

int main() {
Test<int> lambdaFactory;
std::function<int(int)> n = lambdaFactory.createLambda();
std::cout << n(123) << std::endl;
}

C++: Force lamba instances to have unique static variables

Ditch the static variable and use an extended lambda capture:

#include <iostream>

auto make_lambda(){
return [count = 0]() mutable {
return count++;
};
}

If you want different lambda instances to share state with their respective copies but not between them, you can use a std::shared_ptr instead:

auto make_lambda(){
return [count = std::make_shared<int>(0)]() mutable {
return (*count)++;
};
}

Static variables used from lambdas are not initialized

At first glance, this seems like a compiler bug. link_match is not used within its immediate scope, which may confuse the optimizer. Here's a workaround:

void scrape_link(const std::string& url, std:function<void()> callback)
{
static const std::regex link_match {
R"re(href="([^"]+)")re",
std::regex_constants::optimize
};

// force the creation of link_match by using it in the capture clause.

async_download(url,[&link_match, callback=std::move(callback)] (std::vector<char>& data)
{
std::smatch matches;
if(std::regex_search(data.begin(), data.end(), matches, link_match))
std::cout << matches[1] << std::endl;
callback();
});
}


Related Topics



Leave a reply



Submit