[comp.lang.c++] Multiple inheritance and type casting

iain@batserver.cs.uq.oz.au (Iain Fogg) (10/03/90)

Assume the following class definitions:

class B {
  .
  .
  .
  public:
    virtual f ( B &b );
  .
  .
  .
}


class D1 : virtual public B {
  .
  .
  .
  public:
    virtual f ( B &b );
  .
  .
  .
}


class D2 : virtual public B {
  .
  .
  .
  public:
    virtual f ( B &b );
  .
  .
  .
}


class X : public D1, public D2 {
  .
  .
  .
  public:
    virtual f ( B &b );
  .
  .
  .
}


If D1::f() cast the formal parameter to type D1 (ie. (D1 &) b), for
example to interrogate private data members, no problems. However, if
X::f() casts to an X& my compiler complains (TC++ V1.0) saying
"Cannot cast from B& to X&". I *know* that when X::f() is called, the
actual parameter is an X&. Why is it so? Is there a fix? Email replies
and I will summarise.


+----------------------------------------------------------------------+
+ Dr Iain Fogg                                                         +
+ Senior Research Officer                                              +
+                                                                      +
+ Department of Computer Science              Telephone: (07) 377 2903 +
+ University of Queensland                          Fax: (07) 371 0783 +
+ Queensland                                            Telex: AA40315 +
+ Australia 4072                     email: iain@batserver.cs.uq.oz.au +
+----------------------------------------------------------------------+

jdunn@polyslo.CalPoly.EDU (Drunk Dude #3) (10/03/90)

In article <5087@uqcspe.cs.uq.oz.au> iain@batserver.cs.uq.oz.au writes:
>
>Assume the following class definitions:
> [...]
>
>If D1::f() cast the formal parameter to type D1 (ie. (D1 &) b), for
>example to interrogate private data members, no problems. However, if
>X::f() casts to an X& my compiler complains (TC++ V1.0) saying
>"Cannot cast from B& to X&".

This works for me:

void X::f(X &x) {}     // standard method def

X x;
Y y;  // Y is derived from X

X.f(*(X *)&y);         // get a pointer and convert the pointer

This actually makes sense, because converting from one object to another
requires a new (anonymous) object to be created, and that requires that
a constructor exist on the casted-to type to do the conversion.

I have found one bug in TC++ 1.0 involving multiple inheritance, and I 
believe I am in the process of tracking another down now.  If you want
me to send you a copy of the compiler-breaking code when I send it off
to Borland, let me know.  Probably be a week before I get around to it,
though...

>+----------------------------------------------------------------------+
>+ Dr Iain Fogg                                                         +
>+----------------------------------------------------------------------+

-jd




-- 
Major@Jungle1        Plans to go with the flow have the one flaw of being plans 


jdunn@polyslo.CalPoly.EDU

rae@gpu.utcs.toronto.edu (Reid Ellis) (10/05/90)

iain@batserver.cs.uq.oz.au writes:
	>If D1::f() cast the formal parameter to type D1 (ie. (D1
	>&) b), for example to interrogate private data members, no
	>problems. However, if X::f() casts to an X& my compiler
	>complains (TC++ V1.0) saying "Cannot cast from B& to X&".

How do you know that the object passed in is really an "X &"?  If you are
writing some sort of "clone()" method for instance, there is no way to tell if
the object being passed in is really of the type you expect.  This is a design
flaw.  There are other ways to do it.  What is it you're trying to do?

Drunk Dude #3 <jdunn@polyslo.CalPoly.EDU> writes:
	>void X::f(X &x) {} // standard method def
	>X x;
	>Y y;  // Y is derived from X
	>X.f(*(X *)&y);     // get a pointer and convert the pointer

If Y is derived from X, you should be able to simply say:
	x.f(&y); // no cast required

I have found that casts lead to errors, plain and simple.  If you are casting
base types to derived types, something is wrong with your design.  If you are
casting from derived types to base types, you're being silly ;-).


					Reid
--
Reid Ellis  264 Broadway Avenue, Toronto ON, M4P 1V9               Canada
rae@gpu.utcs.toronto.edu || rae%alias@csri.toronto.edu || +1 416 487 1383

iain@batserver.cs.uq.oz.au (Iain Fogg) (10/08/90)

rae@gpu.utcs.toronto.edu (Reid Ellis) writes:

>iain@batserver.cs.uq.oz.au writes:
>	>If D1::f() cast the formal parameter to type D1 (ie. (D1
>	>&) b), for example to interrogate private data members, no
>	>problems. However, if X::f() casts to an X& my compiler
>	>complains (TC++ V1.0) saying "Cannot cast from B& to X&".

>How do you know that the object passed in is really an "X &"?  If you are
>writing some sort of "clone()" method for instance, there is no way to tell if
>the object being passed in is really of the type you expect.  This is a design
>flaw.  There are other ways to do it.  What is it you're trying to do?

>I have found that casts lead to errors, plain and simple.  If you are casting
>base types to derived types, something is wrong with your design.

  I don't agree. Take for example the overloading of operator==. If we
  defined it as follows
	inline operator == (Base &b1, Base &b2) { return b1.IsEqual (b2); }
  where IsEqual is a virtual function declared in class Base. It's
  prototype is
	virtual int IsEqual (Base &);

  Now, any class derived from Base will probably want it's own version
  of IsEqual, but because the formal parameter is a Base &, a cast to
  the derived class will be required to compare attributes of the
  derived class (for example).

  For example,
	class Derived: public Base {
	  public:
	    virtual int IsEqual (Base &b) { return x == ((Derived &) b).x; }
	  private:
	    int x;
	}

  We can then say things like 

	Derived d1, d2;

	if ( d1 == d2 ) ...

  If I've missed something, and there is a more elegant solution to the
  above scenario, please contribute. (Assume that we cannot declare
  operator== as a virtual function in Base).
  
>If you are casting from derived types to base types, you're being
>silly ;-).

