[comp.lang.c++] contravariance confusion

patty@chmsr (Patty Jones) (01/19/90)

What does the following 1.36.1 g++ compiler warning mean??

warning: contravariance violation for method types ignored

and could this become a serious problem later on as our program grows?

Also, I'm not sure I understand what contravariance means -- is that when
you pass a pointer to member of class "base" to a function that expects
a pointer to a member of class "derived"?  But in our situation we have
defined a function (SchedEvent; see below) to expect pointers to class "base" 
and we actually use the SchedEvent function with pointers to class "derived".  

A bit of background:

We are working on an interactive real-time simulation in C++ (in the
domain of satellite ground control) using the g++ 1.36.1
compiler on Suns.

We have a function that creates event objects which are maintained
on an event list; the function prototype is as follows:

  SchedEvent(int, ActiveSimulationObject *, void_fp, char *)

  where
  1) the first argument is an integer which represents the time 
	  the event is scheduled to occur,
  2) a pointer to an ActiveSimulationObject object (ActiveSimulationObject is
     our base class); this points to the object to whom something is going to 
	 occur,
  3) void_fp is a pointer to a member function in class ActiveSimulationObject 
       (i.e., typedef void (ActiveSimulationObject :: *void_fp) (...);  )
  4) the fourth argument is a character string that represents the 
	  arguments to the function pointed to by void_fp.

Our program works but we are getting the "contravariance" compiler warning 
on lines where we call SchedEvent.  We are calling SchedEvent with
pointers to derived classes and their members.   For example,

part of the class hierarchy we have right now is as follows:

		ActiveSimulationObject
                |
                |
          SpacecraftComponent
                |
                |
            TapeRecorder

and part of the class definition for TapeRecorder is as follows:

class TapeRecorder : public SpacecraftComponent {
	char *mode;  // can be "record", "standby", "playback", "fast forward"
	.
	.

 public:
	 void setMode(char *m) { mode = m; }
	.
	.
};

And finally, we declare:

TapeRecorder TR1;

To schedule TR1 to go into "record" mode at time 5, we do the following:

   SchedEvent(5, &TR1, &TR1.setMode, "record"); 

and as I said, we get the "contravariance" warning on lines such as this
where we call SchedEvent.

thanks for your help!

patty

Patty Jones                                        
Center for Human-Machine Systems Research
School of Industrial and Systems Engineering
Georgia Institute of Technology,  Atlanta, GA 30332

UUCP:	patty@chmsr.UUCP               
        {backbones}!gatech!chmsr!patty
INTERNET:	patty@chmsr.gatech.edu
Patty Jones                                        
Center for Human-Machine Systems Research
School of Industrial and Systems Engineering
Georgia Institute of Technology,  Atlanta, GA 30332

UUCP:	patty@chmsr.UUCP               
        {backbones}!gatech!chmsr!patty
INTERNET:	patty@chmsr.gatech.edu

shopiro@alice.UUCP (Jonathan Shopiro) (01/20/90)

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