Well, from the documentation found around, I guess that this isn't a much used feature of C++. Actually, C's pointers to function was almost 1337 feature, and thus the same must be in C++. Actually, in C++ it's even more complicated, because we have... Classes! Thus we need to reference a member of a class. And this class may have children! How about them? How are them handled?
Well, I didn't find an answer to all these questions, so I wrote my code and tried to see what's happening.
The first thing to remember is: when dealing with classes, you're dealing with offsets. In fact, to reference a member of a class, you don't dereference the pointer, but use the scope resolution operator. First, this is the test class:
struct Test {
int a, b;
Test(): a(0), b(0) {}
void fun(int x) { b = x; }
void gun(int x) { a = y; }
};Now take a look on how you can reference the a property:
Test o;
int Test::*p = &Test::a;
std::cout << o.*p << endl;
See? first: this is a pointer to a member of Test, thus we don't write int *Test::p (because Test::p is just nothing). And to get the address, we do use the scope resolution operator: &Test::a.
This is quite simple: resolve the class Test and get the offset of the a property. Yeah. But, offset respect to *what*??
Actually, to reference a real, instanced, integer, we need an instance of Test: Test o. Then, we need to lookup in *that* object for the given offset. We need to refer the object and dereference the pointer. We use the .* (and ->*) operator(s).
Actually, if we're dealing with properties (i.e. not methods), we can also refer directly the object and it's field:
int *p = &o.a;
I don't know if this is allowed in C++ standard, but gcc4 compile it with no warnings.
Similar is with methods: we *need* to refer to the class' offset and then apply it to an object:
void (Test::*f)(int) = &Test::fun;
(o.*f)(123); // Refer and dereference
// void (*f)(int) = &o.fun; // Not allowed.
Does it work something mixed?
int Test::*p = &o.var;
No, because o.var doesn't refer to object's offset, while Test::*p does.
Now, let's try with a child object:
struct Child: public Test {
int c;
void fun(int x) {}
};
Child s;
int Test::*p = &Child::a; // This is ok
// int Test::*f = &Child::c; // !!! Illegal !!!
void (Test::*g)(int) = &Child::gun; // Also this is ok
// void (Test::*h)(int) = &Child::fun; // !!! Illegal !!! (X)
void (Test::*h)(int) = &Test::fun; // Ok, of courseThe first is ok, because we're referencing the a property, which is inherited from Test, thus the Child::a is a valid object when downcasting to Test::a. The second is not valid! Because c is not present in Test, but only in Child. The third is ok, because gun() is inherited from Test. The next one (marked with X), however, doesn't work: it's not allowed a conversion from Child::* to Test::*. Thus, we need to use &Test::fun, but we can of course call it using child's pointer:
(s.*h)(123);
It's also interesting to see how virtual doesn't changes the X's behavior. If you set fun() as virtual in Test, the fourth example above won't work. I'm not sure why this happens, but I guess it's something related to pointers and vtables: when you don't use a pointer, referencing the object and the method, C++ will lookup the vtable (if present, of course) to find the method to call. When dealing with pointers, we know the offset of the function to call. So it just perform type checking on pointers, but doesn't lookup for vtables.
Well, these are just ideas. Remember that they may be *totally* wrong. I'm writing these things at 2:32 (not 14:32 :P), so my mind maybe obfuscated :D I'll take a look to C++ standard, hoping to remember to patch this log. Meanwhile, if some C++ guru wants to explain/correct me, he's welcome.
Stay --sync