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