Ron said:
How is this any more efficent or less convoluted than storing the method
pointer and a constant to add to the "this" pointer?
If Base::f() is virtual there's no method pointer you can store, because if
Derived overloads f() and x is a Derived, (x.*&Base::f)() calls
Derived::f(). (This is slightly odd given that x.Base::f() calls Base::f().
If they'd given that semantics to member pointers, none of this complexity
would exist.) So we need to encode a second case for virtual functions in
there somehow, and test it at each call site where the call might be virtual
(i.e. where Base is an incomplete type or contains some virtual method
compatible with the method pointer's type).
If f() is non-virtual, but implemented in a virtual base class of Base, then
we have a method pointer but no offset. This would need another case, except
that the standard doesn't require implementations to handle it. (It says
that &Base::f is a BasicBase::*, which isn't compatible with Base::*.)
What if f() is virtual and implemented in a virtual base class? On most
implementations, this is simpler than the previous case. We can handle it
like any other virtual function, because the compiler generated a
pointer-adjusting thunk to put into the vtable -- and it did that so the
vtable could be a vector of function pointers instead of a vector of
pointer-plus-this-adjustment-with-special-case-for-virtual-base thingies.
Vtable entries are method pointers, and they're always, to my knowledge,
implemented in just the way I'm suggesting that surface-language method
pointers should be. Almost all of the necessary code is already in the compiler.
This technique is certainly faster for the trivial case, and almost
certainly faster for the general non-virtual case, since the pointers are
half the size and each call requires an indirect jump and an unconditional
direct jump instead of an indirect jump and a conditional jump (with
potential misprediction). I see two problems with it. One is that it's
almost certainly slower for virtual methods (two indirect jumps), but I
think pointers to virtual methods are much rarer than pointers to
non-virtual methods in the wild. The other is that you can't implement
semantics-preserving casts from Base::* to Derived::* (or vice versa) in
nontrivial cases without horrible convolutions. The only sensible way I can
see to do it is to turn the cast into
switch (p) {
case &Base::f: return &Derived::f;
case &Base::g: return &Derived::g;
// ...
}
which is only workable if you have some way of guaranteeing that you haven't
generated duplicate thunks for the same method. This isn't a fatal problem
since the standard doesn't require such casts to work (unless you cast the
pointer back before using it). It's akin to casting from void (*)(Base*) to
void (*)(Derived*), which would be even harder to implement.
-- Ben