Path: utzoo!utgpu!watserv1!watmath!att!dptg!ulysses!andante!alice!shopiro From: shopiro@alice.UUCP (Jonathan Shopiro) Newsgroups: comp.lang.c++ Subject: Re: contravariance confusion Summary: contravariance explained Message-ID: <10359@alice.UUCP> Date: 20 Jan 90 03:46:27 GMT References: <5063@hydra.gatech.EDU> Organization: AT&T Bell Laboratories, Murray Hill NJ Lines: 103 In article <5063@hydra.gatech.EDU>, patty@chmsr (Patty Jones) writes: > > What does the following 1.36.1 g++ compiler warning mean?? > > warning: contravariance violation for method types ignored > (In AT&T 2.0, this is an error. Read on to see why.) In C++ you can say that roughly anything you can do to a base class object, you can do to an object of a derived class. In particular, it is generally okay for a member function of a base class to be called for a derived class object. Thus when a function has an argument which is a pointer to a base class, you can pass the address of a derived class object to it, since in the function base class things will be done and that's okay for a derived class object. It is natural (but wrong) to assume that since it's okay to pass the address of a derived class object to a function that expects the address of a base class object, then it must be okay to pass the address of a derived class member function to a function that expects the address of a base class member function. This problem arises in situations like the following. class Base { public: void memf(); }; typedef void Base::MEMF(); // MEMF is the type of Base::memf void apply(Base* bp, MEMF* mfp) { (bp->*mfp)(); } ... Base b; MEMF* p = &Base::memf; apply(&b, p); // invoke b.memf() the hard way So far this is fine. Next define a derived class and try to use apply with it. class Derived : public Base { public: void memf(); }; ... Derived d; apply(&d, &Derived::memf); // ERROR (or warning) The problem with this innocent-looking code is that a member function of a derived class is used where a member function of a base class is expected. The reason this is not okay is that the member function may then be applied to a base object. For example, look at the following code void fake_apply(Base* bp, MEMF* mfp) { Base b; (b.*mfp)(); } This function is type-correct and has the same signature as apply(). If it were called with a member function of class Derived, that function would be invoked for a Base class object. Bad things could then happen, for example suppose class Derived had some extra data members that were not in class Base, and suppose the function munged them. Chaos would follow. The C++ type system attempts to guarantee that this kind of error never happens and that's why the call is not allowed. You can always violate the type system by using casts but casting member function pointers is particularly dangerous and implementation dependent. In the implementations I know about, you will get away with casting a pointer to derived member function to pointer to base member function and then applying it to a derived object if you don't use multiple inheritance and the function is not virtual. I don't know any reason why this shouldn't work in other present or future implementations, but ... Another approach that may be satisfactory is to use virtual functions. If the function Base::memf() above was virtual, then you could rewrite the erroneous call above apply(&d, &Base::memf); // call d.Derived::memf() the hard way This works because &Base::memf is a virtual pointer which is bound when it is invoked and it is invoked for a Derived object. The limitation of this approach is that any function you want to call in the derived class must be declared as a virtual in the base class. If that isn't good enough for you, you could write a virtual function invoke() in the base class with a string or Symbol argument that tells what function to invoke, and then in each derived class override invoke() with a version that looks up the function and calls it. You might be able to think of some other languages that have similar facilities built in [:-)], but you might prefer to do it by hand if method invocation occurs only rarely in your program and you don't want to lose the other advantages of C++. -- Jonathan Shopiro AT&T Bell Laboratories, Warren, NJ 07060-0908 research!shopiro (201) 580-4229