Reputation: 1369
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?
Upvotes: 5
Views: 5903
Reputation: 172884
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++.
Upvotes: 6
Reputation: 275270
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.
Upvotes: 3