[comp.lang.c++] Questions about C++

mikem@otc.OZ (Mike Mowbray) (06/09/87)

Since the article by dcw@doc.ic.ac.uk (Duncan C White) is likely to make
others unnecessarily wary of C++, I decided to reply here, rather than by
direct e-mail.

In article <452@ivax.doc.ic.ac.uk>, dcw@doc.ic.ac.uk (Duncan C White) says:
> 
> Question 1:	There seems to be an ambiguity in the Book which could cause
> ----------	a lot of confusion if not cleared up:
> 
> For example, given the following [an extract from 'class hierarchies' S:7.5.2]
> 
> 	class employee
> 	class manager        : employee
> 	class director       : manager
> 	class vice_president : manager
> 	class president      : vice_president
> 
> there are several SUBCLASSES of employee:
> 
> 	manager, director, vice_president and president
> 
> whereas manager is the ONLY IMMEDIATE SUBCLASS of employee.
> [In general, however, there may be several IMMEDIATE SUBCLASSES:
> director AND vice_president are BOTH immediate subclasses of manager]
> 
> Which of these terms corresponds to 'derived class' ?

In C++, manager, director, vice_president and president would all be said
to be "derived from employee". "Derivation" is a generic term indicating
that properties of one class are inherited from another, whether or not
the ancestor is immediate.

> Similarly, there is ONLY one IMMEDIATE SUPERCLASS of director [manager] :
> does this correspond to the base class definition ?  Or is employee a base
> class of director, as well ?

Yes (the latter).

> A Basic Difference :
> ------------------
>			[ ... deleted ... ]
> 
> We had assumed that the basic facilities SIMULA and C++ provide would be the
> same : after all, the Introduction says that C++ was designed [ via C with
> classes ] as an alternative to SIMULA, but less expensive at run-time.
> 
> However, SIMULA allows the following:
> 
>>	class vehicle ...		// class defns
>>	class car : vehicle ...
>>	class fourwheeler : car ...
>>
>>	ref( vehicle ) v;
> 
> NOTE : in SIMULA, all class variables are declared and initialised separately
> so in this example, 'v' is declared as a reference to a vehicle, [reference
> appears to be the same term as in C++].  v has not been initialised yet.
> The C++ style declaration 'vehicle v' is meaningless in SIMULA.
> 
> Clearly, then, v could be initialised [ SIMULA actually uses ':-' as a special
> assignment operator for refs ] by the following:
> 
>>	v = new vehicle;
> 
> However, SIMULA allows an important 'other' style of initialization :
> 
>>	v = new car;
> 
> which initialises v, but not to a vehicle-instance: to a car-instance [ car
> must be a subclass of vehicle ]
> 
> C++ objects [vehemently] to this kind of use : probably because it has many
> implications for run-time behaviour.

No. C++ is excellent for doing this sort of thing, but requires a bit of
practice. E.g:

    #include <stream.h>

    class Vehicle {
	public:
	    virtual int  wheels()	{ return 0; }	// default
    };

    // Every vehicle is assumed to have wheels, hence the wheels() member
    // function is virtual.

    class Car : public Vehicle {
	public:
	    int  wheels()		{ return 4; }	// Cars have four wheels
    };

    class ThreeWheeler : public Car {
	public:
	    int  wheels()		{ return 3; }   // 3-wheeler cars have
							// three wheels.
    };

    // The following function has no knowledge of exactly what kind of Vehicle
    // is being passed to it. It only knows that given a Vehicle, you can
    // find how many wheels it has by calling the member function wheels()

    void showwheels(Vehicle &v, char *s)
    {
	cout << s << " has " << v.wheels() << " wheels\n";
    }

    // Create some instances, and print them out:

    main()
    {
	Vehicle v;				// a real Vehicle

	Vehicle *vc = new Car;			// pointer to Vehicle that
						// is made to point to an
						// instance of Car. (The "new"
						// operator returns a pointer
						// to an instance of the class 
						// type.)

	Vehicle &vcr = *(new Car);		// reference to Vehicle that
						// is initialised to refer to
						// an instance of Car. Note
						// that de-referencing of the
						// pointer returned by "new"
						// is necessary.

	Vehicle *vtw = new ThreeWheeler;	// pointer to Vehicle, made
						// to point to an instance of
						// ThreeWheeler.

	Car c;					// A real Car.

	// Now show how many wheels are in each:

	showwheels(v, "v");
	showwheels(*vc, "vc");
	showwheels(vcr, "vcr");
	showwheels(*vtw, "vtw");
	showwheels(c, "c");
    }

When this is run, you get the following output:

	v has 0 wheels
	vc has 4 wheels
	vtw has 3 wheels
	c has 4 wheels

QED.

> This basic problem comes to light again and again :

Yes, it is a common need. C++ addresses it very well.

> o	You can't declare a function which takes a parameter of type vehicle,
> 	and then call it with an expression which is a car-instance.

Yes you can. The thing is that the base classes have to be public. In the
above example, if we had written:

	class Car : Vehicle {
		// ....
	};

it wouldn't have worked, because this declaration says that Car inherits all
the properties of Vehicle, but doesn't show the public properties of Vehicle
to the outside world, hence a Car could not, in this case, be used where
a reference to Vehicle was expected. Changing it to

	class Car : public Vehicle {
		// ...
	};

fixes this.

> o	You can't define a 'list of vehicles' into which you can put any
> 	subclass-instances [ cars, planes, boats, pogo-sticks, minis, fords,
> 	aston martins etc.. ]

Yes, you can. The only criterion is that the subclass-instances must have
vehicle as a public base class. I have written general linked list
classes, general hashtable classes, general binary tree classes, etc,
by making use of this facility.

> o	Associated operators like 'in' and 'qua' [ see below ] aren't available

These are not really needed. You achieve the same result in other ways. See
below.

> It seems to us, that the omission of such facilities in C++ is a major
> obstacle to 'object-orientated' programming : the usual SIMULA approach no
> longer works.  Why the hell can't a list of vehicles contain some cars, some
> planes, some horse-drawn carriages, and some pogo-sticks ??

Fortunately, these facilities are NOT omitted from C++. Indeed, it is these
features that make C++ an excellent language.

> Question 2:	Is this omission as serious as it appears ?  Can the same
> ----------	facilities be provided another way, or is there a separate
> 		"C++ programming metaphor" which entails a different way
> 		of analysing problems so that the missing features are never
> 		needed ??

Since it is not an omission, this question need not now be answered.

> NOTE: The Book seems to suggest two related techniques :
> 
> 	generic vectors [ S:1.16 ]
> 
> which suggests writing macros to 'build' class definitions for specific
> subclass lists [ eg, declare( vector, int ) ]

Personally I don't really like this method, as macros are harder to work with.
Nevertheless, the technique described is quite okay.

> However, this builds an 'integer-vector' class definition, rather than a
> 'vector of things which are any subclasses of integer'

You can't have things which are derived from built-in types. It is
questionable whether this should be the case. However, I've never felt
an urgent need for it.

> 	polymorphic vectors [ S:17 ]
> 
> However, this technique seems to rely on 'void *' to contain a pointer to
> the elements: surely this completely defeats the type-checking scheme ?

Yes. However, there's nothing to stop the programmer writing a generic
vector which had pointers to a suitable base class. Then the type-checking
would be improved, though still not complete. This is done along the lines
given in the book.

> Explanation of 'in' and 'qua' :
> -----------------------------
> 
> 1). The boolean SIMULA function :
> 
> 	i in c
> 
> returns true if the instance, i, is an instance of the given class, c,
> or if i is an instance of any subclass of c [ false otherwise ]
> 
> For example:
> 
> 	a 'car' instance is in 'car'
> 	a 'car' instance is ALSO in 'vehicle'
> 	a 'fourwheeler' instance is in 'fourwheeler', 'car' and 'vehicle'

Interesting. I've never felt the need to use such a feature. By structuring
the inheritance hierarchies properly, this can be avoided, I think.

