According to what I know, the result value of an overloaded operator->()
can be any type T
provided that T
is either a pointer type or it is a class/struct which also exposes an overloaded operator->()
. In effect the compiler has to potentially chain together multiple invocations of those overloaded member access operators, until a result value of a pointer type is obtained. What I'm interested in is whether the standard guarantees anything in what exact way this "chaining" should look like from the syntactic point of view. Consider the following example:
struct A {
int data;
A* operator->() { return this; }
};
struct B {
A operator->() { return A(); }
};
struct C {
B operator->() { return B(); }
};
int main() {
C()->data = 1;
}
The part of particular interest here is the overloaded A* A::operator->()
which returns a pointer to itself (return this
). My first question is whether that's actually legal to do from the language standpoint. I'd imagine that it is, since it matches the requirement of an overloaded operator->()
returning a pointer type. The only problem I see here is that there may be some conditions under which the returned A*
pointer may become dangling. And therein lies the second question - whether the process of chaining multiple operator->()
together by the compiler behind the scenes can result in that pointer becoming invalid. That could happen for instance if the expression C()->data = 1;
would get "unrolled" into the following syntactically similar construct:
A* _internal_compiler_var = ((C().operator->()).operator->()).operator->(); // temporary A is constructed and immediately destroyed after obtaining the pointer value
_internal_compiler_var->data = 1; // Oops : dangling A* pointer
By contrast, the expression C()->data = 1;
would be valid provided that compiler "unrolls" it into a one-liner like the following:
(((C().operator->()).operator->()).operator->())->data = 1; // OK : destruction of temporary A instance is performed after the assignment
So, to sum up what I'd like to know 2 things:
- whether an overloaded
operator->()
returningthis
pointer is valid, and if it is, if there are any potential problems that could come up because of this - whether the standard has some implicit/explicit guarantees about (temporary) object lifetimes used inside the chain of overloaded
operator->()
(particulary if these temporary objects are destroyed after the enclosing expression or are destroyed somewhere in the middle while the resulting pointer value is being evaluated)
According to what I know, the result value of an overloaded operator->()
can be any type T
provided that T
is either a pointer type or it is a class/struct which also exposes an overloaded operator->()
. In effect the compiler has to potentially chain together multiple invocations of those overloaded member access operators, until a result value of a pointer type is obtained. What I'm interested in is whether the standard guarantees anything in what exact way this "chaining" should look like from the syntactic point of view. Consider the following example:
struct A {
int data;
A* operator->() { return this; }
};
struct B {
A operator->() { return A(); }
};
struct C {
B operator->() { return B(); }
};
int main() {
C()->data = 1;
}
The part of particular interest here is the overloaded A* A::operator->()
which returns a pointer to itself (return this
). My first question is whether that's actually legal to do from the language standpoint. I'd imagine that it is, since it matches the requirement of an overloaded operator->()
returning a pointer type. The only problem I see here is that there may be some conditions under which the returned A*
pointer may become dangling. And therein lies the second question - whether the process of chaining multiple operator->()
together by the compiler behind the scenes can result in that pointer becoming invalid. That could happen for instance if the expression C()->data = 1;
would get "unrolled" into the following syntactically similar construct:
A* _internal_compiler_var = ((C().operator->()).operator->()).operator->(); // temporary A is constructed and immediately destroyed after obtaining the pointer value
_internal_compiler_var->data = 1; // Oops : dangling A* pointer
By contrast, the expression C()->data = 1;
would be valid provided that compiler "unrolls" it into a one-liner like the following:
(((C().operator->()).operator->()).operator->())->data = 1; // OK : destruction of temporary A instance is performed after the assignment
So, to sum up what I'd like to know 2 things:
- whether an overloaded
operator->()
returningthis
pointer is valid, and if it is, if there are any potential problems that could come up because of this - whether the standard has some implicit/explicit guarantees about (temporary) object lifetimes used inside the chain of overloaded
operator->()
(particulary if these temporary objects are destroyed after the enclosing expression or are destroyed somewhere in the middle while the resulting pointer value is being evaluated)
1 Answer
Reset to default -1Here is a main
using the structures from the OP with live code
auto main() -> int {
C()->data = 42; // temporary C destroyed
println("{}", C()->data); // output 0, another C temporary destroyed
A* _internal_compiler_var = ((C().operator->()).operator->()).operator->();
_internal_compiler_var->data = 1; // undefined behavior
println("{}", _internal_compiler_var->data); // output 1
(((C().operator->()).operator->()).operator->())->data =
84; // temporary C destroyed
println("{}", (((C().operator->()).operator->()).operator->())->data); // output 0
return 0;
}
The undefined behaviors occur because C++ will retain the pointer to the temporary, but what it points to is destroyed.
;
of the expression they were created in. – NathanOliver Commented 2 days ago