abcscnge@csunb.csun.edu (Scott Neugroschl) (02/26/91)
Hi. I have a question about inheritance and references. I have the following classes: class StackEntry { public: StackEntry() { } virtual ~StackEntry() { } }; class Stack { public: // ... Stack& operator>(StackEntry*&); // Stack pop operator // .... private: // ... }; class Disk : public StackEntry { // ... }; // ... Stack s; Disk *dp; // ... s > dp; // pop s into dp <<== PROBLEM HERE Now the question. Turbo C++ generates a temporary variable (implicit type cast) so that dp never gets set. G++, on the other hand allows the construct, and the code does what I expect. Since Disk is a subclass of StackEntry, and a Disk* can be assigned to a StackEntry*, I think G++ is right. Is it? If not, why? Please respond via Email, as I don't get on this system too often. Thanks, Scott Internet: abcscnge@csunb.csun.edu Compu$erve: 72027,3162 GEnie: S.NEUGROSCHL AT&T: (818)-902-4507 USnail: 8000 Woodley Ave M/S 44-55 Van Nuys, CA, 91409 -- Scott "The Pseudo-Hacker" Neugroschl abcscnge@csuna.csun.edu -- Beat me, Whip me, make me code in Ada -- Disclaimers? We don't need no stinking disclaimers!!!
wmm@world.std.com (William M Miller) (02/26/91)
abcscnge@csunb.csun.edu (Scott Neugroschl) writes: > I have the following classes: > > class Stack { > Stack& operator>(StackEntry*&); // Stack pop operator > }; > > class Disk : public StackEntry { > }; > Stack s; > Disk *dp; > s > dp; // pop s into dp <<== PROBLEM HERE > > Now the question. > Turbo C++ generates a temporary variable (implicit type cast) so that > dp never gets set. G++, on the other hand allows the construct, and > the code does what I expect. Since Disk is a subclass of StackEntry, > and a Disk* can be assigned to a StackEntry*, I think G++ is right. > Is it? If not, why? Turbo C++ has it right, G++ is wrong. Here's what E&S has to say (section 8.4.3, page 154): "If the initializer for a reference to type T is an lvalue of type T or of a type derived (section 10) from T for which T is an accessible base (section 4.6), the reference will refer to the initializer; otherwise, if and only if the reference is to a const an object of type T will be created and initialized with the initializer." The key fact here is that, although Disk is derived from StackEntry, Disk* is NOT derived from StackEntry*; they are different types. (The restriction to const references, BTW, is a change from the 2.0 definition on which Turbo C++ is based, so it's not a bug that the compiler creates a temporary instead of issuing an error; under a fully 2.1-compliant compiler, though, the "s > dp" would be an error.) As another way of looking at this, think of the analogy with int and long. You can convert an int to a long, just as you can convert a Disk* to a StackEntry*, but clearly an int& is not compatible with a long&. While it's true that there are probably no representation differences between a Disk* and StackEntry*, the type implications on which the treatment of references is based are exactly the same as for int and long. > Please respond via Email, as I don't get on this system too often. I've done so, but I thought this question was of sufficient interest to warrant posting, as well. -- William M. Miller, Glockenspiel, Ltd. wmm@world.std.com
jimad@microsoft.UUCP (Jim ADCOCK) (03/05/91)
In article <1991Feb26.060459.6777@csun.edu| abcscnge@csunb.csun.edu (Scott Neugroschl) writes: | |Hi. I have a question about inheritance and references. | |I have the following classes: | | class StackEntry { | public: | StackEntry() { } | virtual ~StackEntry() { } | }; | | class Stack { | public: | // ... | Stack& operator>(StackEntry*&); // Stack pop operator | // .... | private: | // ... | }; | | class Disk : public StackEntry { | // ... | }; | | // ... | | Stack s; | Disk *dp; | | // ... | s > dp; // pop s into dp <<== PROBLEM HERE | |Now the question. | Turbo C++ generates a temporary variable (implicit type cast) so that | dp never gets set. G++, on the other hand allows the construct, and | the code does what I expect. Since Disk is a subclass of StackEntry, | and a Disk* can be assigned to a StackEntry*, I think G++ is right. | Is it? If not, why? As far as I can tell from ARM, neither is right. Section 8.4.3 says that when initializing a reference, if the initializer is an exact match or of a type derived from an exact match as an accessible base, then the initialization is accepted, and no temporary is generated. Otherwise, if the initializer is a const, a temporary is generated. Otherwise "error." In which case, your example is in error, and dp should require an explicit (reference) cast. I don't like this definition, mind you. I'd like the pertinent section in 8.4.3 to say: If the initializer for a reference to type T is an lvalue of type T or of a type derived from T for which T is an accessible base or if T and the initializer are pointers or references to such, then the reference will refer to the initializer; otherwise, if and only if the reference is to a const an object of type T will be created and initialized with the initializer. This change would allow references to pointer or references to accessible base types be initialized by pointers or references to derived types, as you desire, and as common sense would seem to indicate.
wmm@world.std.com (William M Miller) (03/05/91)
jimad@microsoft.UUCP (Jim ADCOCK) writes: > I don't like this definition, mind you. I'd like the pertinent section > in 8.4.3 to say: > > If the initializer for a reference to type T is an lvalue of type T or of > a type derived from T for which T is an accessible base or if T and the > initializer are pointers or references to such, then the reference > will refer to the initializer... NO. This is a BAD idea and opens a gaping hole in the type system as bad as C's allowing assignment of void* pointers to typed pointers without casts. Consider the following example: class B {...}; class D: public B {...}; B B_glob; void get_a_B(B*& Bp) { Bp = &B_glob; } void f() { D* dp; get_a_B(dp); // illegal under current definition } Under your proposed definition, this would be perfectly legal and leave "dp" pointing at a B object -- a sure recipe for disaster. The whole idea behind the C++ type system is that if you do type-insecure things, you ought to have to use an explicit cast to indicate that you know what you're doing; there are no casts in the above code, even though it's terribly type-insecure. In cases where you really do want to be able to do what "common sense would seem to indicate," you can use an explicit cast: D* dp; B*& rbp = (B*&) dp; As I said in an earlier posting on this subject, you can get some insight into why an exact match is required by considering int and long -- even though you can convert an int into a long, just as you can a D* into a B*, that doesn't mean that you should be able to initialize a long& with an int; similarly, you can't initialize a B*& with a D*. -- William M. Miller, Glockenspiel, Ltd. wmm@world.std.com
jimad@microsoft.UUCP (Jim ADCOCK) (03/08/91)
In article <1991Mar5.010600.19687@world.std.com> wmm@world.std.com (William M Miller) writes: |jimad@microsoft.UUCP (Jim ADCOCK) writes: |> I don't like this definition, mind you. I'd like the pertinent section |> in 8.4.3 to say: |> |> If the initializer for a reference to type T is an lvalue of type T or of |> a type derived from T for which T is an accessible base or if T and the |> initializer are pointers or references to such, then the reference |> will refer to the initializer... | |NO. This is a BAD idea and opens a gaping hole in the type system as bad as |C's allowing assignment of void* pointers to typed pointers without casts. |Consider the following example: I agree, it was a bad idea. I can't remember *what* I was thinking! |The whole idea behind |the C++ type system is that if you do type-insecure things, you ought to |have to use an explicit cast to indicate that you know what you're doing; |there are no casts in the above code, even though it's terribly |type-insecure. I agree that this *ought* to be how C++ works, but unfortunately, the automatic *implicit* "cast" of an array formal argument to a pointer to its first element violates these rules of type safety: void zero10000ints(int ar[10000]) { for (int i=0; i<10000; ++i) ar[i] = 0; } main() { int ar[10]; zero10000ints(ar); // oops, run-time crash! return 0; } Are you lobbying the ANSI-C++ committee to fix this obvious hole in C++'s type safety? [The way C++ *ought* to work in this situation is that an array formal argument is *implicitly* converted to a reference to an array of the same type. That way the C tradition that arrays are passed by reference would be maintained, and C++ could be a type-safe language]
chip@tct.uucp (Chip Salzenberg) (03/08/91)
According to jimad@microsoft.UUCP (Jim ADCOCK): >The way C++ *ought* to work in this situation is that an array formal >argument is *implicitly* converted to a reference to an array of the >same type. That way the C tradition that arrays are passed by reference >would be maintained, and C++ could be a type-safe language. C++ is a type-safe language. It just isn't pointer-safe. And no small change along the lines described by Jim will make it so.