Scott Meyers, in Effective Modern C++, says, at lambda chapter, that:
Consider the following code:
void addDivisorFilter()
{
auto calc1 = computeSomeValue1();
auto calc2 = computeSomeValue2();
auto divisor = computeDivisor(calc1, calc2);
filters.emplace_back(
[&](int value) { return value % divisor == 0; }
);
}
This code is a problem waiting to happen. The lambda refers to the local variable
divisor
, but that variable ceases to exist whenaddDivisorFilter
returns. That's immediately afterfilters.emplace_back
returns, so the function that's added tofilters
is essentially dead on arrival. Using that filter yields undefined behaviour from virtually the moment it's created.
The question is: Why is it an undefined behaviour? For what I understand, filters.emplace_back
only returns after lambda expression is complete, and, during it execution, divisor
is valid.
Update
An important data that I've missed to include is:
using FilterContainer = std::vector<std::function<bool(int)>>;
FilterContainer filters;
A lambda is a syntax for creating a class. Capturing a variable means that variable is passed to the constructor for that class. A lambda can specify whether it's passed by reference or by value.
By default, variables are captured by const value . This means when the lambda is created, the lambda captures a constant copy of the outer scope variable, which means that the lambda is not allowed to modify them.
A type lambda lets one express a higher-kinded type directly, without a type definition. For instance, the type above defines a binary type constructor, which maps arguments X and Y to Map[Y, X] . Type parameters of type lambdas can have bounds, but they cannot carry + or - variance annotations.
Capture clause A lambda can introduce new variables in its body (in C++14), and it can also access, or capture, variables from the surrounding scope. A lambda begins with the capture clause. It specifies which variables are captured, and whether the capture is by value or by reference.
That's because the scope of the vector filters
outlives the one of the function. At function exit, the vector filters
still exists, and the captured reference to divisor
is now dangling.
For what I understand, filters.emplace_back only returns after lambda expression is complete, and, during it execution, divisor is valid.
That's not true. The vector stores the lambda created from the closure, and does not "execute" the lambda, you execute the lambda after the function exits. Technically the lambda is constructed from a closure (an compiler-dependent-named class) that uses a reference internally, like
#include <vector>
#include <functional>
struct _AnonymousClosure
{
int& _divisor; // this is what the lambda captures
bool operator()(int value) { return value % _divisor == 0; }
};
int main()
{
std::vector<std::function<bool(int)>> filters;
// local scope
{
int divisor = 42;
filters.emplace_back(_AnonymousClosure{divisor});
}
// UB here when using filters, as the reference to divisor dangle
}
You are not evaluating the lambda function while addDivisorFilter is active. You are simply adding "the function" to the collection, not knowing when it might be evaluated (possibly long after addDivisorFilter returned).
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