const static auto lambda used with capture by reference
Asked Answered
D

2

9

While using some local lambda objects in a C++11 function I was tempted to declare them as const static auto lambda = ... just to let the compiler know that there is just one std::function object needed (and possibly optimize the call and/or inline it) but I realized that capturing local values by reference in this circumstance leads to weird behavior.

Consider the following code:

void process(const Data& data, const std::function<void(DataElement&>& lambda) {
  ...
}

void SomeClass::doSomething()
{
  int foo = 0;

  const static auto lambda = [&foo] () { .... ++foo; .... }

  process(data, lambda);
}

This doesn't work with multiple invocations of doSomething() but the mechanics is not clear.

  • Is foo bound at the first invocation and then kept bound to a stack address which becomes invalid on successive invocations?
  • Am I forced so drop static in this circumstance?

Where is this behavior specified in the standard? Considering it's a static variable where is it constructed? Lazily on first invocation of doSomething() (so that the first invocation works) or at program start?

Disinterested answered 23/6, 2015 at 10:6 Comment(2)
Anyway, currently, you construct the std::function from the lambda at each call. (and the creation of your lambda is light).Duleba
Why not static int foo = 0; ? Since there's only one lambda bound to foo, you probably don't need multiple copies of foo either.Vitiated
D
16

A static function-scope variable is initialised "lazily," when control flow first reaches its declaration. This means that the capture by reference does indeed bind to the foo currently on stack, and when the call terminates, that binding becomes dangling.

Don't try to help the compiler too much; making lambda static looks like a micro-optimisation, with very bad side effects. There's next to no overhead involved in actually creating a closure object, and the compiler can easily inline it regardless of whether it's static or not.

Not to mention the fact that you're not saving on creating the std::function object even with your approach. The type of a lambda expression is an unnamed closure object, not std::function. So even if lambda is static, the std::function object is created in each call anyway (unless the whole thing is inlined).

Daydream answered 23/6, 2015 at 10:13 Comment(7)
Very interesting, especially the last part. Could you elaborate on why the std::function object has to be created on each call? I don't understand why that happens...Spoil
@Spoil Because it's not cached anywhere. The static variable "caches" the closure object (the lambda function), but not a std::function. A temporary std::function is created by the call process(data, lambda) to initialise the function's parameter lambda.Daydream
Right... so would that be resolved if it's declared as a std::function rather than using auto?Spoil
@Spoil It would resolve the caching/re-creation, but you'd still be facing the problem with the reference to foo becoming dangling after the first call.Daydream
Yes, unless you make that a static as well, and make sure the body of the lambda resets it to 0 every time it's called, right?Spoil
@Spoil That would be one way of solving this, true. But we're getting into hack territory, for uncertain performance benefits. Is doSomething called so many times that the creation is a performance bottleneck? And if so, wouldn't it be better to turn process into a template and get rid of std::function altogether?Daydream
@Agnew Oh I completely agree that by now we're overcomplicating the problem immensely and it should be obvious that static is not the way to go. I just wanted to understand what you meant, and I do now ;-) ThanksSpoil
N
0

This doesn't work with multiple invocations of doSomething() but the mechanics is not clear.

This is because foo is allocated on the stack. The exact address of foo depends on the call stack that led to the invocation of doSomething. In other words, the address of foo is likely to be different between function invocations, unless the call stack is exactly the same.

Nephogram answered 23/6, 2015 at 10:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.