[comp.lang.c++] Inheritance and references question

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.