Path: utzoo!utgpu!news-server.csri.toronto.edu!rpi!zaphod.mps.ohio-state.edu!uakari.primate.wisc.edu!zazen!wuarchive!udel!rochester!kodak!islsun!cok From: cok@islsun.Kodak.COM (David Cok) Newsgroups: comp.lang.c++ Subject: Re: Seeking neat way to do binary "virtual" functions. Message-ID: <1991Mar19.200816.10480@kodak.kodak.com> Date: 19 Mar 91 20:08:16 GMT References: Sender: cok@Kodak.COM Distribution: comp Organization: Eastman Kodak Co., Rochester, NY Lines: 194 In article ngo@tammy.harvard.edu (Tom Ngo) writes: > >I am looking for a pretty way to accomplish the following task, which >must be fairly common. I have a base class B with publicly derived >classes D1 and D2. And I have a class BHandler that handles >homogeneous collections of B's, i.e. collections of B's which are >either all D1 or all D2. > ... background stuff deleted > >Now my problem is, BHandler needs to make B's undergo binary >operations, and I have been struggling to find a way to implement >these in an elegant manner. Here are a few of the solutions I have >found: > .. his examples included at end >I would appreciate any suggestions. You'll undoubtedly get many suggestions, but I'll pitch in one: Try implementing your own type-safe downcast: class B { virtual void binary_op(B*) = 0; virtual D1* safe_cast_to_D1() { return (D1*)0; } virtual D2* safe_cast_to_D2() { return (D2*)0; } }; class D1: public B { virtual void binary_op(B*) { D1* d = b2->safe_cast_to_D1(); if (d == (D1*)0) cout << "UhOh!"; else { // do binary op with this and d } } virtual D1* safe_cast_to_D1() { return this; } }; class D2: public B { virtual void binary_op(B*); // complementary implementation to D1 virtual D2* safe_cast_to_D2() { return this; } }; class BHandler { B *b0,*b1; void do_binary_op() { b0->binary_op(b1); } }; >Do people consider this a >fundamental limitation of C++, that the only way to safely convert a >pointer to a class to a pointer to one of its derived classes is >through a virtual function call? > >-- > Tom Ngo > ngo@harvard.harvard.edu > 617/495-1768 lab number, leave message This uses virtual functions, as does your preferred solution [3] below, but seems to me to be clearer. What I do not like about both of these is that they require the base class to know about the derived classes. At least the base class does not have to know about the non-inherited member functions of the derived classes, but it still seems to me to be a lack in the language. This lack could be corrected by the simple addition of type-safe down casting, namely requiring (D1*)b2 to do what b2->safe_cast_to_D1() does above. This would not break any programs which do not already have bugs (from down casting to the wrong derived type). A similar problem arises in virtual functions which return pointers to the base type. In the derived class, they must also return pointers to the base class, when often one wants the derived class function to return a pointer to a derived class object. Again, one can provide this in C++ by adding an additional helper function, but it requires source code access to the Base class, which to my mind should not be necessary. I also believe that this is a lack in the language: it should allow contravariace on the function return type and this problem would go away. Again this solution does not break existing programs, does not add keywords, and would make some of my programs 10s of percent more concise. A third place this problem comes up is on getting to derived class members from a pointer to a Base class. That is, with class B { ... }; class D1: public B { ... void g(); // something specific to D1's }; class D2: public B { ... void h(); // something specific to D2's }; B* b1 = new D1; how do I apply D1::g() to b1? A simple cast is not safe in the general case where b1 may have a long and complicated history. Some recent discussion on the net advocated adding a virtual function g() to B which would give an error message when applied to a true B* (and hence to a D2*). I dislike this immensely because it confuses the base class with what should be derived class concerns. It also requires source code access to the base class. Providing type safe down casting would make this trivial and would satisfy at least one part of why some people want to ask objects for their type. David R. Cok Eastman Kodak Company cok@Kodak.COM Appendix: >(1) Cast B to D1 within a D1 member function: > > void BHandler::do_binary_op() > { > b0->binary_op(b1); > } > void D1::binary_op(B* that_) > { > D1 *that = (D1 *) that_; > // do stuff with this and that > } > > I don't like this solution very much because even though I know > that_ can be converted to a D1*, the cast still seems dangerous. > >(2) Take advantage of the fact that the only truly safe way to convert > a B* to a D1* is through a virtual function call: > > void BHandler::do_binary_op() > { > b1->operate_with(); > b0->binary_op(); > } > D1::operate_with() > { > that = this; // then, that is a static member of D1, of type D1* > } > D1::binary_op() > { > // do stuff with this and that > } > > This is even worse because in my application some calls to > binary_op() need to be nested, so I still have to put a copy of > that in an automatic variable, i.e. > > D1::binary_op() > { > D1* mythat = that; > // do stuff with this and mythat > } > >(3) The safest implementation I have thought of seems less error-prone > but is somewhat cumbersome: > > class BHandler { > B *b0, b1; > void do_binary_op(); > void provide_that(); > } > void BHandler::provide_that() > { > b1->is_that(); > } > class B { > void is_that() =0; > void request_that() =0; > } > class D1 : public B { > D1 (BHandler* h_) : h(h_) {} > BHandler* h; > D1* that; > static D1* static_that; > void is_that() { static_that = this;} > void request_that() > { h->provide_that(); that = static_that; } > void binary_op(); > } > void D1::binary_op() > { > request_that(); > // do stuff with this and that > } > > This is my favorite implementation because by merely glancing at > the code one can tell it is safe... but at what cost? >