1

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

softwarelover
  • 1,009
  • 1
  • 10
  • 22
  • Does this answer your question? [Lifetime of temporary passed through function by reference](https://stackoverflow.com/questions/30519148/lifetime-of-temporary-passed-through-function-by-reference) – jtbandes Jul 04 '20 at 22:05
  • If you change the order a bit, it breaks, as you suspect: https://onlinegdb.com/Hk3Ypd0AU –  Jul 04 '20 at 22:07
  • 1
    For me, it breaks [as is](https://wandbox.org/permlink/s8dujQtY5pfbX2ta) – Paul Sanders Jul 04 '20 at 22:08
  • @jtbandes, not really. I have two scenarios. I cannot readily take the answer from the link you suggested. – softwarelover Jul 04 '20 at 22:15
  • 1
    `var1` is a dangling reference - a reference to an object whose lifetime has ended. Using it exhibits undefined behavior. "Seems to work" is one possible manifestation of undefined behavior. – Igor Tandetnik Jul 04 '20 at 22:16
  • 2
    It's not `max` that creates two temporaries - the **caller** of `max` does. Those temporaries are destroyed at the end of the full expression - basically, at the semicolon that follows `max` call. By that time, one of them is already happily copied into `var2`. So the second `max` call is fine. – Igor Tandetnik Jul 04 '20 at 22:18
  • @Igor. Aha! So there is this subtle difference when the receiving variable is a non-reference variable wishing for a copy only. That's var2. Whereas, var1 is a dangling reference! Got it. (Oh, by the way, I didn't say max creates temporaries. Max creates local references to the temporaries passed by the caller.) – softwarelover Jul 04 '20 at 22:22
  • 1
    `max` receives references as arguments. It has no idea whether they refer to temporaries, or to "normal" named objects, nor how long those objects are going to exist past the function's return. – Igor Tandetnik Jul 04 '20 at 22:31

1 Answers1

2

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.
Igor Tandetnik
  • 50,461
  • 4
  • 56
  • 85
  • Igor, I understand your point you made about arguments being references to temporaries or long-lived objects...But, could you kindly remove that notation that a pointer being assigned a reference variable? That will confuse people (although you put a comment there.) – softwarelover Jul 04 '20 at 22:50
  • I'd need some way to declare `var1` before the braces, but to bind it inside. There's no way to do that in actual C++ syntax, so I'd either have to make one up, or hand-wave around it. `p` is my way of hand-waving. I'm not sure how to express it better. – Igor Tandetnik Jul 04 '20 at 23:19