Why is this recursive lambda function unsafe?

2020-08-23 04:37发布

问题:

This question comes from Can lambda functions be recursive? . The accepted answer says the recursive lambda function shown below works.

std::function<int (int)> factorial = [&] (int i) 
{ 
    return (i == 1) ? 1 : i * factorial(i - 1); 
};

However, it is pointed out by a comment that

such a function cannot be returned safely

, and the reason is supplied in this comment:

returning it destroys the local variable, and the function has a reference to that local variable.

I don't understand the reason. As far as I know, capturing variables is equivalent to retaining them as data members (by-value or by-reference according to the capture list). So what is "local variable" in this context? Also, the code below compiles and works correctly even with -Wall -Wextra -std=c++11 option on g++ 7.4.0.

#include <iostream>
#include <functional>

int main() {

    std::function<int (int)> factorial = [&factorial] (int i)
    {
        return (i == 1) ? 1 : i * factorial(i - 1);
    };

    std::cout << factorial(5) << "\n";

}

Why is the function unsafe? Is this problem limited to this function, or lambda expression as a whole?

回答1:

This is because in order to be recursive, it uses type erasure and captures the type erased container by reference.

This has the effect of allowing to use the lambda inside itself, by refering to it indirectly using the std::function.

However, for it to work, it must capture the std::function by reference, and that object has automatic storage duration.

Your lambda contains a reference to a local std::function. Even if you return the std::function by copy, the lambda will still refer to the old one, that died.

To make a secure to return recursive lambda, you can send the lambda to itself in an auto parameter and wrap that in another lambda:

auto factorial = [](auto self, int i) -> int { 
    return (i == 1) ? 1 : i * self(self, i - 1); 
};

return [factorial](int i) { return factorial(factorial, i); };


标签: c++