Path: utzoo!utgpu!jarvis.csri.toronto.edu!rutgers!gatech!bloom-beacon!apple!oliveb!amdahl!pacbell!osc!strick From: strick@osc.COM (henry strickland) Newsgroups: comp.lang.c++ Subject: Re: Implementing virtual functions that return reference to self Keywords: IT IS UNLAWFUL TO DUPLICATE THIS KEY [KZSU-Stanford studio key] Message-ID: <480@osc.COM> Date: 13 Aug 89 21:52:01 GMT References: <8975@thorin.cs.unc.edu> <2045@cadillac.CAD.MCC.COM> Reply-To: strick@osc.UUCP (henry strickland) Organization: the techwood toaster pastry users group, georgia tech Lines: 224 Company: OBJECT-Sciences Corp (Menlo Park CA) [ Notice! THIS ARTICLE CONTAINS A RETRACTION at the end! Do not flame me [ until you you see my turn of thinking. I include my faulty argument [ because it is VERY tempting and because other issues are addressed, [ such as why someone would ever use operator=() on indefinitely-typed [ objects. If I'm still wrong at the end, please fry me! I want to know! ]]]]] In article <2045@cadillac.CAD.MCC.COM> vaughan@mcc.com (Paul Vaughan) writes: > One of Andrew's arguments had to do with ambiguity when you >had references that refered to objects which could be a subclass of >the stated type (indefinitely typed). Note that this is also >impossible in the current c++. I wrote an article previously (which, [ paul: you say this is impossible, but I think I do it below. Impossible or illegal? --strick ] [ I do not retract this: in fact, I do it. --strick ] >from the lack of response, either didn't get out, or was too garbled >to understand) that compared indefinitely typed references with pointers. >Basically, I don't think there is any ambiguity added with >indefinitely typed references that the language doesn't already >resolve with pointers. His other argument had to do with overloaded >functions. My response to that was that overloaded functions always >work on the stated type of their arguments, rather than the actual >type, and that there is no ambiguity involved there either. The rule >about not being able to overload functions based only on pointer types >simply hides this fact. This isn't perhaps the most desirable >property in terms of language power, but it is efficient. The only >language I know of that allows generic functions with multiple >dispatch is CLOS. I agree. Virtual functions are selected by the actual type of the object; overloaded functions are discriminated by the declared type of the reference or pointer. [ I do not retract this --strick ] ............................................................................. Concerning Andrew Koenig's piece of code: class X { public: virtual X& self() { return *this; } }; class Y: public X { public: virtual Y& self() { return *this; } // illegal }; main() { X* xp = new Y; X* xq = new Y; xp->self() = xq->self(); } The problem here is that a NON-VIRTUAL operator=() is being used in a context where a VIRTUAL one is required. /* and a smart virtual one at that: C++ has another problem from the beginning: in smalltalk, you can't assign objects, just pointers: all pointers are the same size; in C++ the language allows you to assign objects, but if there are derived classes, it sounds like a very bad idea. */ [ I do not retract these ] ........................................................................... The above virtual/nonvirtual problem also exists in the language even if you don't have specialized return types from derived class virtuals. This code compiles without errors with cfront1.2, cfront2.0, and g++1.35.1-: struct X { int a; virtual char* isa(); }; struct Y : X { int b; virtual char* isa(); }; char* X::isa() { return "I am an X."; }; char* Y::isa() { return "I am a Y."; }; X* anXptr(int y) { return y? (new Y) : (new X); }; X& anXref(int y) { return *( y? (new Y) : (new X) ); }; main() { // the problem exists with functions returning pointers X* xp= anXptr(0); X* xq= anXptr(1); *xp = *xq; // the problem exists with ptr variables X& xr= *anXptr(0); X& xs= *anXptr(1); xr = xs; // the problem exists with ref variables // the problem exists with functions returning references X* xxp= & anXref(0); X* xxq= & anXref(1); *xp = *xq; // the problem exists with ptr variables X& xxr= anXref(0); X& xxs= anXref(1); xxr = xxs; // the problem exists with ref variables }; Again, the ONLY problems I can see is that we are using a non-virtual operator=() where a virtual is required, and that assignment of objects of specializable classes is, methinks, a bad idea. Notice that all four of the assignments will break: field b will not be copied. I don't know whether the virtual function table pointer will be copied. I suppose so. Either way it's heinous. [ I am wrong about the following, however.... ] I see no problem with specialized return types from virtual functions of derived classes. It seems to me that they meet the protocol specified by the base class. In the case you have an indefinitely-typed pointer/reference to the object, you get something within the realm of what you're promised. In the case you have a specifically-typed pointer/reference (you know for some reason the exact type), you do not have to do any casts to use any special protocol of the exact type. I thought it was the obvious thing to do. I'm afraid I'm really missing something, and I want to fully understand this problem. The language designer and the implementers of cfront1.2, cfront2.0, and g++1.35 all disallow specialized return types in the virtual function case. Why? What am I missing? strick ........................................................................ AND THEN, as I typed ":wq", but before I hit return, I WAS ENLIGNTENED. This is one of the fundamental differences between single- and multiple- inheritance in C++. This is what I was missing, and is probably what many of us who have been using single-inheritance C++ for a couple of years are not used to thinking: +---------------------------------------------------------------------------+ | In a multiply-inherited object, the pointers to derived classes are NOT | | necessarily the same addresses as the pointers to their base classes. | +---------------------------------------------------------------------------+ So suppose instead my class Y were defined with three bases, with the base X buried in the middle: struct Z { int z; }; struct W { int w; }; struct X { int a; virtual char* isa(); }; struct Y : Z, X, W { int b; virtual char* isa(); }; The memory layout of a Y is probably like this: Y* ==> Z* ==> | int z; | X* ==> | int a; | W* ==> | int w; | | int b; | If the bases had been virtual, Z* and Y* would not even point to the same address. Now if I had either "virtual X& X::self()" or "virtual X* X::self()", like this: struct X { int a; virtual char* isa(); virtual X* self(); }; , a class derived from X in a multiply-inherited way cannot have a virtual function returning a pointer to itself and call that a pointer to an X: struct Y : Z, X, W { int b; virtual char* isa(); virtual Y* self(); }; // illegal , because a pointer to a Y is most definitely NOT a pointer to an X, as shown in my layout diagram above. Forgive me if someone else made this clear, but it didn't sink in when I read it. ......................................................................... To let this sink in a little more, go back to my function `anXptr', and assume the multiple-inherited definition of class Y: X* anXptr(int y) { return y? (new Y) : (new X); }; If y is false, the address returned by (new X) is returned by the function. But if y is true, the function returns the location of the X base-object inside the Y instance allocated by (new Y). The code for these two return types is NOT symmetric, like it would be in the single-inheritance case. Also notice that with m-i one can not say { X* xp= new Y; delete xp; } for the same reason. You can only delete it using pointer of the same type that it was allocated from. In single-inheritance C++ you could use any base pointer to delete it. strick -- strick@osc.com 415-325-2300 uunet!lll-winken!pacbell!osc!strick ( also strick@gatech.edu )