Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does returning const reference from a lambda result in a temporary?

I have a situation where I have a member returning a const&, and then this result is being forwarded within a lambda, which has the same return type.

MSVC2017 identifies this situation as risky, and issues a warning: returning address of local variable or temporary. Empirical testing with clang and other compilers shows this is true across the board. What I do not understand, is why this is different than several method calls which all return the same type.

For example, this works perfectly:

class A {
public:
    const std::string& name() const { return m_name; }
private:
    std::string m_name;
};

class B {
public:
    const std::string& name() const { return m_a.name(); }
private:
    A m_a;
};

//...
B b;
std::cout << b.name();

Works as expected, no warnings/errors at compile or runtime.

But with a lambda, it doesn't:

class A {
public:
    const std::string& name() const { return m_name; }
private:
    std::string m_name;
};

//...
using Getter = std::function< const std::string&() >;
A a;
Getter g = [&a] { return a.name(); };
std::cout << g();

results in a crash, or at least printing corrupted memory

Can someone point me to some info about why this does not work? I would generally expect it to work the same...

like image 471
yano Avatar asked May 04 '18 19:05

yano


1 Answers

The return type of your lambda is not a reference. This is the cause of all your problems.

Your lambda returns a copy of the name. You are storing this lambda in a std::function returning a const std::string&, which means that effectively, you will return a reference to that copy which will get destroyed as soon as std::function's call operator returns!1

Naturally, the fix is to change the return type of the lambda:

Getter g = [&a]() -> const std::string& { return a.name(); };

// or
Getter g = [&a]() -> auto& { return a.name(); };
// or if you are feeling fancy :P
Getter g = [&a]() -> decltype(auto) { return a.name(); };

1: To expand a bit on this, you can imagine std::function's implementation as something like this (only the relevant parts are shown and massively simplified):

template<typename R, typename... Ts>
struct function<R(Ts...)> {
  R operator()(Ts... Args) const {
    return callInternalFunctionObject(Args...); // here: copy will get destructed
  }
};
// R=const std::string&, and Ts is empty
like image 56
Rakete1111 Avatar answered Oct 14 '22 11:10

Rakete1111