Search code examples
c++templatesreturn-by-reference

Why does a local reference to a constant temporary value seem to outlive the method it's defined in?


I am quite puzzled to see why the following piece of code successfully compiles and seems to run correctly by displaying 7 twice. I suspect there is some undefined behavior lurking in both invocations.

#include <iostream>

template <typename T>
const T& max(const T& x, const T& y)
{
    return (x > y) ? x : y;
}

int main()
{
    const int &var1 = max(3, 7);
    std::cout << var1 << '\n';

    int var2 = max(3, 7);
    std::cout << var2 << '\n';

    return 0;
}

Two local reference variables are referring to temporary values 3, 7, respectively, in this example. Then the method returns a reference to the maximum value, which is 7. But, when the method returns, the reference is no longer valid. It is dangling! So, even though var1 seems to refer to a value of 7, isn't it considered undefined with no guarantee that 7 will be there moments later?

Similarly, even though var2 is just supposed to get a copy of what comes out of max(), the return statement of that method returns a dangling reference! So, I think even var2 is unreliable because it grabs a copy from a garbage location.

So, should I consider that both cout statements display garbage (although 7 is displayed twice)?


Solution

  • In your example, using var1 exhibits undefined behavior, as var1 is a dangling reference. Using var2 is fine.

    "The max() method creates two local reference variables to store temporary values 3, 7"

    No, that's not what happens. The caller of max() creates two temporaries, and passes references to them to max(). max itself has no idea whether its arguments are references to temporaries, or to honest long-lived objects. So your examples are roughly equivalent to this:

    const int* p;
    {
      int temp1 = 3;
      int temp2 = 7;
      p = &max(temp1, temp2);  // p points to temp2
    }
    const int& var1 = *p;  // p is dangling by now; temp2 has been destroyed
    

    (Using an intermediate pointer here only because there's no syntax to delay binding a reference).


    int var2;
    {
      int temp1 = 3;
      int temp2 = 7;
      var2 = max(temp1, temp2);  // var2 = 7
    }
    // temp2 is gone, but var2 safely stores a copy of it.