It’s by design. In a nutshell, only the named reference to which the temporary is bound directly will extend its lifetime.
[class.temporary]
5 There are three contexts in which temporaries are destroyed at a
different point than the end of the full-expression. […]6 The third context is when a reference is bound to a temporary.
The temporary to which the reference is bound or the temporary that is
the complete object of a subobject to which the reference is bound
persists for the lifetime of the reference except:
- A temporary object bound to a reference parameter in a function call persists until the completion of the full-expression containing
the call.- The lifetime of a temporary bound to the returned value in a function return statement is not extended; the temporary is destroyed
at the end of the full-expression in the return statement.- […]
You didn’t bind directly to ref2, and you even pass it via a return statement. The standard explicitly says it won’t extend the lifetime. In part to make certain optimizations possible. But ultimately, because keeping track of which temporary should be extended when a reference is passed in and out of functions is intractable in general.
Since compilers may optimize aggressively on the assumption that your program exhibits no undefined behavior, you see a possible manifestation of that. Accessing a value outside its lifetime is undefined, this is what return ref2; does, and since the behavior is undefined, simply returning zero is a valid behavior to exhibit. No contract is broken by the compiler.