I have a class that takes a std::function as a parameter which I assign a lambda function. It works in the constructor but it stops working after that. The debugger says f is "empty" after running the first line. Why?
#include <iostream>
#include <string>
#include <functional>
typedef std::function<void(std::string)> const& fn;
class TestClass
{
public:
TestClass(fn _f) : f(_f) { F(); }
void F() { f("hello"); };
private:
fn f;
};
int main()
{
TestClass t([](std::string str) {std::cout << str << std::endl; });
t.F();
return 0;
}
Calling t.F() causes a fault. Why?
I can solve it by changing it to the following:
int main()
{
fn __f = [](std::string str) {std::cout << str << std::endl; };
TestClass t(__f);
t.F();
return 0;
}
but again, this does not work when I change fn to auto!
int main()
{
auto __f = [](std::string str) {std::cout << str << std::endl; };
TestClass t(__f);
t.F();
return 0;
}
What is the explanation of why this is happening?
We can declare a lambda function and call it as an anonymous function, without assigning it to a variable. Above, lambda x: x*x defines an anonymous function and call it once by passing arguments in the parenthesis (lambda x: x*x)(5) .
If we need to pass a lambda expression as an argument, the type of parameter receiving the lambda expression argument must be of a functional interface type. In the below example, the lambda expression can be passed in a method which argument's type is "TestInterface".
It is possible to pass a list to a lambda function, but it is not possible to use a list with the ** operator.
Lambda functions are inline functions and thus execute comparatively faster.
Note that (1) fn is defined as reference (to const); (2) lambda and std::function are not the same type; (3) You can't bind reference to object with different type directly.
For the 1st case,
TestClass t([](std::string str) {std::cout << str << std::endl; });
t.F();
A temporary lambda is created and then converted to std::function which is a temporary too. The temporary std::function is bound to the parameter _f of the constructor and bound to member f. The temporary will be destroyed after this statement, then f becomes dangled, when t.F(); it fails.
For the 2nd case,
fn __f = [](std::string str) {std::cout << str << std::endl; };
TestClass t(__f);
t.F();
A temporary lambda is created and then bound to reference (to const). Then its lifetime is extended to the lifetime of the reference __f, so the code is fine.
For the 3rd case,
auto __f = [](std::string str) {std::cout << str << std::endl; };
TestClass t(__f);
t.F();
lambda is created and then converted to std::function which is a temporary. The temporary std::function is bound to the parameter _f of the constructor and bound to member f. The temporary will be destroyed after this statement, then f becomes dangled, when t.F(); it fails.
(1) You could declare fn as non-reference like typedef std::function<void(std::string)> fn;, then std::function will be copied and every case would work well.
(2) Don't use names begin with double underscore, they're reserved in C++.
typedef std::function<void(std::string)> const& fn;
This isn't a std::function, it is a reference to a std::function.
TestClass(fn _f) : f(_f) { F(); }
fn f;
Here you take a const& to a std::function and bind it to another const& to a std::function. The F() in the body of the constructor works, as the reference is valid at least as long as the constructor is.
TestClass t([](std::string str) {std::cout << str << std::endl; });
This creates a std::function temporary created from the lambda. This temporary lasts as long as the current line (until the ;).
Then the temporary std::function is discarded.
As TestClass takes the std::function by const&, it doesn't extend the temporaries lifetime.
So after the line, any call of the std::function const& is undefined behavior, which you see in the call to .F() later.
fn __f = [](std::string str) {std::cout << str << std::endl; };
This does reference lifetime extending. The temporary std::function created from the lambda has its lifetime extended to the lifetime of the __f variable.
As an aside, this line also makes your program ill formed, no diagnostic required, by having a variable containing a double underscore. Such identifiers are reserved for the implementation of the compiler, you may not create them.
TestClass t(__f);
We then pass this reference (referring to a lifetime extended temporary), and everything works.
auto __f = [](std::string str) {std::cout << str << std::endl; };
This creates a variable __f (see above, bad name) that is a lambda.
A lambda is not a std::function. A std::function can be created from a lambda implicitly.
TestClass t(__f);
This creates a temporary std::function from the lambda, passes it to the TestClass constructor, then destroys the temporary.
After this line, the call to .F() ends up following a dangling reference, and undefined behavior results.
Your core problem may be that you think a lambda is a std::function. It is not. A std::function can store a lambda.
Your second problem is typedefing something as a const&, which is almost always a really stupid idea. References behave differently than values in fundamental ways.
Your third problem is the double understore in your variable names. (Or an identifier starting with an _ followed by a capital letter).
If you want to know how std::function works and what it is, there are plenty of good SO posts on the subject with various levels of technical detail.
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