> 2). The polymorphic SIMULA function :
> 
> 	i qua c
> 
> "qualifies" the instance, i, as a class, c. [ more explanation below example ]
> 
> For example: given the following:
> 
>>	ref( vehicle ) v;
>>	v = new car;		// vehicle v is a car: C++ would already object
> 
> The following use, despite actually being logically correct, fails to compile :
> 
>>	print v.wheels;		// attempt to print the number of wheels
>>				// that the car-instance v has.
> 
> whereas this is ok:
> 
>>	print (v qua car).wheels;

If we had:
	class Car : Vehicle {
	    public:
		int wheels()	{ return 4; }
		int refuel();
	};

then the following could be done:

	Vehicle *v = (Vehicle*) new Car;

	((Car*)v)->refuel();

Note that explicit casting is necessary to initialise v, since here Car does
not have Vehicle as a public base class. Similarly, to access Car::fuel, a
further explicit cast is required. Needless to say, such constructions are
an indication of poor structure, and should be avoided as much as possible.

> Note: the semantics of qua are a strange mixture of compile-time and run-time 
> events.  At compile-time, v must be an instance of a car, or an instance of a
> superclass of car, otherwise a compile time error is generated.
> However, at run-time, v is checked to see that it actually is 'in' car, and
> a fatal run-time error is generated if not.

C++ does not use any features that require storing information of what types
a thing is derived from. This is for reasons of efficiency, and to adhere to
the basic philosophy that if something doesn't use a feature, it should not
have to pay for it.

> Finding the Immediate Superclass:
> --------------------------------
>	[ ... deleted ... ]
> 
> Question 3 :	Can anyone think of a nicer way to implement in ?
> ----------	We tried :

I would try to re-structure my design so that it was not necessary.
Such things are very inefficient.

> 
>>typedef int boolean ;
>>
>>class universal_class {
>>	static char *name = "universal_class";
> 
> This, of course, caused us to run straight into the
> 
> 	'initialization of static class members'
> 
> problem discussed a few articles ago..  ( of course, we didn't read that
> article until we had wasted several hours trying various increasingly
> desperate and mucky methods ... :-)

If you really, really, want to do this, the following is a possibility:

	class Universal_Class {
		static char *name;
	    public:
		Universal_Class();

		char const *nm()	{ return name; }
	};

	Universal_Class::Universal_Class()	// constructor
	{
	    if (name == NULL)
		name = "Universal_Class";
	}

This relies on the fact that static things start off 0. The "nm" member is
there so that anything can read Universal_Class::name safely.

> Conclusion:
> ----------
> 
> There are many aspects in C++ which we like [ operator overloading and true
> reference variables especially ] but it does seem to be, in some sense, a
> "weaker kind" of class-based language than SIMULA.

I think C++ classes are VERY powerful, but like any new thing they do
require the programmer to both read the book carefully, and get some
experience.

This is not to say the C++ is the final answer. For example, adding features
such as multiple inheritance, parametrized classes, exception handling, etc,
would be good. However, C++ is designed for high efficiency and these
features take time to develop under such circumstances. I find it interesting
how so much has already been added to C, which is considered quite a stable
language, without introducing serious backward incompatibility. The ongoing
enhancement of C++ seems to prove that it IS possible for a language to be
both reliable and stable, but also evolving.

			Mike Mowbray
			Systems Development
			Overseas Telecommunications Commission (Australia)

UUCP:   {seismo,mcvax}!otc.oz!mikem              ACSnet: mikem@otc.oz

west@calgary.UUCP (Darrin West) (06/10/87)

In article <125@otc.OZ>, mikem@otc.OZ (Mike Mowbray) writes:
> Since the article by dcw@doc.ic.ac.uk (Duncan C White) is likely to make
> others unnecessarily wary of C++, I decided to reply here, rather than by
> direct e-mail.
> 
> In article <452@ivax.doc.ic.ac.uk>, dcw@doc.ic.ac.uk (Duncan C White) says:
> > [stuff - omitted]

which was replied to, admirably, and politely.

> 
> > o	Associated operators like 'in' and 'qua' [ see below ] aren't available
> 
> These are not really needed. You achieve the same result in other ways. See
> below.

I don't think you can make this blanket statement.  There are a lot of
simulationists who would not like to live without it. :-) ?

> > > Explanation of 'in' and 'qua' :
> > -----------------------------
> > 
> > 1). The boolean SIMULA function :
> > 
> > 	i in c
> > 
> > returns true if the instance, i, is an instance of the given class, c,
> > or if i is an instance of any subclass of c [ false otherwise ]
> > 
> > For example:
> > 
> > 	a 'car' instance is in 'car'
> > 	a 'car' instance is ALSO in 'vehicle'
> > 	a 'fourwheeler' instance is in 'fourwheeler', 'car' and 'vehicle'
> 
> Interesting. I've never felt the need to use such a feature. By structuring
> the inheritance hierarchies properly, this can be avoided, I think.

This is probably the best C++ solution to the problem: use what is available!
Otherwise you might end up in a situation like "running a C program on
an interpreter written in lisp".  Each language has its forte's (how DO you
make an egrave [or whatever]), but forcing either one to do what the
other is good at may lead to problems.

> 
> > 2). The polymorphic SIMULA function :
> > 
> > 	i qua c
> > 
> > "qualifies" the instance, i, as a class, c.
> > 
> > For example: given the following:
> > 
> >>	ref( vehicle ) v;
> >>	v = new car;		//vehicle v is a car: C++ would already object
> > 
> > The following use, fails to compile :
> > 
> >>	print v.wheels;		// attempt to print the number of wheels
> >>				// that the car-instance v has.
> > 
> > whereas this is ok:
> > 
> >>	print (v qua car).wheels;
> 
> If we had:
> 	class Car : Vehicle {
> 	    public:
> 		int wheels()	{ return 4; }
> 		int refuel();
> 	};
> 
> then the following could be done:
> 
> 	Vehicle *v = (Vehicle*) new Car;
> 
> 	((Car*)v)->refuel();
> 
> Note that explicit casting is necessary to initialise v, since here Car does
> not have Vehicle as a public base class. Similarly, to access Car::fuel, a
> further explicit cast is required. Needless to say, such constructions are
> an indication of poor structure, and should be avoided as much as possible.
> 
[Reformatted without permission:]
> > Note: the semantics of qua are a strange mixture of compile-time and 
> > run-time events.  At compile-time, v must be an instance of a car, or an
> > instance of a superclass of car, otherwise a compile time error is
> > generated.  However, at run-time, v is checked to see that it actually
> > is 'in' car, and a fatal run-time error is generated if not.

