0

Let's consider the following program:

#include <iostream>
#include <tuple>
using namespace std;

const int* f()
{
    static int i = 5;
    return &i;
}

int main()
{
    auto [a] = std::forward_as_tuple( f() );
    //auto&& [a] = std::forward_as_tuple( f() );           // same error
    //const auto& [a] = std::forward_as_tuple( f() );      // same error
    cout << *a << endl;
    return 0;
}

I expect it to print 5. I tried it on gcc 13.1 and MSVC 19.35, and it produces correct result when all optimizations are turned off. Anyway, when I add -O1 for gcc or /O2 for MSVC, the program starts crashing in runtime, and I can hardly understand why this happens. I found the following Note on cppreference:

If the arguments are temporaries, forward_as_tuple does not extend their lifetime; they have to be used before the end of the full expression.

Is it the explanation? If so, can someone please elaborate what language rules am I violating in my code?

soad
  • 49
  • 6
  • 1
    *Is it the explanation?* Yes. The rule is only function local references extend the lifetime of a temporary. You can't pass a temporary through a function. – NathanOliver May 25 '23 at 23:46
  • Doesn't the note say the opposite? It says that you can pass a temporary through a function but only for usage in the same full expression. – soad May 25 '23 at 23:51
  • 3
    I meant to say you can't pass a temporary through a function and extend its lifetime afterwards. – NathanOliver May 25 '23 at 23:53
  • `const int& a = std::move( 5 );` is illegal as well? it seems to work. UPD: works only in MSVC – soad May 26 '23 at 00:02
  • 1
    That's the "fun" thing about undefined behavior, it can just "work". – NathanOliver May 26 '23 at 00:07
  • 1
    If you are **lucky**, undefined behavior will cause a noticeable fault. But if you are **unlucky**, undefined behavior will seem to work as you expect. You won't even notice the undefined behavior is there, but it's a trap waiting to be sprung. How to combat undefined behavior is by enabling your compiler warnings and addressing those warnings, and using sanitizers that will fault for some kinds of undefined behavior (alas, not for all undefined behavior). – Eljay May 26 '23 at 12:37

1 Answers1

1

Following the trail that @NathanOliver had laid I managed to find an answer at cppreference:

a temporary bound to a reference parameter in a function call exists until the end of the full expression containing that function call: if the function returns a reference, which outlives the full expression, it becomes a dangling reference.

and

In general, the lifetime of a temporary cannot be further extended by "passing it on": a second reference, initialized from the reference variable or data member to which the temporary was bound, does not affect its lifetime.

soad
  • 49
  • 6