Bloody stupid, more likely.


Iain Fogg.


PS. For those familiar with TC++ classlib, this scenario arises when
    testing the equality of objects of classes derived from Object.

bashford@scripps.edu (Don Bashford) (10/10/90)

In article <5130@uqcspe.cs.uq.oz.au> iain@batserver.cs.uq.oz.au writes:
>rae@gpu.utcs.toronto.edu (Reid Ellis) writes:
>
>>I have found that casts lead to errors, plain and simple.  If you are casting
>>base types to derived types, something is wrong with your design.
>
>  I don't agree. Take for example the overloading of operator==. If we
>  defined it as follows
>	inline operator == (Base &b1, Base &b2) { return b1.IsEqual (b2); }
>  where IsEqual is a virtual function declared in class Base. It's
>  prototype is
>	virtual int IsEqual (Base &);
>
>	class Derived: public Base {
>	  public:
>	    virtual int IsEqual (Base &b) { return x == ((Derived &) b).x; }
>	  private:
>	    int x;
>	}
>
>  We can then say things like 
>
>	Derived d1, d2;
>
>	if ( d1 == d2 ) ...
>
>  If I've missed something, and there is a more elegant solution to the
>  above scenario, please contribute. (Assume that we cannot declare
>  operator== as a virtual function in Base).
>  

Here is a working example (tested with g++) that does what I think
your example tries to do.  Note that all the == and IsEqual
machinery is defined in the Base class and that as long as the
notion of "equalness" remains the same, the derived class can
continue to use it thanks to the standard conversions under derivation
(see Lippman, p. 319).

#include <stream.h>

class Base {
private:
  int x;
public:
  Base (int xv) : x(xv) {}
  int IsEqual (Base &b) {return x == b.x;}
  friend int operator == (Base &b1, Base &b2);
};

int operator == (Base &b1, Base &b2) {return b1.IsEqual(b2);}