This is the crux of the issue.  And can be easily dealt with, if you
want to spent valuable run time doing it.  I rewrote class simset of simula
in C++, and ran across the famous check to see if the next linkage element
was a link, or a head (ie have we reached the end of the circular list?

The following was the solution I came up with.  I can't say that I totally
understood the simula code I was copying, but attempted to do a "one to
one" conversion as best I could.

The point of interest is the "in_link" virtual function.  It is redifined
to return 1 only in link.  This allows a run time check of any linkage
object to determine its type (sort of).  A different way to do this (and
I suspect that this is what simula actually does) would be to have a
member variable of linkage (say "int type") be set to a unique value
in each different derivation of linkage.  So link would set "type" equal
to one, and head would set "type" equal to 2 in their constructors.  This
would be slightly faster than dereferencing the virtual function pointers,
but does cost at run time (and development time).

My personal preference is to find a different way to do the same thing.
Since C++ does not provide this facility automatically (qua), why not use
your imagination, and come up with a viable alternative.  For example
have a head object which points to the head and tail, and don't use
a circular list (ie mark the end of the list with NULL in the same way
that simula marks the end of the list with a head object).

simset.h

#ifndef NULL
#define NULL 0
#endif

class link;
class head;

class linkage {
public:
    linkage *SUC,*PRED;
    virtual int in_link() {return(0);}
	/* a better way to do this might be with a flag variable
	   which is set to true only in a "link"'s constructor */
    link* suc();
    link* pred();
    
};

class link : public linkage {
public:
    int in_link() {return(1);}

    void out();
    void follow(linkage *);
    void precede(linkage *);
    void into(head *);
};

class head : public linkage{
public:
    link* first();
    link* last();

    int empty() {return(SUC == this);}
    int cardinal();
    void clear();

    head();	/* Constructor, run upon creation of new "head" */
};

simset.h

#include "simset.h"

link* linkage.suc()
{
    if (SUC->in_link()) return((link*)SUC);
    else return(NULL);	/* pointing back at head */
}

link* linkage.pred()
{
    if (PRED->in_link()) return((link*)PRED);
    else return(NULL);	/* pointing back at head */
}



void link.out()
{
    if (SUC != NULL){
	SUC->PRED = PRED;
	PRED->SUC = SUC;	/* can PRED be NULL? */
	SUC = PRED = NULL;
    }
}

void link.follow(linkage *X)
{
    out();	/* remove from other sets */
    if (X != NULL){
	if (X->SUC != NULL) {	/* can be combined with an && */
	    PRED = X;
	    SUC = X->SUC;
	    SUC->PRED = X->SUC = this;
	}
    }
}

void link.precede(linkage *X)
{
    out();
    if (X != NULL){
	if (X->SUC != NULL){	/* can be combined with an && */
	    SUC = X;
	    PRED = X->PRED;
	    PRED->SUC = X->PRED = this;
	}
    }
}

void link.into(head *S)
{
    precede(S);
}



head.head()
{
    /* run upon construction of an object of type head */
    SUC = PRED = this;
}

link* head.first()
{
    return(suc());
}

link* head.last()
{
    return(pred());
}

int head.cardinal()
{
    int i = 0;
    linkage *x;

    x = this->suc();
    while (x != NULL){
	i++;
	x = x->suc();
    }

    return(i);
}

void head.clear()
{
    link *x;

    x = first();
    while(x != NULL) {
	x->out();
	x = first();
    }
}

and a test program.


#include <stdio.h>
#include "simset.h"

class person : public link{
public:
    virtual void print() { printf("person\n");}
};

class male : public person{
public:
    virtual void print() { printf("male\n");}
};

class female : public person{
public:
    virtual void print() { printf("female\n");}
};

main()
{
    int i;
    head *h;
    person *t;

    h = new head;

    for (i = 0; i< 10; i++) {
	(new male)->into(h);
	(new female)->into(h);
	(new person)->into(h);
    }

    printf("card: %d\n",h->cardinal());

    while (!h->empty()){
	t = (person*)h->first();
	t->print();
	t->out();
    }
}




> 
> C++ does not use any features that require storing information of what types
> a thing is derived from. This is for reasons of efficiency, and to adhere to
> the basic philosophy that if something doesn't use a feature, it should not
> have to pay for it.
> 
> > Finding the Immediate Superclass:
> > --------------------------------
> >	[ ... deleted ... ]
> > 
> > Question 3 :	Can anyone think of a nicer way to implement in ?
> > ----------	We tried :
> 
> I would try to re-structure my design so that it was not necessary.
> Such things are very inefficient.
> 
> > 
> >>typedef int boolean ;
> >>
> >>class universal_class {
> >>	static char *name = "universal_class";

Were you honestly considering using strcmp to tell you which class you
had found?  You might want to make "name" or "Name" a virtual function
that is redifined upon further derivation.

C++ gives me more programming power combined with efficiecy at run
time than any other language I have run across.  I am using it
to write my thesis project, which is a Time Warp emulator.  (ie a study
of an optimistic synchronisation mechanism which is very useful for
distributing a simulation.  See "Virtual Time" - David Jefferson ACM '85?)
It (C++) has turned out to be quite handy!

-- 
Darrin West, Master's Unit (read: student).	..![ubc-vision,ihnp4]!
Department of Computer Science			alberta!calgary!west
University of Calgary.				Can you say '88 Winter Games?
Brain fault (cortex dumped)

dcw@icdoc.UUCP (06/12/87)

Thanks for the many messages we received about our questions about C++
versus SIMULA.  This article, and the following two, are a summary of what
we have learned.
I'm afraid the third part [ in, super, qua and conclusion ] is again very
large...

NB: There may be some overlap with Mike Mowbray's response <125@otc.OZ> - I
did ask people not to respond via news...

----------------------------------------------------------------------------

Part1/3

Derived and base class defns		We asked whether 'derived class'
----------------------------		corresponded to 'ancestor' or only
					to 'immediate ancestor' : parent.

Paul Calder <calder@uluru.stanford.edu> explained :

>"Derived class" and "base class" are like your "SUBCLASS" and "SUPERCLASS",
>except that some rules apply only to "IMMEDIATE" relatives.  For example -
>
>    "A pointer to a class may be converted to a pointer to a public
>    base class (read "any of the SUPERCLASSES") of that class"
>							Section 6.7
>
>    "... the public members of the base class (read "IMMEDIATE SUPERCLASS")
>    are public for the derived class ..."
>							Section 8.5.9, para 2
>
>In general, The Book seems to be fairly clear, using expressions like "a
>base class" where any will do, and "the base class" where only the immediate
>parent is meant.  I find that the semantics are usually intuitive.

This shows that there is a certain amount of ambiguity, but it seems to be a
fair summary, so let us define :

o	a base class      == superclass
o	THE base class    == immediate superclass 
o	a derived class   == subclass 
o	THE derived class == immediate subclass

Andrew Koenig, AT&T Bell Labs <ark@alice.uucp> and David Wonnacott, AT&T
Corporate Education <davew@whuxm.uucp> came to the same conclusion.

dcw@icdoc.UUCP (06/12/87)

Summary, part 2/3:


Our 'Basic Difference'		We thought that SIMULA allows a subclass
----------------------		instance to be assigned to a superclass
				variable, and that C++ doesn't.

Many people pointed out that THIS IS DEAD WRONG :  [ ooooppps... ]

In SIMULA, the ONLY way of declaring an instance is:

	ref( fred ) x;		// x is an instance of fred, which is a
				// class declared somewhere above

Our mistake was that we assumed that C++, like SIMULA, had ONLY one way to
define instances :

	fred x;			// instance of fred

whereas, of course, an instance can also be declared using :

	fred *xptr;		// not initialised yet: like SIMULA ref
or:
	fred& xref = x;		// must have some initialization with it,
				// except when its a function parameter

So, C++ is quite happy with :

	vehicle *v = new car;
and
	car c;
	vehicle &v = c;

This, of course, means that you can, after all:

>...declare a function which takes a parameter of type vehicle *,
>and then call it with an expression which is a car *.
>
>...define a 'list of vehicles' into which you can put any
>subclass-instances [ cars, planes, boats, pogo-sticks, minis, fords,
>aston martins etc.. ]

Several people commented that C++ would indeed be a weak form of object
orientated language if it couldn't handle this kind of thing: this is
precisely what we thought [ and had trouble believing ].

NB: Another common problem was mentioned by a few people: if any member of
the class hierarchy was not derived publicly from its immediate superclass,
even the pointer conversions would not work...

For example:

class vehicle ..
class car : vehicle ..		// if 'public' omitted, then
vehicle *v = new car;		// this declaration is rejected, apparently

dcw@icdoc.UUCP (06/12/87)

*** WARNING: 300 LINE ARTICLE... HIT j IF YOU'RE NOT INTERESTED...

Summary, part 3/3:


'IN', 'QUA' and 'SUPER':	We found that several important
-----------------------		operators aren't available in C++.

a). SUPER

Paul Calder <calder@uluru.stanford.edu> said :

>It is mildly inconvenient that C++ does not provide COMPILE-TIME access to
>a derived class's base class.  We have found that there are a number of
>situations where a virtual function calls the corresponding function in its
>base class ....    It would be convenient to specify this as base::

I'm not sure about 'compile-time only'.. but even that would be a help.

Jacob Gore <gore@nucsrl.uucp> Northwestern University, Computer Science
Research Lab, [ Chicago? ] sent us a fascinating letter about Objective-C.
He says:

>From my experience, 'super' is very useful.  Most often, it is used to envoke
>the 'default action', as specified by the superclass, after performing the
>action specific to this class.  A method 'doSomething' in class C may perform
>some action, and then call the method 'doSomething' in its superclass.  Of
>course, if the superclass' name is known, it can be used in place of 'super',
>but (a) the specific name is not always known, (b) using the specific name 
>hinders reusability, and (c) using 'super' all the time is more convenient, as
>well as more obvious.

Jonathan Shopiro, AT&T Bell Labs, however, suggested that super was unnecessary,
saying:

>... just as you almost never have
>to determine the class of an object in Smalltalk, you never need it in
>C++ either.

To summarise, most people felt the need for some form of 'super', although
this was not unamimous.


b). QUA

Thomas Maslen <maslen@rocky.stanford.edu> suggested:

>Type-casting can be used for the "treat it as an X" part of qua.

Note, however, that SIMULA would give a compile-time error if the instance
belongs to an entirely different hierarchy, whereas C++ will let it straight
through.

David Wonnacott <davew@whuxm.uucp> also suggested type-casting, giving the
example:

>vehicle *vp = new car;
>
>	((car *) vp) -> wheels();
>

And continued with:

>Note that, when you put in an explicit type cast, NO type checking is done.
>It is therefore up to the programmer to remember that vp does, in fact, point
>to a car..  If vp pointed to a boat (no function called wheels()), strange
>things (not an error) would happen at run time.

This is an important point: in both languages, it is up to the programmer to
ensure that a vehicle-instance is actually a car before applying QUA [or type
casts].  However, at least SIMULA will give an immediate run-time error,
whereas C++ will carry on regardless...

c). IN

