Summary
In C++, when I return a lambda from a function that's captured a variable local to that function, what specifically happens, and why? The compiler (g++) seems to allow it, but it gives me different results than I was expecting, so I'm not sure if this is technically safe/supported.
Details
In some languages (Swift, Lisp, etc.) you can capture local variables in a closure/lambda, and they stay valid and in scope as long as the closure is in scope (I've heard it called "lambda over let over lambda" in a Lisp context). For example, in Swift, the example code for what I'm trying to do is:
func counter(initial: Int) -> (() -> Int) {
var count = initial
return { count += 1; return count }
}
let c = counter(initial: 0)
c() // returns 1
c() // returns 2
c() // returns 3
I tried to write a C++ equivalent of this like the following:
auto counter(int initial)
{
int count = initial;
return [&count] () -> int {
count = count + 1;
return count;
};
}
However, the result I get is:
auto c = counter(0);
std::cout << c() << std::endl; // prints 1
std::cout << c() << std::endl; // prints 1
std::cout << c() << std::endl; // prints 1
If I capture a variable that's still in scope, it works as I'd expect. For example, if I do all of the following in a single function:
int count = 0;
auto c = [&count] () -> int {
count = count + 1;
return count;
};
std::cout << c() << std::endl; // prints 1
std::cout << c() << std::endl; // prints 2
std::cout << c() << std::endl; // prints 3
So I guess my question is, in the first C++ example above, what is actually getting captured? And is it defined behavior, or do I just have a reference to some random memory on the stack?
return [&count] () -> int {
This is a capture by reference. The lambda captures a reference to this object.
The object in question, count
, is in the function's local scope, so when the function returns, count
gets destroyed, and this becomes a reference to an object that went out of scope and gets destroyed. Using this reference becomes undefined behavior.
Capturing by value seems to solve this problem:
return [count] () -> int {
But your obvious intent is so that each invocation of this lambda returns a monotonically-increasing counter value. And merely capturing the object by value is not enough. You also need to use a mutable lambda:
return [count] () mutable -> int
{
return ++count;
};
But the pedantical answer to your question "what happens" is that a lambda is essentially an anonymous class, and what a lambda captures are really class members. Your lambda is equivalent to:
class SomeAnonymousClassName {
int &count;
public:
SomeAnonymousClassName(int &count) : count(count)
{}
int operator()
{
// Whatever you stick in your lambda goes here.
}
};
Capturing something by reference translates to a class member that's a reference. Capturing something by value translates to a class member that's not a reference, and the act of capturing lambda variables translates to passing them to the lambda class's constructor, which is what happens when you create a lambda. A lambda is really an instance of an anonymous class, with a defined operator()
.
In a regular lambda, the operator()
is actually a const
operator method. In a mutable lambda, the operator()
is a non-const
, a mutable operator method.
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