class Derived : public Base {
private:
  int y;
public:
  Derived (int xv) : Base(xv) {}
};

main ()
{
  Derived d1(4), d2(4), d3(5);
  cout << (d1 == d2) << "\n" << (d2 == d3) << "\n";
}
  

>  Now, any class derived from Base will probably want it's own version
>  of IsEqual, but because the formal parameter is a Base &, a cast to
>  the derived class will be required to compare attributes of the
>  derived class (for example).
>

If a derived class wants its own version of IsEqual then the notion
of "equalness" must have changed, so it is quite natural that you
should need to define a new set of functions such as

Derived::IsEqual (Base &)
Derived::IsEqual (Derived &)
int operator == (Base &, Derived &)

and so on.  This might require making x a protected rather than
private member of Base or providing an access function.
As far as I can see, none of this requires the use of virtual functions.
Function overloading is enough.

Don Bashford
bashford@scripps.edu

rae@gpu.utcs.toronto.edu (Reid Ellis) (10/10/90)

Reid Ellis <rae@gpu.utcs.toronto.edu> writes:
	|I have found that casts lead to errors, plain and simple.  If
	|you are casting base types to derived types, something is
	|wrong with your design.

Iain Fogg <iain@batserver.cs.uq.oz.au> writes:
	|I don't agree. Take for example the overloading of
	|operator==. If we defined it as follows
	|  inline operator == (Base &b1, Base &b2) { return b1.IsEqual (b2); }
	|where IsEqual is a virtual function declared in class Base.
	|It's prototype is
	|  virtual int IsEqual (Base &);
	|Now, any class derived from Base will probably want it's own
	|version of IsEqual, but because the formal parameter is a
	|Base &, a cast to the derived class will be required to
	|compare attributes of the derived class (for example).
	|
	|For example,
	|  class Derived: public Base {
	|    public:
	|      virtual int IsEqual(Base &b) { return x == ((Derived &) b).x; }
	|    private:
	|      int x;
	|  }
	|We can then say things like 
	|
	|  Derived d1, d2;
	|
	|  if ( d1 == d2 ) ...

But how can it be *known* that the operand of operator==() is a Derived?
One could equally well say something like:

	Derived d1;
	Base b;

	if(d1 == b) ...

and the above operator==() would fail, or at least return a
meaningless value since the operand is not a Derived.  This is what is
meant by "dangerous".  It's even more so if pointers are being dealt
with rather than ints.  The closest "safe" method of accomplishing the
above is to define the following methods in base:

	virtual int Base::IsEqual(Base &b);
	virtual int Base::IsEqual(Derived &d)
		{ panic("Attempt to compare a Base and a Derived\n"); }

	int Derived::IsEqual(Base &b)    { return b.isEqual(*this); }
	int Derived::IsEqual(Derived &d) { return x == d.x; }

Now obviously this is not always possible since modifying the base
class may not be an option.  This leads to things like the NIH
library's "static char *name" and "virtual char * getName()" type of
functionality for each class to identify itself.  [The actual code is
different, but the idea is basically as represented]  Then you can
write code as follows:

	int Derived::IsEqual(Base &b)
	{
	// Check for same type first
	if(b.getTypeName() == getTypeName()) {
		// Okay, it's really a Derived
		return ((Derived &)b).x == x;
	} else {
		panic("Derived::IsEqual wrong type", b.getTypeName());
	}

It is ugly, but it checks for the correct type before doing anything
dangerous.  Real code would have to be more complicated than the above
since it would have to handle descendants of Derived as well.

It comes down to the fact that if two base pointers are being compared
in this manner, where derived class data is required to determine the
result, the comparison is being done at the wrong level.  Somewhere
type information is being lost where it needn't be.

					Reid
--
Reid Ellis  264 Broadway Avenue, Toronto ON, M4P 1V9               Canada
rae@gpu.utcs.toronto.edu || rae%alias@csri.toronto.edu || +1 416 487 1383