lambda expression always returns a prvalue, hence dangling reference?

Hi,


I found the following in cppreference.com in the documentation for std::function:


Care should be taken when a std::function whose result type is a reference is initialized from a
lambda expression without a trailing-return-type. Due to the way auto deduction works, such
 lambda expression will always return a prvalue. Hence, the resulting reference will usually bind 
to a temporary whose lifetime ends when std::function::operator() returns.


1
2
std::function<const int&()> F([]{ return 42; });
int x = F(); // Undefined behavior: the result of F() is a dangling reference 


I don't find this to be true!

Last edited on
For those who want a link to the quote: https://en.cppreference.com/w/cpp/utility/functional/function

I got a segfault when I ran that inside main.
Last edited on
In what sense do you not "find this to be true"?
U.B. can seem to work, until it doesn't.
BTW, if you reference a webpage, give the link:
https://en.cppreference.com/w/cpp/utility/functional/function

Ganado wrote:
I got a segfault when I ran that inside main

@Ganado, what was the exact code you ran?
And what system are you on?
I have tried both an online compiler and MinGW.

onlinegdb.com with C++17 selected from the right-hand side dropdown menu.
https://www.onlinegdb.com/online_c++_compiler

mingw - g++ (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 8.1.0
g++ -std=c++17 main.cpp -o main


Program is:
1
2
3
4
5
6
7
#include <functional>

int main()
{
    std::function<const int&()> F([]{ return 42; });
    int x = F();
}

Segmentation fault (core dumped)

Crashes in both cases.

PS: I am in no way claiming I understand exactly what's going on here, only that it appears the cppreference article is correct (and even if it didn't crash for me, I would still not bet against cppreference).
Last edited on
@dutch says In what sense do you not "find this to be true"?
Well x becomes 42 after the assignment and I find no dangling reference...

Actually I find that the lambda which is supposed to return a prvalue actually does not:

1
2
auto l = []() { return 42; };
decltype(auto) z = l();                // z is int, not int&& 


No dangling reference here!!

Have you not bothered reading the thread?
The point is that there's "no dangling reference" FOR YOU, AT THIS TIME, WITH THE EXACT SYSTEM AND COMPILER THAT YOU HAPPENED TO USE. If that's all you care about, then fine.

For example, if I compile it with clang++ it runs, but if I compile it with g++ it segfaults.
Do you understand the problem now???
on the other hand:


 
decltype(auto) y = F();            // y is const int& 


so if my reasoning is correct, y refers to a const int returned by F() which is created in that call and is destroyed at the end of that line?? Is this the dangling reference?

I am a bit lost here but am I correct?
I understand @dutch.... you are saying that there is a dangling reference that my system and compiler is not "getting", not producing any problem but the dangling is there anyway and I cannot trust this code as it is, because any compiler enhancement could very well show the ugly face of the dangling reference?



Yeah, that's the idea. The important point is that if a trusted source says something is U.B. then you cannot really disprove it with any particular system / configuration.

Even for g++ it will not segfault if you turn on optimization.
In the online compiler Ganado links to, you can do that in the tool menu (upper right) and add "extra compiler flags" (add -O).
z is int, not int&&
int means you've got a prvalue, while int&& means you've got an xvalue. Note that l() isn't an id-expression.
https://en.cppreference.com/w/cpp/language/decltype

The problem is that std::function's operator() essentially calls the stored function and returns the result:
1
2
3
4
const int& std::function<const int&()>::operator()()
{
  return stored_callable_object_();
}

If stored_callable_object_() is a prvalue, trouble ensues because the return value is bound to a temporary.
Last edited on
I still don't understand. Doesn't the fact that it's a const int& expand the lifetime of the temporary?
e.g. doing const int& foo = 42;
Clearly I am mistaken, but I don't understand why.
Last edited on
> Doesn't the fact that it's a const int& expand the lifetime of the temporary?

See:
Lifetime of a temporary
Whenever a reference is bound to a temporary or to a subobject thereof, the lifetime of the temporary is extended to match the lifetime of the reference, with the following exceptions:

a temporary bound to a return value of a function in a return statement is not extended: it is destroyed immediately at the end of the return expression. Such function always returns a dangling reference.
...
In general, the lifetime of a temporary cannot be further extended by "passing it on": a second reference, initialized from the reference to which the temporary was bound, does not affect its lifetime.
https://en.cppreference.com/w/cpp/language/reference_initialization#Lifetime_of_a_temporary
Doesn't the fact that it's a const int& expand the lifetime of the temporary?
No, because this... is an exception to the rule.
The lifetime of a temporary bound to the returned value in a function return statement ([stmt.return]) is not extended; the temporary is destroyed at the end of the full-expression in the return statement.

https://timsong-cpp.github.io/cppwp/class.temporary#6.11
https://en.cppreference.com/w/cpp/language/reference_initialization#Lifetime_of_a_temporary

1
2
3
4
int const& f() 
{
  return 42;
}

My (technically wrong) mental model is that return 42 initializes a int const&, which extends the lifetime of the temporary until the (imminent) end of the function. Maybe that's helpful/easier to remember.
Last edited on
Thanks you two, didn't know it was explicitly stated as an exception to the rule.
Last edited on
Topic archived. No new replies allowed.