Bjarne Stroustrup suggested :

>A piece of advice: If you can avoid it, don't even simulate IN. Most
>often you don't need it. I consider INSPECT and QUA efficiency hacks
>in Simula. In C++ you can most often dispense with them in favor of
>using virtual functions.

>If you must, and in general, you access a member of a base class
>(immediate or not) by prefixing the member name by its class name:

David Wonnacott <davew@whuxm.uucp> says, of the IN operator:

>C++ does not provide any facility for this, as it would involve a
>storage overhead.  As I understand it, the only place where C++ adds any
>storage or run-time calculation is with virtual functions.
>
>There are, however, many times when you would like to find out what class
>an object is, or determine whether it is derived from a given class.  This
>can be accomplished by creating a base class (traditionally called "object"),
>and deriving all other classes from it.  class object an then have member
>functions equivalent to "in".  Writing such a class is pretty hard, and I
>suggest that you look around for a public domain C++ library that provides
>such facilities (I know one exists, you might try asking on the net).

I'm very interested in such a PD C++ library...  any comment, anyone ??

Several people suggested ways of implementing a restricted, 'IMMEDIATE IN'
which we will call 'isa' :

Andrew Koenig <ark@alice.uucp> made an excellent suggestion:

>So far, we have had to rely on our own devices to determine what
>kind of individual is designated by an employee pointer.  We would
>really like the implementation to keep track of that for us.  For
>example, we might like to have a string called, say "title" that
>represents the rank of the employee.
>
>	class employee: {
>		virtual char* title() { return "employee"; }
>		// other stuff
>	};
>
>	class manager: employee {
>		char* title() { return "manager"; }
>		// other stuff
>	};
>
>... ditto for director
>
>... Now, if we have an employee pointer ep, we can say:
>
>	printf ("%s\n", ep->title());
>
>and it will print the the actual employee title.

Andrew's scheme works very nicely: add to employee the following function:
[not virtual, not overloaded: ONLY in employee]

	boolean isa( char *p )
	{
		return strcmp( this->title(), p ) == 0;
	}

and then all subclasses of employee can use it.  Some examples:

	void test( char *s, boolean b )
	{
		cout << s << ": " << ( b?"true\n":"false\n" ) ;
	}

	employee e;
	employee *eptr = new manager;
	manager *mptr = new manager;
	
	test( "e isa employee"   , e.isa("employee") );
	test( "e isa manager"    , e.isa("manager") );
	test( "eptr isa employee", eptr->isa("employee") );
	test( "eptr isa manager:", eptr->isa("manager") );
	test( "mptr isa employee", mptr->isa("employee") );
	test( "mptr isa manager" , mptr->isa("manager") );
	.... and many other similar tests one can think of..

Paul Calder <calder@uluru.stanford.edu> suggested a similar scheme.

Jacob Gore <gore@nucsrl.uucp> says that Objective-C provides:

>	[x isMemberOf: C] iff x is an instance of class C
>	[x isKindOf: C] iff x is an instance of C' and C' is a subclass of C
>		(also, if [x isMemberOf: C] then [x isKindOf: C]).

From this description, isMemberOf is 'isa' and isKindOf is 'in'.

It sounds like Objective-C has more run-time support than C++ [comments
welcome]   It sounds like a nice language: where can we get it, anyone ?


CONCLUSION:
----------

I think the best summary comes from David Wonnacott <davew@whuxm.uucp>:

>without the ability of the compiler to use a derived class pointer
>when a base class pointer is needed, or to use a derived class object to
>initialize a base class reference, C++ would indeed provide very weak
>object - oriented facilities.  It does lack important functions like "in"
>and "super", but they incur a storage and time overhead, so they are left
>for the user to implement where needed.  They are extremely hard to implement
>(as you learned whey you tried), so you should look for a library that
>provides them.

Bjarne Stroustrup commented, of direct relevance to our requirement for C++ :

>Are you aware of the task library as a C++ alternative to the
>Simula class SIMULATION?

Susan Coatney <coatney@venera.isi.edu> elaborated :

>[the task library] is a package developed for discrete-event simulation
>utilizing co-routine style multitasking.

We weren't aware of the task library: the only information we had about C++
was the Book itself.  Obviously, it is not the authorative source we had
hoped (semi :-)

We will obviously have to seek out whatever documentation came with the
C++ sources [ we are running 1.1, and have 1.2 awaiting installation - I
tried to install it on SUN 3/50s and BSD4.3, but failed miserably ]

Bjarne's final suggestion was:

>Have you seen my overview of C++ in SIGPLAN Notices Oct'86. Many people
>find it a better introduction to the concepts in C++ than the book
>itself.

Now we know about it, we will certainly look it up.

Finally, we are very interested in any further [private or public]
communication about the suitability of C++ for simulation.

	Thanks to everyone who responded for all the help...
	our apologies for the obvious boo-boo.
			-- Duncan White & Phil Hands.

-----------------------------------------------------------------------------
JANET address : dcw@uk.ac.ic.doc| Snail Mail :  Duncan White,
--------------------------------|               Dept of Computing,
  This space intentionally      |               Imperial College,
  left blank......              |               180 Queen's Gate,
  (paradoxical excerpt from     |               South Kensington,
  IBM manuals)                  |               London SW7
----------------------------------------------------------------------------
Tel: UK 01-589-5111 x 4982/4991
----------------------------------------------------------------------------

mat@mtx5a.UUCP (m.terribile) (06/14/87)

This is one more voice in a large discussion.

> > However, SIMULA allows an important 'other' style of initialization :
> > 
> >>	v = new car;
> > 
> > which initialises v, but not to a vehicle-instance: to a car-instance [ car
> > must be a subclass of vehicle ]
> > 
> > C++ objects [vehemently] to this kind of use : probably because it has many
> > implications for run-time behaviour.
> 
> No. C++ is excellent for doing this sort of thing, but requires a bit of
> practice. E.g:

As noted, the use of an object of class *derived* in place of an object of
type *base* is allowed (so long as the base class is public) and is one of
C++'s main OOP features.  And yes, there *are* many implications for runtime
behaviour and they are all good.  Because of the static typing structure
(more on this later) there are no runtime checks necessary and the runtime
costs are quite well contained.  See the last couple paragraphs of 7.2.8
in The Book (first edition.)

> > o	Associated operators like 'in' and 'qua' [ see below ] aren't available
> 
> ...
> 
> > Explanation of 'in' and 'qua' :
> > -----------------------------
> > 
> > 1). The boolean SIMULA function :
> > 
> > 	i in c
> > 
> > returns true if the instance, i, is an instance of the given class, c,
> > or if i is an instance of any subclass of c [ false otherwise ]

In C++, this information is known at compile time.  If the object represented
by *i* is not of a type derived from type *c*, you simply cannot use it as
such.  Excplicit type coercion (casting) overrides this limitation.  This
also protects you from attempting to use a method that will be unavailable
at runtime -- a very desirable property for real-world software.

> > whereas this is ok:
> > 
> >>	print (v qua car).wheels;
> 
> If we had:
> 	class Car : Vehicle {
> 	    public:
> 		int wheels()	{ return 4; }
> 		int refuel();
> 	};
> 
> then the following could be done:
> 
> 	Vehicle *v = (Vehicle*) new Car;
> 
> 	((Car*)v)->refuel();
> 
> Note that explicit casting is necessary to initialise v, since here Car does
> not have Vehicle as a public base class. Similarly, to access Car::fuel, a
> further explicit cast is required. Needless to say, such constructions are
> an indication of poor structure, and should be avoided as much as possible.

Indeed.  If we expect to ask how many wheels a vehicle has, the number of
wheels ought to be a property of vehicles.

> C++ does not use any features that require storing information of what types
> a thing is derived from. This is for reasons of efficiency, and to adhere to
> the basic philosophy that if something doesn't use a feature, it should not
> have to pay for it.

And the static-type structure has advantages for non-academic software, as
noted above.

> This is not to say the C++ is the final answer. For example, adding features
> such as multiple inheritance, parametrized classes, exception handling, etc,
> would be good. However, C++ is designed for high efficiency and these
> features take time to develop under such circumstances. I find it interesting
> how so much has already been added to C, which is considered quite a stable
> language, without introducing serious backward incompatibility. The ongoing
> enhancement of C++ seems to prove that it IS possible for a language to be
> both reliable and stable, but also evolving.

I've had the pleasure of meeting and chatting with Bjarne Stroustrup, and
reading a number of papers that he has written.  It seems clear to me from
these that we are, in many ways, pushing the standard compile-link-debug
environment very near its limits.  As you say, the evolution of C++ must
be slow if it is to be good.  At present, the limits on that evolution
seem to be 1) our ability to judge what is The Right Way To Do Something,
and what is a merely a booby-trapped shortcut, 2) our current programming
environments, and 3) the ability of one individual, no matter how gifted,
to get X, Y, and Z done and working in a given amount of time.

Would anyone care to examine #2 in a careful, stepwise way?
-- 

	from Mole End			Mark Terribile
		(scrape .. dig )	mtx5b!mat
					(Please mail to mtx5b!mat, NOT mtx5a!
						mat, or to mtx5a!mtx5b!mat)
					(mtx5b!mole-end!mat will also reach me)
    ,..      .,,       ,,,   ..,***_*.

mikem@otc.OZ (Mike Mowbray) (06/17/87)

In article <964@vaxb.calgary.UUCP>, west@calgary.UUCP (Darrin West) says:

>> > o	Associated operators like 'in' and 'qua' [ see below ] aren't available
>> 
>> These are not really needed. You achieve the same result in other ways. See
>> below.
> 
> I don't think you can make this blanket statement.  There are a lot of
> simulationists who would not like to live without it. :-) ?

Earlier in my C++ "career", I wondered about this very thing, thinking
it would be nice to know at run-time if an instance of class x was
actually a class something_else. The thing that leads me to make the above
"blanket statement" is that operators like in and qua are basically only
needed is to enable you to *do* things based on what they return.

I.e:  the crucial thing (I believe) is to shift one's design focus from
"what is xyz?" to "what do I want to do with xyz?" Then it becomes
clear that virtual functions are perfect for the job. Someone else
mentioned that it's not clear yet exactly what techniques are good
techniques. "Run-time explicit type info" versus "virtual functions"
are a classic case of this and I'm sure we haven't heard the last of it.

By the way, if you haven't read Bjarne's paper on multiple-inheritance
(delivered at EUUG in May I understand), get a copy somehow. It's
fascinating. Not only can you have multiple base classes in the obvious
sense, you can have virtual base classes (where the grandparent base
part of two separate parent base classes is a single entity in the
derived child class rather than two separate lots of grandparent).  You
can also derive (actually name-extend) from a pointer within the class
or outside it (great for getting rid of all those dummy little member
functions we all know and hate). I for one will be very interested to
play with it if/when it becomes part of commercial C++.

			Mike Mowbray
			Systems Development
			Overseas Telecommunications Commission (Australia)

UUCP:   {seismo,mcvax}!otc.oz!mikem              ACSnet: mikem@otc.oz

tomm@voodoo.UUCP (06/18/87)

In article <129@otc.OZ> mikem@otc.OZ (Mike Mowbray) writes:

[ stuff deleted -tomm- ]

>By the way, if you haven't read Bjarne's paper on multiple-inheritance
>(delivered at EUUG in May I understand), get a copy somehow.
	       ^^^^
This sounds very good.  What or who is EUUG, and where can I get a copy
of this paper?

Thanks in advance,

-- 
Tom Mackey                                (206) 342-1442 (wk)
Boeing Computer Services    ....uw-beaver!ssc-vax!voodoo!tomm
M/S 03-73,     P.O. Box 24346,     Seattle, WA     98124-0346

keith@nih-csl.UUCP (06/19/87)

In article <129@otc.OZ>, mikem@otc.OZ (Mike Mowbray) writes:
> In article <964@vaxb.calgary.UUCP>, west@calgary.UUCP (Darrin West) says:
> 
> >> > o	Associated operators like 'in' and 'qua' [ see below ] aren't available
> >> 
> >> These are not really needed. You achieve the same result in other ways. See
> >> below.
> > 
> > I don't think you can make this blanket statement.  There are a lot of
> > simulationists who would not like to live without it. :-) ?
> 
> Earlier in my C++ "career", I wondered about this very thing, thinking
> it would be nice to know at run-time if an instance of class x was
> actually a class something_else. The thing that leads me to make the above
> "blanket statement" is that operators like in and qua are basically only
> needed is to enable you to *do* things based on what they return.
> 

The Simula "in" operator is, I believe, like Smalltalk's "isKindOf"
method, i.e, it allows you to test an object to see if it is an
instance of a particular class or of a derived class.  IsKindOf is
useful for checking the type of argument objects to virtual functions,
even in a statically type checked language like C++.

Suppose you're implementing a Smalltalk-like class Dictionary.  Class
Dictionary is a derived class of Collection, which in turn is derived
from class Object:

	class Object {
	...
	};

	class Collection : public Object {
	...
	virtual void add(const Object&);  // add an Object to a Collection
	...
	};

	class Dictionary : public Collection {
	...
	virtual void add(const Object&);  // add an Association to a Dictionary
	...
	};

The problem is that class Dictionary should only accept instances of
class Association (which is a key object - value object pair) or of a
class derived from Association as arguments, so you should check the
class of the argument.  You can't do this just by defining
Dictionary::add(const Association&), because you can't overload a
virtual function in a derived class without also overloading it in the
base class.  This means that to use static type checking you'd have to
also define Collection::add(const Association&), which is undesirable
because you should be able to define new kinds of Collections without
having to modify class Collection itself.  So to make Dictionary::add
check its argument class you need to do it at run-time with isKindOf
(a.k.a. "in").

-- 
	Keith Gorlen			phone: (301) 496-5363
	Building 12A, Room 2017		uucp: seismo!elsie!nih-csl!keith
	National Institutes of Health
	Bethesda, MD 20892

mikem@otc.OZ (Mike Mowbray) (06/21/87)

In article <363@voodoo.UUCP>, tomm@voodoo.UUCP (Tom Mackey) says:

> >By the way, if you haven't read Bjarne's paper on multiple-inheritance
> >(delivered at EUUG in May I understand), get a copy somehow.
> 	         ^^^^
> This sounds very good.  What or who is EUUG, and where can I get a copy
> of this paper?

I presume EUUG is "European Unix-Users Group"? I don't know about
availability in the USA (which is why I said to get a copy "somehow"). I
believe it is (or will be) published in their proceedings. Perhaps
someone more familiar with EUUG can shed more light ... ?

By the way, it says very clearly in the abstract that multiple-inheritance
is not yet considered part of standard C++, so we'll have to be patient.

			Mike Mowbray
			Systems Development
			Overseas Telecommunications Commission (Australia)

UUCP:   {seismo,mcvax}!otc.oz!mikem              ACSnet: mikem@otc.oz

mikem@otc.OZ (Mike Mowbray) (06/21/87)

In article <226@nih-csl.UUCP>, keith@nih-csl.UUCP (keith gorlen) says:

> Suppose you're implementing a Smalltalk-like class Dictionary.  Class
> Dictionary is a derived class of Collection, which in turn is derived
> from class Object:
> 
> 	class Object {
> 	...
> 	};
> 
> 	class Collection : public Object {
> 	...
> 	virtual void add(const Object&);  // add an Object to a Collection
> 	...
> 	};
> 
> 	class Dictionary : public Collection {
> 	...
> 	virtual void add(const Object&);  // add an Association to a Dictionary
> 	...
> 	};
> 
> The problem is that class Dictionary should only accept instances of
> class Association (which is a key object - value object pair) or of a
> class derived from Association as arguments, so you should check the
> class of the argument.  You can't do this just by defining
> Dictionary::add(const Association&), because you can't overload a
> virtual function in a derived class without also overloading it in the
> base class.  This means that to use static type checking you'd have to
> also define Collection::add(const Association&), which is undesirable
> because you should be able to define new kinds of Collections without
> having to modify class Collection itself.  So to make Dictionary::add
> check its argument class you need to do it at run-time with isKindOf
> (a.k.a. "in").

Yes, this is annoying and I have indeed run into it myself.  However, I
not sure whether such designs are really a good idea or not.  (I know
lots of people do it, but that does not necessarily mean it's good
style.) My reservations are based on the fact that we are essentially
asking class Collection to do something beyond what normal Collections
do. I.e: we are passing an Object in and relying on the actual virtual
function to return an error or something if the Object was not an
Association. This might be okay, and it might not. I think it's too
early to really know. More real-world usage of object-oriented
techniques in critical high-performance applications with large
maintenance problems is required.

By the way, it IS possible to achieve a limited "isa" mechanism in
the current version of C++, for certain cases. It relies on knowledge
of what cfront generates, and therefore should be put into the same
category as the "object is first (hidden) argument" stuff. It can only
be used to check the type of things with virtual functions. (One
objection to adding this feature to C++ proper is the run-time
overhead. Where virtual functions already exist, there is already a
pointer which can in principle be used if you're desperate enough.) The
following program illustrates the idea. Use this at your own risk though.

// *********************** typetst.c *************************************
#include <stream.h>

#define  IS_TYPE(classtype,instance)  ((instance)._vptr == ::classtype\
__vtbl)
	    // "classtype" must have virtual functions.
	    // "instance" must be an instance of a class with virtual
	    // functions (or a reference thereto).
	    // If either is not the case, there'll be a compile-time error.
	    //  (either "_vptr: no such member"
	    //   or     "xxxxx__vtbl is undefined"
	    //   or something like that.)
//--------------------------------------------------------
class Object {
	virtual void func() { }
};

class Association : public Object {
	// ......
};

class Collection : public Object {
	// .....
    public:
	virtual int  add(Object&)	// return 0 if error.
			{ return 0; }
};

class Dictionary : public Collection {
	// ....
    public:
	int add(Object &obj)	// return 0 if arg not an Association
	    {
		if (IS_TYPE(Association,obj))
		{
		    // ... do whatever ...
		    return 1;
		}
		else
		    return 0;
	    }
};
//--------------------------------------------------------
main()
{
    Object	obj;
    Association assoc;

    Dictionary  dict;
    Collection *col = &dict;

    cout << col->add(obj) << "  " << col->add(assoc) << "\n";

    // should output:  0 1
}
// **** (end) ************************************************************

And it does indeed work, at least on my present version of cfront (1.2.1):

$ typetst
0 1
$

Note also, that IS_TYPE doesn't work for derived cases:

Association assoc;

if (IS_TYPE(Object,assoc))	// will yield 0.
 .....

To remedy this is harder since extra type information is needed. E.g: you
could have a table associated with each class which keeps track of all
its ancestors' vptrs. Each instance would need a pointer to the correct
table. The following is an example:

// ************* typetst.c ***********************************************
#include <stream.h>

typedef int  (**Type)(...);
//----------------------------------------------------------------------
class AncTypes { // Ancestor Types
	Type  *tbl;	// array, terminated by a NULL
	int    sz;

	int    sealed()		{ return sz < 0; }
	void   do_enrol(Type t);
    public:
	void   enrol(Type t, int s) { if (!sealed()) {
					do_enrol(t);
				        if (s)
					  sz = -sz;
				      }
				    }
	int    has(Type);
};

void AncTypes::do_enrol(Type tp)
{
    if (sz == 0) {
	tbl = new Type [sz=1];
	*tbl = tp;
    }
    else {
	// see if already there
	register Type *ceiling = tbl+sz;
	for (register Type *t = tbl; t < ceiling; ++t)
	    if (*t == tp)
		return;

	// not there, so enrol...
	Type *newtbl = new Type [++sz];
	Type *tmp = newtbl;
	for (t = tbl; t < ceiling; )
	    *tmp++ = *t++;
	*tmp = tp;
	delete tbl;
	tbl = newtbl;
    }
}

int  AncTypes::has(Type tp)
{
    for (register Type *t = tbl + (sz<0 ? -sz : sz) - 1; t >= tbl; --t)
	if (*t == tp)
	    return 1;
    return 0;
}
//----------------------------------------------------------------------
class Object {
	static AncTypes   _atypes;
        AncTypes        *_atptr;
    public:
	void		   init(AncTypes *at, AncTypes &ps)
			     {
			       if (at==NULL)
				 (at=&ps)->enrol((Type)_vptr,1);
			       else
				 at->enrol((Type)_vptr,0);
			       _atptr = at;
			     }

	Object(AncTypes *at=NULL)		{ init(at,_atypes); }

	virtual void dummy()	{ }

	AncTypes &atypes()	{ return *_atptr; }
};

#define  IS_KIND(classtype,instance)  ((instance).Object::atypes().has((Type)::classtype\
__vtbl))
//----------------------------------------------------------------------
	// Now some classes derived from Object. Note that the additional
	// complexity in the classes and constructors is quite minimal.

class Association : public Object {
	static AncTypes   _atypes;
	void  dummy()		{ }
	// ......
    public:
	Association(AncTypes *at=NULL) : (at ? at : &_atypes)
						{ init(at,_atypes); }
};

class Special_Assoc : public Association {
	static AncTypes   _atypes;
	// ......
    public:
	Special_Assoc(AncTypes *at=NULL) : (at ? at : &_atypes)
						{ init(at,_atypes); }
};

class Collection : public Object {
	static AncTypes   _atypes;
	// .....
    public:
	Collection(AncTypes *at=NULL) : (at ? at : &_atypes)
						{ init(at,_atypes); }

	virtual int  add(Object&)	// return 0 if error.
			{ return 0; }
};

class Dictionary : public Collection {
	static AncTypes   _atypes;
	// ....
    public:
	Dictionary(AncTypes *at=NULL) : (at ? at : &_atypes)
						{ init(at,_atypes); }

	int add(Object &obj)	// return 0 if arg not an Association
	    {
		if (IS_KIND(Association,obj))
		{
		    // ... do whatever ....
		    return 1;
		}
		else
		    return 0;
	    }
};
//----------------------------------------------------------------------
main()
{
    Association   assoc;

    Dictionary  dict;
    Collection *col = &dict;

    Special_Assoc spec1, spec2;
    Object	  obj;

    cout << col->add(obj)   << "\n";		// 0
    cout << col->add(assoc) << "\n";		// 1
    cout << col->add(spec1) << "\n";		// 1
    cout << col->add(spec2) << "\n";		// 1
    cout << col->add(dict)  << "\n";		// 0
}
// **** (end) ************************************************************

The way it works is that derived objects pass a non-null argument to
their Base constructor and this ultimately modifies the way the Object
constructor operates. When one instance of any class has been
constructed, a locking mechanism comes into play, which reduces the
amount of overhead associated with creating further instances of that
particular type of object. The mechanism above could obviously be made
a bit faster by using something more sophisticated than linear search.

Obviously, a compiler could make this nicer, but there's always
going to be lots of run-time overhead involved, so maybe it's better
left up to the user.

Needless to say, use of this sort of thing is for those with open
eyes.  There's a number of holes one could fall down since there's no
error checking. I daresay that it should always be possible to do this
sort of thing, unless cfront is modified to explicitly deny it (and
even then one could play silly-buggers with ordinary C functions to
achieve a similar result, I think).  As I said above though, I'm
ambivalent about whether this design style is, in principle, good or
bad.

		    Mike Mowbray
		    Systems Development
		    Overseas Telecommunications Commission (Australia)

UUCP:   {seismo,mcvax}!otc.oz!mikem              ACSnet: mikem@otc.oz

keld@diku.UUCP (Keld J|rn Simonsen) (06/21/87)

In article <363@voodoo.UUCP> tomm@voodoo.UUCP (Tom Mackey) writes:
>In article <129@otc.OZ> mikem@otc.OZ (Mike Mowbray) writes:

>[ stuff deleted -tomm- ]

>>By the way, if you haven't read Bjarne's paper on multiple-inheritance
>>(delivered at EUUG in May I understand), get a copy somehow.
>	       ^^^^
>This sounds very good.  What or who is EUUG, and where can I get a copy
>of this paper?

EUUG is the European UNIX systems User Group.

A copy of the proceedings of the EUUG Helsinki conference can be
obtained by writing:

EUUG secretary
Owles Hall, Buntingford
Herts, SG9 9PL
Great Britain

telephone +44 763 73039      Fax: +44 763 73255
Eunet: euug@inset.uucp

I am not sure of the price, but it should be in the neighbourhood
of USD 50,- for USENIX members.

Keld Simonsen, EUUG Conference Officer
>Thanks in advance,

>-- 
>Tom Mackey                                (206) 342-1442 (wk)
>Boeing Computer Services    ....uw-beaver!ssc-vax!voodoo!tomm
>M/S 03-73,     P.O. Box 24346,     Seattle, WA     98124-0346

bs@alice.UUCP (06/24/87)

 > We weren't aware of the task library: the only information we had about C++
 > was the Book itself.  Obviously, it is not the authorative source we had
 > hoped (semi :-)''

It is, I hope, authorative, but naturally cannot be complete if by complete
you mean ``contains a list of all usefull libraries of functions and classes''.
The task library isn't part of the language it is a library. It is documented
in the ``released notes'' that AT&T ships with the C++ tape.

We clearly need a way of making libraries known and available to people that
needs them. This is, however, a thorny administrative problem.

Good luck.

	- Bjarne

bs@alice.UUCP (06/24/87)

It has been suggested in this series of articles that C++ lacks IN and SUPER.

``IN'' (and INSPECT) was deriberately not included (after much consideration
of my experience with SIMULA) and can as pointed out can easily be provided
by a programmer for a SPECIFIC tree of classes (either in a type secure and
often reasonably efficient manner using a virtual function or in an optimally
efficient and insecure manner using a cast).

C++ certainly has an equivallent to the SMALLTALK ``SUPER'' (which SIMULA
unfortunately lacks):

	class B {
		// ...
	public:
		virtual print() { cout << "B"; }
	};

	class D : public B {
		// ...
	public:
		print() {
			cout << "D : ";	// do my stuff
			B::print();	// let my base class B do its stuff
		}
	};

	main()
	{
		D d;
		d.print();
		B* p = &d;
		p->print();
	}

will print ``D : B'' twice.

The reason that the notation ``name_of_base_class::'' is used rather than
``super'' is that this more explicit notation is general enough to cope with
the needs of multiple inheritance (multiple base classes):

	class A { /* ... */ print(); };
	class B { /* ... */ print(); };
	class C : A, B {
		// ...
		print() { /* ... */ A::print(); B::print(); }
	};

It should also be noted that most or all of the features of a ``more dynamic''
i.e. dynamically, but not statically, typed language can be provided as a
library in a statically typed language such as C++. Keith Gorlen's OOPS
library demonstrates that dramatically by providing the most fundamental
and useful non-graphical SMALLTALK classes for C++. (OOPS provides standard
``container classes'' such as dictonaries and sets, the ability to write an
object to disk and read it back again, SMALLTALK-like Is and IsKindOf, etc.).
The inverse operation, ``simulating'' a static type system for a language
that posesses only dynamic type checking is much harder (e.g. it is a popular
and very hard research problem to determine which part of a Smalltalk system
is used by a specific program - and in which way - to ensure completeness
and enable optimization).

keith@nih-csl.UUCP (keith gorlen) (06/24/87)

In article <131@otc.OZ>, mikem@otc.OZ (Mike Mowbray) writes:
> // *********************** typetst.c *************************************
> #include <stream.h>
> 
> #define  IS_TYPE(classtype,instance)  ((instance)._vptr == ::classtype\
> __vtbl)
> 	    // "classtype" must have virtual functions.
> 	    // "instance" must be an instance of a class with virtual
> 	    // functions (or a reference thereto).
> 	    // If either is not the case, there'll be a compile-time error.
> 	    //  (either "_vptr: no such member"
> 	    //   or     "xxxxx__vtbl is undefined"
> 	    //   or something like that.)
> //--------------------------------------------------------

This technique assumes that all instances of a class will have the
same vtbl address, which is not true unless you use the +e compiler
switch.  Without this switch, each .c file that #includes the
declarations of a class with virtual functions gets its own instance
of vtbl, and objects constructed by code in two different .c files
will have different vtbl addresses as a result, even though they may
belong to the same class.  Since using the +e switch is such a pain, I
wouldn't recommend this method.

> 
> Note also, that IS_TYPE doesn't work for derived cases:
> 
> Association assoc;
> 
> if (IS_TYPE(Object,assoc))	// will yield 0.
>  .....
> 
> To remedy this is harder since extra type information is needed. E.g: you
> could have a table associated with each class which keeps track of all
> its ancestors' vptrs. Each instance would need a pointer to the correct
> table. The following is an example:
> 

My OOPS class library uses instances of class Class to describe the
class hierarchy at run-time.  The programmer must declare one, static,
instance of class Class for each class he writes.  Each instance of
class Class contains a pointer to the instance of class Class that
describes the base class, the class name as a character string, the
size in bytes of instances of the class described, the address of a
function for reading instances of the class from a stream, and other
useful information.  Each class defines the virtual function const
Class* isA() to return the address of its Class descriptor instance.
Also, the (static) constructor for class Class adds all the Class
instances linked in the program into a Dictionary, keyed by the name
of the class.  This structure supports run-time type checking, object
I/O, object copying operations, object I/O (i.e., storeOn(ostream&)
and readFrom(istream&)), and allows class names to be printed in error
messages.  It has low overhead, and adds no additional member
variables to objects.  I've been using it for over a year, and am
satisfied with it, except that I wish I had named the class something
else!  (The whole idea was stolen from Smalltalk-80)
-- 
	Keith Gorlen			phone: (301) 496-5363
	Building 12A, Room 2017		uucp: seismo!elsie!nih-csl!keith
	National Institutes of Health
	Bethesda, MD 20892

mat@mtx5a.UUCP (m.terribile) (06/26/87)

> > ...
> > check its argument class you need to do it at run-time with isKindOf
> > (a.k.a. "in").
> 
> Yes, this is annoying and I have indeed run into it myself.  However, I
> not sure whether such designs are really a good idea or not.  (I know
> lots of people do it, but that does not necessarily mean it's good
> style.) My reservations are based on the fact that we are essentially
> asking class Collection to do something beyond what normal Collections
> do. ...  More real-world usage of object-oriented techniques in critical
> high-performance applications with large maintenance problems is
> required. ...   it IS possible to achieve a limited "isa" mechanism in
> the current version of C++, for certain cases. It relies on knowledge
> of what cfront generates,  ... (One objection to adding this feature to
> C++ proper is the run-time overhead.  ...
> pointer which can in principle be used if you're desperate enough.) The

If run-time overhead were an issue, the ``new'' and ``delete'' operators
would not have been included.  The issue is far more elemental.

Why do we want to use the ``is'' test?  Because we want to determine
something about the type structure.  That means that either

	1) we have encoded some information in the type structure in a
	   manner that is not a part of the language and that violates the
	   supported paradigms, which were carefully designed to encourage
	   good practices, or

	2) We are relying on the incidental relationships within the type
	   structure to provide us with information we should have represented
	   explicitly.

If you *need* a method invocation, program it, ferpeetsakes and make it
explicit!
-- 

	from Mole End			Mark Terribile
		(scrape .. dig )	mtx5b!mat
					(Please mail to mtx5b!mat, NOT mtx5a!
						mat, or to mtx5a!mtx5b!mat)
					(mtx5b!mole-end!mat will also reach me)
    ,..      .,,       ,,,   ..,***_*.

mikem@otc.OZ (Mike Mowbray) (07/02/87)

In article <227@nih-csl.UUCP>, keith@nih-csl.UUCP (keith gorlen) says:
> Summary: Suggested technique for testing the class of an object
> 	 has some problems.
> 
> In article <131@otc.OZ>, mikem@otc.OZ (Mike Mowbray) writes:
>>
>> #define  IS_TYPE(classtype,instance)  ((instance)._vptr == ::classtype\
>> __vtbl)
>>		 [ ... deleted ... ]
>> //--------------------------------------------------------
> 
> This technique assumes that all instances of a class will have the
> same vtbl address, which is not true unless you use the +e compiler
> switch.
>		[ ... deleted ... ]
> Since using the +e switch is such a pain, I wouldn't recommend this method.

Agreed.

(Don't know why I mentioned this dubious method at all - I just thought
 it was an interesting curiosity.)

			Mike Mowbray
			Systems Development
			Overseas Telecommunications Commission (Australia)

UUCP:   {seismo,mcvax}!otc.oz!mikem              ACSnet: mikem@otc.oz

weh@druhi.ATT.COM (HopkinsWE) (07/06/87)

In article <134@otc.OZ>, mikem@otc.OZ (Mike Mowbray) writes:
> In article <227@nih-csl.UUCP>, keith@nih-csl.UUCP (keith gorlen) says:
> > Since using the +e switch is such a pain, I wouldn't recommend this method.
> 
> Agreed.

If one is using virtual member functions in classes which appear in many
different source files, I highly recommend using the +e option to CC
to reduce the size of the object code significantly (the addendum to
the release notes quotes a savings of 25%).

A simple approach to using the +e option is to use +e0 (only include
external reference to virtual table) for all .c files, and compile
the .h files containing the class declarations using +e1 (generate
globally accessible virtual tables). Wherever the .o files containing
the class member functions are included, also include the .o containing
the virtual tables. If they are placed in an archive (``library''), both
will be automatically included by loader if the class is used anywhere.

Thus, all .c files may be treated uniformly (all compiled with +e0 option);
the only added work is to add a general rule for compiling .h files containing
class declarations, a simple task if using make.


				Bill Hopkins
				AT&T
				11900 N. Pecos St.
				Denver, CO 80234
				{allegra|ihnp4}!druhi!weh

P.S. Please note that the .h files will have to be temporarily copied
or linked into a .c file; otherwise the c compiler won't compile it.

keith@nih-csl.UUCP (07/09/87)

In article <2002@druhi.ATT.COM>, weh@druhi.ATT.COM (HopkinsWE) writes:
> In article <134@otc.OZ>, mikem@otc.OZ (Mike Mowbray) writes:
> > In article <227@nih-csl.UUCP>, keith@nih-csl.UUCP (keith gorlen) says:
> > > Since using the +e switch is such a pain, I wouldn't recommend this method.
> > 
> > Agreed.
> 
> A simple approach to using the +e option is to use +e0 (only include
> external reference to virtual table) for all .c files, and compile
> the .h files containing the class declarations using +e1 (generate
> globally accessible virtual tables).

This won't work on my .h files -- I usually have derived classes.  For
example, suppose you have a base class B with virtual functions
declared in the file B.h and classes D1 and D2 derived from B declared
in files D1.h and D2.h, respectively.  Now to compile D1.h or D2.h,
you must include the declaration of their base class B, so you might
as well #include "B.h" in D1.h and D2.h.  So if you compile both D1.h
and D2.h with the +e1 option both D1.o and D2.o have global
definitions of the vtbl for class B.  Have I misunderstood something?

-- 
	Keith Gorlen			phone: (301) 496-5363
	Building 12A, Room 2017		uucp: seismo!elsie!nih-csl!keith
	National Institutes of Health
	Bethesda, MD 20892

crocker@ihwpt.ATT.COM (ron crocker) (07/10/87)

> > A simple approach to using the +e option is to use +e0 (only include
> > external reference to virtual table) for all .c files, and compile
> > the .h files containing the class declarations using +e1 (generate
> > globally accessible virtual tables).
> 
> This won't work on my .h files -- I usually have derived classes.
> ...
> Have I misunderstood something?

	Yes, I think so.  You compile D1.c and D2.c with +e0.  D1.o
	and D2.o then refer to some external vtbls, including the
	one for class B.  You then compile a file (or set of files
	if there are some conflicts) containing only header files,
	call it headers.c, with +e1.  Now, headers.o contains the
	definitions for the vtbls, and ld takes care of the rest.
	(This all is nonsense if I have misunderstood something. 
	However, this is the mechanism I remember using, and I
	believe it is a reasonable representation of what Hopkins
	said.)
> 
> -- 
> 	Keith Gorlen			phone: (301) 496-5363

--
Ron Crocker
AT&T Bell Laboratories
Room IH 6D535
Naperville-Wheaton Road
Naperville, IL  60566

(312) 979-4051

weh@druhi.UUCP (07/10/87)

In article <229@nih-csl.UUCP>, keith@nih-csl.UUCP (keith gorlen) writes:
> In article <2002@druhi.ATT.COM>, weh@druhi.ATT.COM (HopkinsWE) writes:
> > A simple approach to using the +e option is to use +e0 (only include
> > external reference to virtual table) for all .c files, and compile
> > the .h files containing the class declarations using +e1 (generate
> > globally accessible virtual tables).
> 
> This won't work on my .h files -- I usually have derived classes.  For
> example, suppose you have a base class B with virtual functions
> declared in the file B.h and classes D1 and D2 derived from B declared
> in files D1.h and D2.h, respectively.  Now to compile D1.h or D2.h,
> you must include the declaration of their base class B, so you might
> as well #include "B.h" in D1.h and D2.h.  So if you compile both D1.h
> and D2.h with the +e1 option both D1.o and D2.o have global
> definitions of the vtbl for class B.  Have I misunderstood something?

Keith's observation is correct. The simplistic solution I suggested
is fatally flawed; there is no point in using the +e option WITHOUT
iheritance and virtual member functions. After some thought, writing
a test case, and consultation with other C++ developers, a reasonable
solution does exist. In summary, a special .c file is used to include
ALL class declarations which needs a virtual table. That file is
compiled using +e1 while all the other .c files are compiled
using +e0. The following example illustrates this:

// base.h
#ifndef base_h
#define base_h

class base {
	int one_int;
public:
	base () { one_int = 0; }
	virtual int get_one_int () { return one_int; }
};

#endif

// derived.h
#ifndef derived_h
#define derived_h

#include <stdio.h>
#include "base.h"

class derived: public base
{
	int one_int;
public:
	derived () { one_int = 1; }
	int get_one_int () { return one_int; }
	void display () { printf("derived::one_int = %d, base::one_int = %d\n",
			get_one_int (), base::get_one_int ()); }

};

#endif

// derived_2.h
#ifndef derived_2_h
#define derived_2_h

#include <stdio.h>
#include "base.h"

class derived_2: public base
{
	int one_int;
public:
	derived_2 () { one_int = 2; }
	int get_one_int () { return one_int; }
	void display () { printf("derived_2::one_int = %d, base::one_int = %d\n",
			get_one_int (), base::get_one_int ()); }

};

#endif

// defs.c
#include "base.h"
#include "derived.h"
#include "derived_2.h"

// main.c
#include "derived.h"
#include "derived_2.h"

main()
{
	derived one_obj;
	derived_2 one_obj_2;
	one_obj.display();
	one_obj_2.display();
}


This compiled using the following commands:

CC +e1 -c defs.c
CC +e0 main.c defs.o

And executes with the expected results:

$ a.out
derived::one_int = 1, base::one_int = 0
derived_2::one_int = 2, base::one_int = 0


The #ifndefs are necessary to ensure that each class is included
once (not a bad practice to follow in general). This approach
has been used in some medium sized projects, and the problem
experienced is that the preprocessor and compiler tables are
stressed.

				Bill Hopkins
				AT&T
				11900 N. Pecos St.
				Denver, CO 80234
				{allegra|ihnp4}!druhi!weh