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