I stumbled upon something weird. We all know the taking the address of the return value from a function-call is not allowed - since it's an r-value.
But this seems to be fine:
#include <iostream>
struct Foo {
int val;
Foo* operator &() { // <-- just like normal "&"
return this;
}
};
Foo test() {
return Foo{3};
}
int main() {
Foo* p = &test(); // <-- wtf? allowed and works
printf("%d", p->val);
}
Maybe by overloading the "&"-operator, the return value of first brought into scope of the caller (living on the stack, I guess) and therefore addressable.
Yields deterministic results on (Clang-14 and GCC-12)
Question is: Undefined Behaviour or Legal?
Assembler output clearly shows, that the return value is cached.
[Clang 14.0.0 without arguments]
main: # @main
push rbp
mov rbp, rsp
sub rsp, 16
call test()
mov dword ptr [rbp - 16], eax <---
lea rdi, [rbp - 16] <---
call Foo::operator&()
mov qword ptr [rbp - 8], rax
mov rax, qword ptr [rbp - 8]
mov esi, dword ptr [rax]
movabs rdi, offset .L.str
mov al, 0
call printf
xor eax, eax
add rsp, 16
pop rbp
ret
CodePudding user response:
There's two separate questions here. First, will it compile? Second, will it work as intended?
The code in question compiles because
Foo* p = &test();
translates into
Foo* p = test().operator&();
which is perfectly legal. For example, compare against something like this:
std::string makeAString() {
return "whee!";
}
auto length = makeAString().length(); // Perfectly fine
This code is legal, just as yours is.
However, there's a separate question about whether it will work correctly. And as mentioned in the comments, no it won't, because the pointer you're storing in p
points to an object whose lifetime has ended. Using that pointer results in undefined behavior.
So, in that sense, your code will compile, but it's not a good idea to run it. :-)
CodePudding user response:
The behavior of your program is undefined.
User-defined operators have different semantics than the built-in operators. User-defined operators behave just like the function calls they actually are. Since your overloaded operator&
is not ref-qualified, there's no reason it can't be called on an rvalue.
This doesn't change the lifetime of the object though. There is no pointer-lifetime-extension. Foo* p = &test()
still yields a dangling pointer, since the lifetime of the temporary Foo
object returned by test
ends at the end of the full expression. Attempting to dereference p
beyond that point results in undefined behavior. Remember, undefined behavior really means undefined. Anything can happen, and "appears to work" is included in "anything".