简体   繁体   中英

std::functions and lambda function passing

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 tF() 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?

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 tF(); 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 tF(); 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM