[comp.lang.c++] What to do about generics and polymorphism in C++

nagle@well.sf.ca.us (John Nagle) (03/22/90)

      Let's start a discussion about the problem of how to achieve
generic objects (ones with types as parameters, at least at compile
time) in C++.  Some possibilities:

      1.  Explicitly use "pointer to void".  Works, but violates
	  type safety and requires much repetitive writing.

      2.  Encapsulate the necessary messy definitions in preprocessor
	  defines, as shown in "The C Answer Book".  Ugly and tends
	  to produce confusing error messages when trouble occurs.
	  May not be type safe.

      3.  Add compile-time generic facilities along the lines of those
	  in Ada to the language.  Provides similar functionality to
	  #2, with proper checking and diagnostics, but requires
	  a language change.

      4.  Add types as first-class objects, as in Smalltalk, so that
	  one can have type-valued variables and can do declarations
	  at run time.

      5.  Other?

Comments?

						John Nagle

marc@dumbcat.UUCP (Marco S Hyman) (03/23/90)

In article <16808@well.sf.ca.us> nagle@well.UUCP (John Nagle) writes:
 >       Let's start a discussion about the problem of how to achieve
 > generic objects (ones with types as parameters, at least at compile
 > time) in C++.  Some possibilities:
 > 
 >       1.  Explicitly use "pointer to void".  Works, but violates
 > 	  type safety and requires much repetitive writing.

Doesn't always work.  In the case of multiple inheritance pointer adjustment
is not performed when assigning to a void pointer.  When you take from a
void * a different type than was placed in the void pointer, things break.
Example:

	#include <stdio.h>
	class B1 {
	public:
		int	i1;
		B1(): i1(0) {};
	};
	class B2 {
	public:
		int	i2;
		B2(): i2(0) {};
	};
	class D: public B1, public B2 {
	public:
		int	d;
		D(): d(0) {};
	};

	main()
	{
	void * generic;
	D something;
	B1 * b1 = &something;
	B2 * b2;

	++b1->i1;
	generic = b1;	// no adjustment
	b2 = generic;	// no adjustment
	printf( "%d\n", b2->i2 );
	}

The output will be 1, not zero.  The compiler assumes you know what you're
doing when you use void *.  Ouch.

// marc
-- 
// {ames,decwrl,sun}!pacbell!dumbcat!marc
// pacbell!dumbcat!marc@lll-winken.llnl.gov

basti@orthogo.UUCP (Sebastian Wangnick) (03/23/90)

nagle@well.sf.ca.us (John Nagle) writes:
>      Let's start a discussion about the problem of how to achieve
>generic objects (ones with types as parameters, at least at compile
>time) in C++.

What about the following possibility: Encapsulate the constaints
put onto the parameter type by the generic type into a new class,
and inherit a new parameter class from this one and from the original
parameter class.

This seems to be the way choosen in Smalltalk, where the most obvious
constraints put onto objects by, let say, Collections or Sets, 
i.e., test for equality and generation of a hash code per object,
are inherited by every object from the base class Object. Other classes
putting additional burdens onto their parameter classes, such as linked lists,
describe these constraints in another class, Link in this case,
from which every possible parameter class has to be inherited.

But with the dawn of multiple inheritance, it will be possible to use
parameter classes for certain generic classes *without* recoding them,
just by inheriting a new class from the original parameter class *and*
the constraint class, as I stated above.

This has even further benefits, because the constraint class is
the source of information where all the requirements needed by the
generic class from their parameter classes are bundled. Viewed with the
eyes of a software engineer, this special class shouuld better be called
the requirements class for the generic class.

Sebastian Wangnick,                       BRD -> D -|||> Deutsches Reich

dog@cbnewsl.ATT.COM (edward.n.schiebel) (03/24/90)

From article <16808@well.sf.ca.us>, by nagle@well.sf.ca.us (John Nagle):
> 
>       Let's start a discussion about the problem of how to achieve
> generic objects (ones with types as parameters, at least at compile
> time) in C++.  Some possibilities:
> 
>       3.  Add compile-time generic facilities along the lines of those
> 	  in Ada to the language.  Provides similar functionality to
> 	  #2, with proper checking and diagnostics, but requires
> 	  a language change.
> 
This is my choice.  Although I am not at all familiar with Ada, I
assume you are refering to an implementation generics similar to 
parameterized types described by Bjarne in some paper I can't find right 
now.  It seems to me that parameterized types provide
the type saftey  (as opposed to using void*'s) and the 
run-time efficiency (vrs. types as first-class objects)
that makes the most sense within the C++ language.  C++'s contributors
have always taken these two concerns very seriously throughout the 
language's evolution, and I don't expect them to stop now.

I have used and written (with more or less success) generic types
based on the preprocessor and I must say that it is an interim solution
at best.  Nonetheless, the generic types created by that scheme are a 
fair approximation of pramaterized types, and I have come to rely on
that kind of capability.  

From the first Usenix C++ Workshop (my first real introduction to the 
language) to the last OOPSLA, and probably through this years Usenix
conference (see you there), all I have heard is "what about generics" 
"what about exception handling" "what about garbage collection"?  
I don't even want to get another garbage collection discussion started,
and between the other two, for my money,
I would like to see parameterized types implemented first.  I believe
they would be adopted and used by more programs much quicker
than exception handling and would provide the greatest benefit to
C++'s users.

	Ed Schiebel
	AT&T Bell Laboratories
	dog@cblph.att.com
			

leo@atcmp.nl (Leo Willems) (03/26/90)

From article <152@dumbcat.UUCP>, by marc@dumbcat.UUCP (Marco S Hyman):
> In article <16808@well.sf.ca.us> nagle@well.UUCP (John Nagle) writes:
>  >       Let's start a discussion about the problem of how to achieve
>  > generic objects (ones with types as parameters, at least at compile
>  > time) in C++.  Some possibilities:
>  > 
>  >       1.  Explicitly use "pointer to void".  Works, but violates
>  > 	  type safety and requires much repetitive writing.
> 
> Doesn't always work.  In the case of multiple inheritance pointer adjustment
> void * a different type than was placed in the void pointer, things break.

Stuff deleted

> 	void * generic;
> 	D something;
> 	B1 * b1 = &something;
> 	B2 * b2;
> 
> 	++b1->i1;
> 	generic = b1;	// no adjustment
> 	b2 = generic;	// no adjustment

Under C++ 1.2 the last sentence will give a "bad assigment type" error.
As I understand it, assignment to void* must be casted in C++ (which
is different in ANSI-C) thereby bringing type-information back.

Isn't this true in C++ 2.0? (I have a lot of code (of the preprocessor-
generic type) which will break if type-information is lost (using a cast)).

If you apply a cast in the example does it work correct then?
(If it works, is it legal or just luck? Andrew Koenig told me a long time
ago it is legal, but that was in the 1.2 darkages, in which I still live :-))


Leo Willems			Internet: leo@atcmp.nl
AT Computing			UUCP:     mcvax!hp4nl!kunivv1!atcmpe!leo
P. O. Box 1428				
6501 BK  Nijmegen		Phone:    +31-80-566880
The Netherlands			Fax:	  +31-80-555887

marc@dumbcat.UUCP (Marco S Hyman) (03/27/90)

In article <574@atcmpe.atcmp.nl> leo@atcmp.nl (Leo  Willems) writes:
 > From article <152@dumbcat.UUCP>, by marc@dumbcat.UUCP (Marco S Hyman):
 > > 	void * generic;
 > > 	D something;
 > > 	B1 * b1 = &something;
 > > 	B2 * b2;
 > > 
 > > 	++b1->i1;
 > > 	generic = b1;	// no adjustment
 > > 	b2 = generic;	// no adjustment
 > 
 > If you apply a cast in the example does it work correct then?
 > (If it works, is it legal or just luck? Andrew Koenig told me a long time
 > ago it is legal, but that was in the 1.2 darkages, in which I still live :-))

