Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 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?

like image 375
Mochan Avatar asked Aug 08 '16 14:08

Mochan


People also ask

How do you pass a function to lambda?

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) .

How do you pass lambda as an argument?

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".

Can we pass list to lambda function?

It is possible to pass a list to a lambda function, but it is not possible to use a list with the ** operator.

Which is faster regular function and lambda function?

Lambda functions are inline functions and thus execute comparatively faster.


2 Answers

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++.

like image 137
songyuanyao Avatar answered Sep 17 '22 22:09

songyuanyao


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.

like image 37
Yakk - Adam Nevraumont Avatar answered Sep 21 '22 22:09

Yakk - Adam Nevraumont