> I don't think you can define what "f->spam" means
Well, you can, but only the first half of what it means, so I don't think this changes your point.
If f is a pointer, then f->spam means to dereference the pointer f, then access the spam attribute of the result. If f is an instance of a user-defined class, rather than a raw pointer, it can redefine ->. But only to change the "dereference" part, not the "access the spam attribute" part. In other words, f->spam means something like (*(f.operator->())).spam and you can overload the operator->.
Of course, under very restricted conditions, you can do something like this:
#include <iostream>
struct pstatus;
struct status { int eggs, spam; }
struct pstatus {
status *p;
status* operator->() {
return reinterpret_cast<status*>(reinterpret_cast<char*>(p) - sizeof(int));
}
};
pstatus frob() {
return pstatus{new status{1, 2}};
}
int main() {
auto f = frob(); // eggs = 1, spam = 2
std::cout << f->spam; // accesses eggs, and prints 1 rather than 2
return 0;
}
Compile that with g++ or clang++ with -std=c++11 (we don't actually need any C++11 feature here, the code's just a bit shorter and simpler with auto, etc.), and it should compile without warnings, and print out 1 rather than 2 when you run it.
The trick is that "access the spam attribute" actually means "access bytes 4-8 of the struct as an int", so if we have a pointer to 4 bytes before the start of the real struct, you get the int at bytes 0-4 of the real struct, which is the real eggs.
(Actually, we should be using the difference between offsetof(spam) and offsetof(eggs), not sizeof(int), so it would be legal even with non-default alignment. But so many tiny things could turn this code into undefined behavior, even with that change, like just adding a virtual method to status, or referencing f->eggs in code that doesn't even run… so let's not worry about bulletproofing it.)