G++ (the version I used to check the above example) does not require the
cast.  My cfront 2.0 hasn't arived for this machine yet so I can't test it,
but am under the impression that when you use a case you are only telling
the compiler that you know what you are doing.  If you specify

	b2 = (B2 *) generic;

you are telling the compiler that you know generic is a B2.  No adjustment
will be made and your code breaks.  The moral is *don't* use void* for
generics.

// marc
-- 
// {ames,decwrl,sun}!pacbell!dumbcat!marc
// pacbell!dumbcat!marc@lll-winken.llnl.gov

leo@atcmp.nl (Leo Willems) (03/28/90)

From article <574@atcmpe.atcmp.nl>, by leo@atcmp.nl (Leo  Willems):
> From article <152@dumbcat.UUCP>, by marc@dumbcat.UUCP (Marco S Hyman):

stuff deleted

>> Doesn't always work.  In the case of multiple inheritance pointer adjustment
>> void * a different type than was placed in the void pointer, things break.
> 
> Stuff deleted
> 
>> 	void * generic;
>> 	D something;
>> 	B1 * b1 = &something;
>> 	B2 * b2;
>> 
>> 	++b1->i1;
>> 	generic = b1;	// no adjustment
>> 	b2 = generic;	// no adjustment
> 
> Under C++ 1.2 the last sentence will give a "bad assigment type" error.
> As I understand it, assignment to void* must be casted in C++ (which
> is different in ANSI-C) thereby bringing type-information back.
> 
Oops, I spoke to soon: casting a B1* to B2* is nonsense (most of the time)

	b2 = (B2*) generic;	// most of the time wrong.

However, even if probably wrong, leaving out the cast will genete a compiler
error.

What I had in mind (and of which I think is legal in C++ 1.2 and 2.0):

class b{ ... };
class bd : b { .... };

	bd bd_o;

	void *g;
	b *bp;

	g = &bd_o;

	bp = (b*) g;	// not casting here will give an error

Sorry about that.


Leo Willems			Internet: leo@atcmp.nl
AT Computing			UUCP:     mcvax!hp4nl!kunivv1!atcmpe!leo
P. O. Box 1428				
6501 BK  Nijmegen		Phone:    +31-80-566880
The Netherlands			Fax:	  +31-80-555887
Leo.

rfg@ics.uci.edu (Ronald Guilmette) (03/28/90)

In article <153@dumbcat.UUCP> marc@dumbcat.UUCP (Marco S Hyman) writes:
>In article <574@atcmpe.atcmp.nl> leo@atcmp.nl (Leo  Willems) writes:
> > From article <152@dumbcat.UUCP>, by marc@dumbcat.UUCP (Marco S Hyman):
> > > 	void * generic;
> > > 	D something;
> > > 	B1 * b1 = &something;
> > > 	B2 * b2;
> > >
> > > 	++b1->i1;
> > > 	generic = b1;	// no adjustment
> > > 	b2 = generic;	// no adjustment
> >
> > If you apply a cast in the example does it work correct then?
> > (If it works, is it legal or just luck? Andrew Koenig told me a long time
> > ago it is legal, but that was in the 1.2 darkages, in which I still live :-))
>
>G++ (the version I used to check the above example) does not require the
>cast.  My cfront 2.0 hasn't arived for this machine yet so I can't test it,

g++ started out as a C compiler, and got changed into a C++ compiler later.
You can still find occasional reminders of its lineage in places, like
for instance in the fact that the type-checking & type-conversions
have not been "hardened" to cfront standards yet.  (Some people may
prefer the looser typing that g++ provides.)

g++ still allows you to assign a void* to other types of pointer variables.

Cfront doesn't allow this without explicit casting.

>... The moral is *don't* use void* for generics.

I concur.


// Ron Guilmette (rfg@ics.uci.edu)
// C++ Entomologist
// Motto:  If it sticks, force it.  If it breaks, it needed replacing anyway.