2

Is it legal to cast a pointer to a method of derived class to a pointer to a method of base class, even though the base class does not declare any methods, especially of the "casted" method is called through an object of type base class, as follows:

// works in VS 2008 and g++ 4.5.3
struct Base
{
};

struct Fuu : public Base
{
    void bar(){ std::cout << "Fuu::bar" << std::endl; }
    void bax(){ std::cout << "Fuu::bax" << std::endl; }
};

struct Foo : public Base
{
    void bar(){ std::cout << "Foo::bar" << std::endl; }
    void bax(){ std::cout << "Foo::bax" << std::endl; }
};

typedef void (Base::*PtrToMethod)();

int main()
{
    PtrToMethod ptr1 = (PtrToMethod) &Foo::bax;
    PtrToMethod ptr2 = (PtrToMethod) &Fuu::bax;

    Base *f1 = new Foo;
    Base *f2 = new Fuu;

    (f1->*ptr1)();
    (f2->*ptr2)();
}
Olumide
  • 5,397
  • 10
  • 55
  • 104
  • 1
    No. For "plain" pointers the rule is known as "covariance": you can convert a `Derived*` to `Base*` (because a `Derived` is-a `Base`) but not the other way around (because a `Base*` doesn't necessarily point to a `Derived`). For _pointers-to-member_ the rule is known as "contravariance", and is basically _the opposite_: you can convert a `T Base::*` to `T Derived::*` (because a member of `Base` is also present in a `Derived` object) but not the other way around (because `Derived` can add members not in `Base`). Also _please_ don't use C-style casts: they're unsafe, unclear, and hard to spot. – gx_ Jul 19 '13 at 13:47
  • Fine print: Your example, though it compiles and "works", is probably not legal. Either cast the `Base*` `f1` to `Foo*` (`static_cast` if you're sure, `dynamic_cast` if you're not [but it requires `Base` to have at least one `virtual` function]), or add virtual functions to the public interface of `Base`. – gx_ Jul 19 '13 at 14:17

3 Answers3

5

It's worth noting that the reason the target object is contravariant is because this is effectively a parameter being passed to the function, and parameters are, in theory, contravariant (if the function can use a Base*, it can be safely plugged into any algorithm which provides only Derived* as the actual arguments).

However, for arbitrary parameters, a shim may be needed to adjust the pointer, if the base subobject is not placed at the beginning of the derived class layout. With pointer-to-members, pointer adjustment for the this pointer is built into the language. (And for this reason, pointer-to-member-of-class-with-virtual-inheritance can get quite large)

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • I always use Tony Soprano's aphorism: money goes up, shit runs downhill. In this case: downcasting parameters is trouble, upcasting is natural. – TemplateRex Jul 19 '13 at 14:15
  • I understand the "implicit `this` parameter" reasoning for member functions ("methods", as asked by the question), but for completeness, could it be extended to member _variables_? – gx_ Jul 19 '13 at 14:23
  • 2
    @gx_: From a theoretical perspective, a pointer-to-member-variable *is* a function, mapping a strongly typed object address to a strongly typed subobject address. In the presence of virtual inheritance, these functions qualify as methods because they are dynamically dispatched. In most implementations, an actual function call is never used, the subobject-access code is inserted directly at the point of use, but this is no different from inlining any other function. – Ben Voigt Jul 19 '13 at 14:27
2

No. This is described in section 4.11 of the standard (I have n3337.pdf draft):

A prvalue of type “pointer to member of B of type cv T”, where B is a class type, can be converted to a prvalue of type “pointer to member of D of type cv T”, where D is a derived class (Clause 10) of B. If B is an inaccessible (Clause 11), ambiguous (10.2), or virtual (10.1) base class of D, or a base class of a virtual base class of D, a program that necessitates this conversion is ill-formed. The result of the conversion refers to the same member as the pointer to member before the conversion took place, but it refers to the base class member as if it were a member of the derived class. The result refers to the member in D’s instance of B. Since the result has type “pointer to member of D of type cv T”, it can be dereferenced with a D object. The result is the same as if the pointer to member of B were dereferenced with the B subobject of D. The null member pointer value is converted to the null member pointer value of the destination type.57

In general pointer to member conversions work in the opposite direction then pointers to derived/base classes. Pointers to (sub)objects can be converted towards the base class and pointer to methods can be converted towards more derived classes.

Please note that your program is ill formed if you attempt to call through any of the above pointers when the object involved is not of type Foo/Fuu or their derivative.

While I believe your code takes the risk it looks like similar casting (even without inheritance involved) was heavily used in OWL 2.0 library from Borland at some point.

Tomek
  • 4,554
  • 1
  • 19
  • 19
0

It is legal to cast the pointer. In order to use it, you must cast it back to its original type. The underlying problem is that the pointer-to-function points to a member of the derived class; there is no guarantee that that member is a member of the base class.

Pete Becker
  • 74,985
  • 8
  • 76
  • 165