I'm convinced that all three functions have undefined behavior.
To people who insist that f3 is not UB (or even f1/f2): shall you please try to run this code:
#include <iostream>
const char* const& f1() { return "hello1"; }
const char* const& f2() { return static_cast<const char*>("hello2"); }
const char* const& f3() { const char* const& r = "hello3"; return r; }
int main()
{
using namespace std;
//#define F f1
//#define F f2
#define F f3
const char* const& ret = F();
cerr << ret;
cerr << ",";
cerr << ret;
return 0;
}
(I used cerr rather than cout to get immediate flushing. You can change cerr to cout and add a cout << flush; after the second output of ret.)
On my GCC here's what I got printed:
- with
f1: hello1,8??q? (some random chars after the comma)
- with
f2: hello2,8j?y5 (some random chars after the comma)
- with
f3: hello3,, (a second comma after the comma)
That looks very much like UB to me...
(Note: If I remove either const& then it "works". The const& to really remove being the one in the return type of course.)
I think that's because what happens in f3 is something like this:
const char* const& f3()
{
const char* __tmp001 = &("hello3"[0]); // "array decaying"
const char* const& r = __tmp001;
return r;
}
Indeed the string literal "hello3" is not a const char*, it's a (static) const char [7]. In the code const char* const& r = "hello3";, the reference can't be bound to this char array directly because it has not the same type, so the compiler has to create a temporary char pointer (created on the stack) initialized by implicit conversion (array-to-pointer decaying) to which the reference is bound (demo). The lifetime of this temporary const char* is "extended" to the lifetime of the reference r, thus doesn't end at the first semicolon, but ends when the function returns (demo and output with all optimizations off). So f3 returns a "dangling reference". In my test output code, any subsequent operation which overwrites the stack makes the UB visible.
Edit after jalf's comment: I'm conscious that "it prints garbage on the second output" is not a proof of UB. A program with UB can as well work exactly as expected, or crash, or do nothing, or whatever. But, nonetheless, I don't believe that a well-defined program (with no UB) would print garbage like that...