Topic: run-time type checking (was: Covariant Types in Derived Classes)


Author: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Date: Sun, 9 Aug 1992 07:41:08 GMT
Raw View
In article <1992Aug6.143641.3600@cadsun.corp.mot.com> shang@corp.mot.com writes:
>In article <1992Aug6.000129.25587@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU (John
>MAX Skaller) writes:
>> In article <1992Aug4.134233.18613@cadsun.corp.mot.com> shang@corp.mot.com
>writes:
>> >Therefore, if you want dog and wolf mate by calling
>> >the function:
>> >
>> >    void foo (Animal *a, Mammal *m)
>> >    {
>> >      if (a#==r#) a->mate(*m);  // okay (I use a# to express typeof(*a))
>> >    }
>> >
>> >you must cast them into DogFamily first:
>> >
>> >    foo ((DogFamily*)aWolf, (DogFamily*)aDog);
>> >
>>  NO. This is my argument against downcasting or type knowledge.
>
>This is not a downcast, a safe upcast instead.

 Quite right. My apologies.
>
>>  You get a SPACE penalty even if you DONT use it.
>> There is probably no speed penalty, on the contrary, a compiler supported
>> RTTI mechanism must surely be faster than  a manually coded one.
>>
>
>The space penalty is on the secondary storage.

 Mm. Before linkage.
>
>>
>>  RTTI is not necessary for domestic systems.
>> It is required for foreign interfacing maybe, and it is required
>> to implement exceptions as their definition stands at the moment.
>>
>Well, John, don't you think your statements contradictory in your
>different posts?

 Quite possible.

>As far as I can remember, your constrained pointer
>proposal for heterogeneity do use a type switch.

 Yes, but not on the run-time type of the object,
on the run-time type of the pointer itself, and the pointer
is explicitly declared to have one of several types.
Safe, sound, secure, impossible to get a run-time error.

>And heterogeneity
>is not necessarily foreign.

 Of course. Tagged pointers are for DOMESTIC systems.
They cant work for foreign systems, because the tag types
are associated with declared classes: static linkage of the
program is assumed.

>Even for the homogeneity case -- the
>"mate" function -- you still have not provide a better solution
>than language supported RTTI. Tell me please, are you willing to
>put your beloved pet into an animal box to mate even without opening
>the box to see what animal it is? How do you think if the animal
>is not a nice animal (its virtual function may swallow your pet)?!

 Tagged pointers are not an alternative to 'thisclass',
a proposal I tentatively support, especially the way you described
it, it sound quite good.

 Tagged pointers COULD be used in the animal case
if you wanted to have a FIXED set of animals with varying
mate or fight results depending on a matrix types---because
tagged pointers could provide 'poor mans multiple dispatch'.
And you could THEN use the animal x animal case with 'thisclass'.
SO you get the special cases AND the default case of only
mating with the same type. Still no downcasting anywhere.

>
>>  It can be PROVED that compiler supplied
>> RTTI is not required domestically.
>> Rather, it can be proved DOWNCASTING is not required.
>> First, domestic means:
>>
>>  a) A single statically linked program
>>  b) No foreign interfaces---all objects are created by
>> the program, and there are no predefined class libraries that
>> have been ill-defined and thus require RTTI.
>>  c) No exception handling.
>>
>
>We certainly have different opinions on what is domestic and what
>is foreign. By your definition, surely RTTI and DOWNCAST is not
>required.

 Oh. At least we agree on something :-)

>Even the argument is not required to be TYPED. Many ML
>based languages can automatically infer the type of the argument.
>
>But your definition of domestic system is too narrow to support
>general purpose programming, to support software engineering and
>large scale systems.

 No it isnt. See below ...

>Your domestic definition will exclude
>systems like servers and libraries, exclude a large scale system
>that have to be divided into servral parts to be developed
>independently.

 It does not exclude libraries at all---only pre-existing
ill-defined ones. (So you can't use the excuse that library
XXXX is written in such a way you HAVE to use downcasting).

 It certainly excludes things like across process
communication, persistance, and all such concepts
which C++ ITSELF CANNOT SUPPORT PROPERLY because it is
ignorant of the appropriate concepts.

 Using library functions to support concurrency, for
example, requires foreign interfacing and hand written
code to get around C++ lack of knowledge, so one can't
expect tagged pointers to work across process boundaries
automatically anymore than a pointer can.
>
>In the following post, you said,
>
>       you should stop trying to convince me of the advantages
>       of RTTI
>
>If you said it is not necessary, where are the advantages? and for
>what purpose on earth should we make a sound one?

 Necessary and convenient are two different things.
Templates are not necessary, but they are convenient.
Exceptions are necessary---you cannot emulate them, they
must be built in to the language.

 Safe casting requires RTTI. It is surely convenient
sometimes to downcast, and sometimes sound. For example,
everywhere a tagged pointer is used, a downcast could
be done instead soundly. In fact tagged pointers with
bases with virtual functions could be IMPLEMENTED using
RTTI instead of a tag possibly.

 But tagged pointer give you MUCH more than downcasting,
they are a fully fledged generic facility on their own. And you
dont need RTTI for them in domestic systems. Mm, perhaps
tagged pointers WITH RTTI could also be used for
foreign interfacing---I hadn't though of that.
>
>       and try to help me calculate what limitations
>       must be imposed on it to make its use SOUND.
>
>If the SOUND RTTI and DOWNCAST is the one in which all the type
>errors can be detected at COMPILE TIME and the one ensuring NO
>VIOLATION at run time, I've already suggested one.
>

 I must have missed it. Even your 'thisclass'
does NOT have this property unless it is so restricted to
be unusable. (Restricted to where the compiler can infer the type).

 I would agree downcasting is sound where the compiler
can infer the type. But in these cases, RTTI is never needed
because the type is known statically. And in these cases
the programmer could easily arrange to have a pointer
of the required static type, so in these cases downcasting
is not particularly useful or convenient.

--
;----------------------------------------------------------------------
        JOHN (MAX) SKALLER,         maxtal@extro.ucc.su.oz.au
 Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------




Author: shang@corp.mot.com (David (Lujun) Shang)
Date: Mon, 10 Aug 92 18:27:29 GMT
Raw View
In article <1992Aug9.074108.27674@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU (John
MAX Skaller) writes:
> >
> >The space penalty is on the secondary storage.
>
>  Mm. Before linkage.

No, after linkage (I refer to the implementation of "iskindof".)

> >Your domestic definition will exclude
> >systems like servers and libraries, exclude a large scale system
> >that have to be divided into servral parts to be developed
> >independently.
>
>  It does not exclude libraries at all---only pre-existing
> ill-defined ones. (So you can't use the excuse that library
> XXXX is written in such a way you HAVE to use downcasting).
>

How do you infer the type of an input of a libray function
in the development of the library, if the input is a tagged
pointer as you suggested? Will you re-compile the library
source? If not, suppose the type of the input is B[D1,D2]*,
and we want to pass the input to a narrowed interface like
B[D1]*, or D1*, or to an interface B[D2,D3]*, you do need
RTTI and downcasting. The downcasting is performed implicitly,
for example, if you pass a "p" of B[D1,D2]* to B[D2,D3]*,
you first downcast it to D2 and then pass it to B[D2,D3]*.
If you re-compile the library source, it is possible for the
compiler to infer the actual type of the input for a given
call and determine whether this pointer can be passesd to
a narrowed interface. However, the library user have to
get the access right to the library source.
>
>  But tagged pointer give you MUCH more than downcasting,
> they are a fully fledged generic facility on their own. And you
> dont need RTTI for them in domestic systems.

I agree if you plan to use the *complete type inference*
technique to implement tagged pointers for the domestic
systems as you defined (I disagree on your point that the
programs using libraries can meet this requirement).

> Mm, perhaps
> tagged pointers WITH RTTI could also be used for
> foreign interfacing---I hadn't though of that.

Yes, of cuase. You can use type-id to tag the
pointer at run-time for a tagged pointer. This is an
alternative way to impelmet your tagged pointers, which
need RTTI and downcasting (implicit). Actually, any
polymorphic pointers can be tagged. It is not necessary
to tag only the pointers with a limited selecltion. In
this way, we can efficeintly remove all the type tags from
the object code so that a complete RTTI can be implemented
without the enforced overhead on tranditional types.

Your constrained pointer proposal is useful in some case. But
in general, its deffect is the violation of open/close
principle in software development: it may cause the necessity
to re-write and re-compile the exsisting codes once a new
derived classes is introduced.

David Shang




Author: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Date: 30 Jul 92 17:11:27 GMT
Raw View
In article <1992Jul29.160546.21766@cadsun.corp.mot.com> shang@corp.mot.com writes:
>In article <1992Jul28.214528.6360@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU (John
>MAX Skaller) writes:
>> In article <1992Jul27.170255.8185@cadsun.corp.mot.com> shang@corp.mot.com
>>
>>  The proposed RTTI downcast is WORSE: it return a NULL POINTER
>> if the cast fails due to type mismatch on pointers.
>>
>No. The RTTI I suggested does not return any NULL pointers. Quite simple and
>safe, the compiler does not let you program pass if there is a potential type
>error. It seems that you did not read what I wrote carefully.

 I apologise, I was indeed refering to my understanding of the
current deliberations of the ANSI/ISO committee.

 I do not recall how you proposed to constrain downcasting
so it might be detected at compile time (given the function body
to whom the parameter is passed is separately compiled).

 Such a constraint must be implemented by modifying the syntax
for parameter declarations, perhaps like so:

 f(Base[Derived1,Derived2]*);

The pointer of type Base* must be passed EVEN if it does not point
to Derived1 or Derived2, since f may sensibly use the common
base class Base to access the object.

This mechanism, incidently, leads to consideration of

 f([AnyType1,AnyType2]*);

where the types need not be related at all.

>> the proposed RTTI/downcast is any different. It may have slightly
>> better optimistaion oportunities, like all built in features.
>>
>Not just slightly. The compiler can know the specific type in many cases and
>hence can detect the type error at compile time.

 Could you give another example of how the compiler can detect
incorrect types? Certainly in the case of classes derived from a base
I do not see how passing a base could ever be wrong. And the compiler
*already* detects if a class not derived from the base is passed.

>For those types unknown to the
>compiler, the compiler can also detect the type error through RTTI.
>>
>> >Type safe downcast with RTTI will not have the above problems:
>> >
>>  WRONG. You get a system crash if you dont check for 0 pointer.
>>
>Angain, RTTI has nothing to do with null pointer. To return a null pointer for
>an RTT error is an irresponsible manner.

 Well I am sorry but I was refering to my understanding of the
current state of Bjarnes proposal, in which pointer casts to the
derived type of the wrong type yield a null pointer. (For references,
and exception is raised).

>At least, an exception should be
>raised for the error. I'm even not satisfied with the exception handling
>because you leave type error at run time! What I suggested is to detect all the
>type errors at compile time just by explicit run time type check. Sounds
>strange? I've mentioned this several times in my previous posters and examples
>are also given and I'm tired of repeating.

 I'm sorry but that sound contradictory. The compiler can
ensure a type check is done at run-time, but not statically check
the type at compile time, at least in some cases.
>
>David Shang
>
>
> --
>> ;----------------------------------------------------------------------
>>         JOHN (MAX) SKALLER,         maxtal@extro.ucc.su.oz.au
>>  Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
>> ;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------


--
;----------------------------------------------------------------------
        JOHN (MAX) SKALLER,         maxtal@extro.ucc.su.oz.au
 Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------




Author: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Date: 30 Jul 92 17:28:30 GMT
Raw View
In article <1992Jul29.162440.21848@cadsun.corp.mot.com> shang@corp.mot.com writes:
>In article <1992Jul28.210755.3548@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU (John
>MAX Skaller) writes:
>> >
>> >> NOW: I want to demonstrate why using RTTI is WRONG in this case,
>> >> and why the Species() function is required.
>> >>
>> >Remenber, your function Species() IS JUST FOR THE PURPOSE TO PROVIDE RTTI.
>>
>>  Yes. I'm not against having run-time-type information
>> available.
>
>See your original statement -- what you want to demonstrate.

 I refered to compiler generated RTTI. User generated RTTI
must obey the current restrictions of C++, and so is as sound and safe
as C++ currently is. I also refered to a specific case in one place,
and say in general in another, intending to say 'always'. OK?

>
>> >>
>> >> The demonstration is so simple, and applies so generally, I wonder
>> >> why I didnt think of it before.
>> >>
>> >>  class DOG : Animal { ....
>> >>  class HUND : Animal { .. // german programmer
>> >>
>> >This is never an execuse. If it is, your Species() function has the same
>> >problem:
>> >
>> >  char* DOG::Species()
>> >        { return "DOG"; }
>> >  char* DOG::Species()
>> >        { return "HOUND"; }   // german programmer
>>
>>  What?? The compiler catches this error: it is a duplicate
>> definition.
>>
>The class DOG can be declared as static in different modules and it is highly
>possible if an Australian and a German work for the same project that is
>separated into many different modules.

 Sorry. The linker catches the error, not the compiler.

>
>> Actually, I think downcasting is OK when you already knew what the
>> type was, or could have. Then it is just a convenience. And safe.
>>
>Then we have not disagreement any more.

 Well, I'm not sure about that. We certainly dont have a
specific *agreement* about when downcasting is sound and when
it is not, nor how to suitably constrain it.

 My efforts at the moment are directed to preserving the
mechanism suggested for Bjarne's proposal, that is, using
a downcasting of pointers, getting 0 if the wrong type
is there (and exceptions to references).

 I am looking at a constraint where all the things that
a function can downcast to must be listed (or implied by the list)
in the interface.

 f(Base[Derived1,Derived2]*);

 This mechanism has some nice features, although I am
not sure about its soundness yet.

 Member functions can always downcast to their class.
That immediately implies a 'thisclass' facility, and with no
additional syntax in the declaration.

 Furthermore, explicit downcasting operators are not
required always, since the declaration already lists the legal
downcasts.

 Even better, the syntax might support casting to unrelated
types, and can be implemented then, and when no virtual functions
are available, by using tagged pointers or some other mechanism.

 Finally, for the moment, one can implement

 f(Base[Derived1, Derived2]* b)
 {
  ...
  type switch(ab) {
  case Derived1:
   b->Derived1_member();
  case Derived2:
   b->Derived2_member();
  default:
   b->Base_member();
  };
 }

although there would have to be an implied break after each case.
--
;----------------------------------------------------------------------
        JOHN (MAX) SKALLER,         maxtal@extro.ucc.su.oz.au
 Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------




Author: shang@corp.mot.com (David (Lujun) Shang)
Date: Fri, 31 Jul 92 23:49:28 GMT
Raw View
In article <1992Jul30.171127.18238@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU
(John MAX Skaller) writes:
>  I do not recall how you proposed to constrain downcasting
> so it might be detected at compile time (given the function body
> to whom the parameter is passed is separately compiled).
>
Very simple: using RTTC to keep a type safe downcast. If you want to cast an
animal to a cat, check whether the animal is actually a cat. If you want to
cast an argument of some unknown type T1 to an argument of some unknown type
T2, check whether T1 is actually T2. If you do not check, the compiler tells
you that your program has a potential error.

>  Such a constraint must be implemented by modifying the syntax
> for parameter declarations, perhaps like so:
>
>  f(Base[Derived1,Derived2]*);
>
No, not help, just for the reason you give: given the function body to whom the
parameter is passed is separately compiled.

>  Could you give another example of how the compiler can detect
> incorrect types? Certainly in the case of classes derived from a base
> I do not see how passing a base could ever be wrong. And the compiler
> *already* detects if a class not derived from the base is passed.
>
Your "mate" example is the best one, I don't think it is necessary to make a
new one.

    Animal::mate (thiscalss &peer);
    // you needn't check whether peer is the same type as this object
    // since the interface ensures that.

    Cat c1,c2;
    Dog d1,d2;
    Crockdile k1, k2;
    Lizard z1, z2;
    Mammal *m1, *m2;
    Reptile *r1, *r2;
    Animal *a1, *a2;

    c1.mate(c2);   // okay.
    c1.mate(d1);   // error detected by compiler

    m1 = &d1;
    a1 = &d2;
    m1->mate(*a2);  // okay (through type inference)

    m1 = &c1;
    m2 = &d2;
    m1->mate(*m2);  // error detected by compiler (through type inference)

    a1->mate(*a2);  // error detected by compiler (two unitialized types)

    m1 = new Mammal;
    m2 = new Mammal;
    m1->mate(*m2);  // okay. Note that if Mammal can be instantiated
                    // by new, "mate" has to be defined in class Mammal,
                    // so that "mate" at the Mammal level is meaningful.
                    // If you cannot write "mate" at Mammal level, Mammal
                    // must be remain abstract, and this case will never
                    // happen!

    void foo (Animal *a1, Animal *a2)
    {
      a1->mate(*a2);  // error detected by the compiler
    }

    void foo (Animal *a, Reptile *r)
    {
      if (a#==r#) a->mate(*r);  // okay (I use a# to express typeof(*a))
    }

    void foo (Animal *a)
    { Dog d;
      Cat c;
      if (a#==Dog)
      {  a->mate(d);      // okay
         *a = d;          // okay
      }
      else
         *a = c;         // error
    }

    <Animal AnimalType> void foo (AnimalType *a1, AnimalType *a2)
    {
      a1->mate(*a2);  // okay
    }


> >What I suggested is to detect all the
> >type errors at compile time just by explicit run time type check. Sounds
> >strange? I've mentioned this several times in my previous posters ...
>
>  I'm sorry but that sound contradictory. The compiler can
> ensure a type check is done at run-time, but not statically check
> the type at compile time, at least in some cases.

I hope the above example can convince you that the resulting program will be
type-error free at run time.

David Shang





Author: shang@corp.mot.com (David (Lujun) Shang)
Date: Fri, 31 Jul 92 23:01:35 GMT
Raw View
In article <1992Jul30.172830.18501@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU
(John MAX Skaller) writes:
>  I refered to compiler generated RTTI. User generated RTTI
> must obey the current restrictions of C++, and so is as sound and safe
> as C++ currently is. I also refered to a specific case in one place,
> and say in general in another, intending to say 'always'. OK?
>
I cannot understand why user generated RTTI is sound and safe while the
compiler generated RTTI is not.

> >
> >> Actually, I think downcasting is OK when you already knew what the
> >> type was, or could have. Then it is just a convenience. And safe.
> >>
> >Then we have not disagreement any more.
>
>  Well, I'm not sure about that. We certainly dont have a
> specific *agreement* about when downcasting is sound and when
> it is not, nor how to suitably constrain it.
>
If you think "downcasting is OK when you already knew what the type is", then
we have no disagreement. I said from the begining that the downcast should only
be allowed when you already know that the cast is on the exact type you want.
How do you know within a polymorphic behavior? Just by explicit run time type
check. The compiler will *enforce* you to do so in order to produce the safe
code. If you perform a downcast which you are not sure of the type within the
context, the compiler does not know either, then the compiler will not let your
program pass.

>  I am looking at a constraint where all the things that
> a function can downcast to must be listed (or implied by the list)
> in the interface.
>
>  f(Base[Derived1,Derived2]*);
>
>  This mechanism has some nice features, although I am
> not sure about its soundness yet.
>
Are Derived1 and Derived2 sealed classes? Or is it true that the pointer
"Base[Derived1,Derived2]*" can only accept exactly two derived classes ( no
derived classes of Derived1 or Derived2)? IF NOT, such constraint helps nothing
in RTTI and downcast. Let me suppose YES, it still helps little (see below)

>  Member functions can always downcast to their class.
> That immediately implies a 'thisclass' facility, and with no
> additional syntax in the declaration.
>
It is never a necissity to downcast "this" to its own class! "thisclass" is not
used to specify "this", it's a kind of garbage to say that "this cat is a cat".
"thisclass" is used to specify other object (input of the member function) that
is of the same type of this object. Member function CANNOT downcast its input
to its class without care if the input is not specified by "thisclass".

>  Furthermore, explicit downcasting operators are not
> required always, since the declaration already lists the legal
> downcasts.
>
True, but not by your reason. Example,

    Animal a1 = new Cat;
    Animal a2 = new Dog;
    Cat c = *a1;
    Dog *d = a2;

No explicit RTTC required here. Both you and the complier know these downcasts
are correct and safe;

You proposed a constraint through a fixed set of *exact* types (I suppose so).
What is the purpose of such contraint? It cannot eliminates or even reducing
the necessity of run time type check. Non-derterminisim between the two and
among the infinitive both require run time type check.

When I refer to a fixed set of "exact types", I usually use them to make the
interface more precise. But additional RTTC is needed when you pass a less
constrained argument to this interface.

>  Even better, the syntax might support casting to unrelated
> types, and can be implemented then, and when no virtual functions
> are available, by using tagged pointers or some other mechanism.
>
A complete RTTI should already support casting from a set of unrealted types to
a concrete type. It has nothing to do with any constraint.

>  Finally, for the moment, one can implement
>
>  f(Base[Derived1, Derived2]* b)
>  {
>   ...
>   type switch(ab) {
>   case Derived1:
>    b->Derived1_member();
>   case Derived2:
>    b->Derived2_member();
>   default:
>    b->Base_member();
>   };
>  }
>
> although there would have to be an implied break after each case.

No default case needed here in the type switch because the interface of "f"
already tells you that there are only two cases of the input type. This is the
reason we need constraint: to help the resulting implementation more correct.
While "thiscalss" describes a covariant constraint of dependent types, tagged
union types (what you proposed is actually a kind of tagged union type as far
as I can understand) desribes a constraint of independent types. They cannot be
replaced with each other.

David Shang







Author: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Date: Sun, 2 Aug 1992 12:15:22 GMT
Raw View
In article <1992Jul31.230135.1201@cadsun.corp.mot.com> shang@corp.mot.com writes:
>In article <1992Jul30.172830.18501@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU
>(John MAX Skaller) writes:
>>  I refered to compiler generated RTTI. User generated RTTI
>> must obey the current restrictions of C++, and so is as sound and safe
>> as C++ currently is. I also refered to a specific case in one place,
>> and say in general in another, intending to say 'always'. OK?
>>
>I cannot understand why user generated RTTI is sound and safe while the
>compiler generated RTTI is not.

 This not what I meant. Compiler generated RTTI just *is*, it
is neither sound, nor safe nor anything else. The issue is how you
ACCESS it.

 The form of RTTI in the animal example (virtual Species()
function) is not necessarily safe, as you have pointed out (one
could assign the wrong species name). But is IS sound, that is,
it is type-safe and preserves the open closed principle because
it uses existing sound C++ mechanisms, namely, virtual functions.
It does NOT downcast--downcasting, like all casting, is unsound
(in general).

 The difference between downcasting to any old derived
class an using existing mechanisms, is that no existing
mechanism (other than casting) can provide you with access
to an interface other than a SUBSET of the one you know the
object by. Downcasting provides access to a SUPERSET of the
interface.

 Providing access to an object via a base class ONLY
is a restriction. That restriction is what enables polymorphism
to work properly. Downcasting removes that restriction and thereby
allows access to the exact type of the object, and to interfaces
that you were not originally granted access to.

 If there is something *we* dont not understand, it is this:
what guarrantees are voided (lost) if one gains access to an object
via an interface by which it was not statically known?


 class X {
 protected:
  int x;
 public:
  int get()const {return x;}
 };

 f(X&);

 class Y : public X {
 public:
  void set(int y){x=y;}
 };

 Y aY;

 f(aY);

Please consider this example carefully, because I think it summarises
our points of view.

In my view f should not be allowed to modify its object.
To me, a proper language system would guarrantee that.
If it does, I can take advantage of the fact that i know
that aY has not changed.

In your view, it would be allowed to (by downcasting the X to a Y,
then using the 'set' method.)
In your view, the explict interface with which f is declared
need not constrain what behaviours the X object may exhibit.

>If you think "downcasting is OK when you already knew what the type is", then
>we have no disagreement. I said from the begining that the downcast should only
>be allowed when you already know that the cast is on the exact type you want.

 But you dont know the type without asking the object.
 You are nowhere required to specify which downcasts you might do.

>>  I am looking at a constraint where all the things that
>> a function can downcast to must be listed (or implied by the list)
>> in the interface.
>>
>>  f(Base[Derived1,Derived2]*);
>>
>>  This mechanism has some nice features, although I am
>> not sure about its soundness yet.
>>
>Are Derived1 and Derived2 sealed classes? Or is it true that the pointer
>"Base[Derived1,Derived2]*" can only accept exactly two derived classes ( no
>derived classes of Derived1 or Derived2)?

 It will accept ANY pointer that can convert to a pointer
to Base. It is just like  Base*, with explicit permission to
cast to Derived1 and Derived2. (And ANY of their base classes,
since you can then upcast)

 But please note: this proposal as it stands is just as
flawed as the original downcasting because the thing is not
a real type. It makes the interface explicit:

 f(B[D1,D2]&);

but only for f, nothing would stop you circumventing this by

 g(B& b){ f(b); }


>IF NOT, such constraint helps nothing
>in RTTI and downcast. Let me suppose YES, it still helps little (see below)
>
>>  Member functions can always downcast to their class.
>> That immediately implies a 'thisclass' facility, and with no
>> additional syntax in the declaration.
>>
 What I meant by this is that

 dog::mate(animal&)

implies

 dog::mate(animal[dog]&);

without you needing to write it.

>
>>  Furthermore, explicit downcasting operators are not
>> required always, since the declaration already lists the legal
>> downcasts.
>>
>True, but not by your reason.

 I refer to Bjarne's proposal: it requires explict downcasts.
(To the best of my knowledge)

>Example,
>
>    Animal a1 = new Cat;
>    Animal a2 = new Dog;

 But this is illegal syntax!

>    Cat c = *a1;
>    Dog *d = a2;
>
>No explicit RTTC required here.
>Both you and the complier know these downcasts
>are correct and safe;

 Yes, but why would you do something that stupid?
Why would you not do it the other way:

 Cat c*=new Cat;
 Animal * a=c;

>
>You proposed a constraint through a fixed set of *exact* types(I suppose so).

No, the constraint is that you can only downcast to the listed types,
or those implied by that (via subsequent upcasts).

>What is the purpose of such contraint? It cannot eliminates or even reducing
>the necessity of run time type check.

 The purpose was to make the donwcastability explicit
in the interface. As I pointed out, as it stands, it fails to even
do this.

 And I say again: I am NOT against using compiler generated RTTI,
per se.


>Non-derterminisim between the two and
>among the infinitive both require run time type check.

 Yes, but it CAN be done without RTTI in the object:
the information can be put into the pointers instead.
This seems to be BETTER than RTTI in the object, because
there is no restriction on the need for virtual functions,
indeed the objects wouldn't even have to share a common base.
This is precisely what's needed for het aggregates of finite
different types. The only problem is that its not extensible.

 The extensibility may HAVE to be sacrificed in return
for soundness. If this were not the case, then you could
just use a virtual function in the first place.
>
>>  Even better, the syntax might support casting to unrelated
>> types, and can be implemented then, and when no virtual functions
>> are available, by using tagged pointers or some other mechanism.
>>
>A complete RTTI should already support casting from a set of unrealted types to
>a concrete type. It has nothing to do with any constraint.

 The implementation is currently considered a 'constraint'.

>
>>  Finally, for the moment, one can implement
>>
>>  f(Base[Derived1, Derived2]* b)
>>  {
>>   ...
>>   type switch(ab) {
>>   case Derived1:
>>    b->Derived1_member();
>>   case Derived2:
>>    b->Derived2_member();
>>   default:
>>    b->Base_member();
>>   };
>>  }
>>
>> although there would have to be an implied break after each case.
>
>No default case needed here in the type switch because the interface of "f"
>already tells you that there are only two cases of the input type.

 That was NOT the intention.

 In a heterogenous collection, the idea was that
a new type and new routine added might say:

 f(Base[NewType]*);

It then can downcast to NewType (which it knows about). But the
collection STILL contains objects of types UNKNOWN to IT.
These are the defaults.

>This is the
>reason we need constraint: to help the resulting implementation more correct.
>While "thiscalss" describes a covariant constraint of dependent types, tagged
>union types (what you proposed is actually a kind of tagged union type as far
>as I can understand) desribes a constraint of independent types. They cannot be
>replaced with each other.

 You can use the constrained downcasting to implement
"thisclass".  It leaves the problem of covariance up to the programmer.
And there is no run-time overhead unless it is used.

 virtual animal::mate(animal&);

 dog::mate(animal& a) // Implies mate(animal[dog]&);
      // note: NOT COVARIANT.
 {
  ... code using animal only, might return
  dog other&=a; // exception on type error
  .. code using COVARIANCE--dog
 }

So its up to the programmer. MORE flexible than a mere covariant type,
and sound too.


--
;----------------------------------------------------------------------
        JOHN (MAX) SKALLER,         maxtal@extro.ucc.su.oz.au
 Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------




Author: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Date: Sun, 2 Aug 1992 17:00:51 GMT
Raw View
In article <1992Jul31.234928.1313@cadsun.corp.mot.com> shang@corp.mot.com writes:
>In article <1992Jul30.171127.18238@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU
>(John MAX Skaller) writes:
>>  I do not recall how you proposed to constrain downcasting
>> so it might be detected at compile time (given the function body
>> to whom the parameter is passed is separately compiled).
>>
>Very simple: using RTTC to keep a type safe downcast. If you want to cast an
>animal to a cat, check whether the animal is actually a cat. If you want to
>cast an argument of some unknown type T1 to an argument of some unknown type
>T2, check whether T1 is actually T2. If you do not check, the compiler tells
>you that your program has a potential error.

 Oh, thats ensure *safety*. It is not the kind of constraint
I was talking about---I assumed it can be done safely. I'm not
interested in how to do it safely, or whether we want to
do it safely. Surely I agree IF we are allowed to downcast
somewhere, we would LIKE to be sure we get the right pointer
back. It is the IF that interests me.

 I want to constrain downcasting so it is SOUND.

 The type of constraint I'm talking about is a limitation
on what base pointers are *allowed* to be downcast to what
derived classes.

 There is an implicit constraint here: you must know the
name of a derived class to downcast to it.  That means if I want
to stop you downcasting to my class, I can make it anonymous,
for example. I have ACCESS control. But only by not naming my class.

 I do NOT think this constraint is enough.  By enough I
mean something specific: I mean enough to ensure that downcasting
is always SOUND as well as safe.  The syntax

 f(Base[Derived1,Derived2]* b);

was devised by me to demonstrate an additional constraint: in f you are
only allowed to  downcast b, and only to Derived1 or Derived2
(or any base of them).


 [And as it stands the above mechanism is NOT enough to
ensure soundness. Because it can be trivially circumvented.]
>
>Your "mate" example is the best one, I don't think it is necessary to make a
>new one.
>
>    Animal::mate (thiscalss &peer);
>    // you needn't check whether peer is the same type as this object
>    // since the interface ensures that.

 And gives a run-time error if not. The type system is then
no longer statically type safe. At the moment, it is. My thisclass
suggestion did not have this problem. The type check was done under
programmer control.

>
>    Cat c1,c2;
>    Dog d1,d2;
>    Crockdile k1, k2;
>    Lizard z1, z2;
>    Mammal *m1, *m2;
>    Reptile *r1, *r2;
>    Animal *a1, *a2;
>
>    c1.mate(c2);   // okay.
>    c1.mate(d1);   // error detected by compiler

 Oh, yes. True. You are quite correct: when the exact type
is know because a value is used, the compiler can check.
I understand.
>
>    m1 = &d1;
>    a1 = &d2;
>    m1->mate(*a2);  // okay (through type inference)

 Yes, this is harder for the compiler.

[other good examples deleted]

>
>I hope the above example can convince you that the resulting program will be
>type-error free at run time.

 Yes. Not all cases can be checked though, some must be
deferred to run-time. Then I suppose you get a warning from the
compiler.

 f(animal& a1, animal& a2) { a1.mate(a2); }
 // must be checked at run time

--
;----------------------------------------------------------------------
        JOHN (MAX) SKALLER,         maxtal@extro.ucc.su.oz.au
 Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------




Author: shang@corp.mot.com (David (Lujun) Shang)
Date: Mon, 3 Aug 92 17:03:41 GMT
Raw View
In article <1992Aug2.170051.7534@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU (John
MAX Skaller) writes:
> >    Cat c1,c2;
> >    Dog d1,d2;
> >    Crockdile k1, k2;
> >    Lizard z1, z2;
> >    Mammal *m1, *m2;
> >    Reptile *r1, *r2;
> >    Animal *a1, *a2;
> >
> >    c1.mate(c2);   // okay.
> >    c1.mate(d1);   // error detected by compiler
>
>  Oh, yes. True. You are quite correct: when the exact type
> is know because a value is used, the compiler can check.
> I understand.
> >
> >    m1 = &d1;
> >    a1 = &d2;
> >    m1->mate(*a2);  // okay (through type inference)
>
>  Yes, this is harder for the compiler.
>
> [other good examples deleted]
>
>  Yes. Not all cases can be checked though, some must be
> deferred to run-time. Then I suppose you get a warning from the
> compiler.
>
>  f(animal& a1, animal& a2) { a1.mate(a2); }
>  // must be checked at run time
>

What you gave here was already included in my examples. You just deleted my
examples of the cases which YOU DON'T THINK could be checked by the compiler!

David Shang




Author: shang@corp.mot.com (David (Lujun) Shang)
Date: Mon, 3 Aug 92 16:51:59 GMT
Raw View
In article <1992Aug2.121522.19096@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU (John
MAX Skaller) writes:
>  class X {
>  protected:
>   int x;
>  public:
>   int get()const {return x;}
>  };
>
>  f(X&);
>
>  class Y : public X {
>  public:
>   void set(int y){x=y;}
>  };
>
>  Y aY;
>
>  f(aY);
>
> Please consider this example carefully, because I think it summarises
> our points of view.
>
> In my view f should not be allowed to modify its object.
> To me, a proper language system would guarrantee that.
> If it does, I can take advantage of the fact that i know
> that aY has not changed.
>

You cannot take this advantage in C++ since it allows unsafe downcast.

> In your view, it would be allowed to (by downcasting the X to a Y,
> then using the 'set' method.)

The programmer has already been allowed to do this as long as downcast, safe or
not safe, sound or not sound, *constrained* or *not constrained*,  is enabled.
It is NEVER my view that the programer should abuse downcast to access derived
interfaces when the design prohibits to do that.

> In your view, the explict interface with which f is declared
> need not constrain what behaviours the X object may exhibit.
>

On the contrary, I advocate a style of more strict constraint. "thisclass" is a
kind of constraint, "type set" is another kind of constraint. There is a more
general constraint: the type dependency constraint. All of these should be
reflected in the interfaces.


> >If you think "downcasting is OK when you already knew what the type is",
> >then we have no disagreement. I said from the begining that the downcast
> >should only be allowed when you already know that the cast is on the exact
> >type you want.
>
>  But you dont know the type without asking the object.

This is an implementation issue. Why should I ask the object for its type?
Instead, I can ask the polymorphic argument who represents the object, say, the
polymorphic refrence, the polymorphic parameter, or the polymorphic pointer.

>
> >>  Member functions can always downcast to their class.
> >> That immediately implies a 'thisclass' facility, and with no
> >> additional syntax in the declaration.
> >>
>  What I meant by this is that
>
>  dog::mate(animal&)
>
> implies
>
>  dog::mate(animal[dog]&);
>
> without you needing to write it.
>

Does
 dog::fight(animal&)
imply
  dog::fight(animal[dog]&)?
I doubt that a compiler can be intelligent enough even to understand the
meaning of an operation's name.

> >What is the purpose of such contraint? It cannot eliminates or even reducing
> >the necessity of run time type check.
>
>  The purpose was to make the donwcastability explicit
> in the interface. As I pointed out, as it stands, it fails to even
> do this.
>

Is it reasonable to restrict a base pointer to B[D1,D2]? If I need to add a new
derived class "Dn" into the list, should I modify the interface that I already
implementated and used by other components of the system?

In general, by listing a set of derived classes to ensure the downcastability
in the interface is not practicable. Besides, it helps nothing for the so
called sound downcast because you still need RTTI to check the validation of
the downcast. The interface cannot ensure that the downcast is always valid.

However, I am not agianst using "type set" constraint in the interface. As I
said already, I advocate it, but not for the purpose of "sound" downcast. In
one of my previous poster, I already used this concept to express the interface
of + operator:

   <Number InputType>                // the type of the right operand
   Number { thisclass, InputType }   // the output type
   Number::operator+                 // the name of operator
   ( InputType y);                   // the input specification

The output type of + may be either the type of the left operand or the type of
the right operand:

 int n; float f;
 ...
 Number { float, int } m;
 m = n+f;   // the current type of "m" is float.

However, the compiler will generate the efficient codes and do all the standard
conversions if the operator + is built-in. Otherwise, the conversion will be
performed according to the user-defined "+" and the output type will be unknown
to the compiler and then RTTI for "m" is required:

 switch (typeof(m))
 { case int: n = m; ... break;
   case float: f = m; ...
 }

>  You can use the constrained downcasting to implement
> "thisclass".

No, I cannot.

>
>  virtual animal::mate(animal&);
>
>  dog::mate(animal& a) // Implies mate(animal[dog]&);
>       // note: NOT COVARIANT.

I cannot see any constraint specified in the interface!

>  {
>   ... code using animal only, might return
>   dog other&=a; // exception on type error

Aha! the constraint is implied in the implementation! Therefore, type errors
like cat.mate(dog), bird.mate(fish) can only be found at run time, and other
disadvantages ... [ I've already pointed out ]

>   .. code using COVARIANCE--dog
>  }
>




Author: chip@tct.com (Chip Salzenberg)
Date: Mon, 3 Aug 1992 20:33:03 GMT
Raw View
According to shang@corp.mot.com:
>    void foo (Animal *a, Reptile *r)
>    {
>      if (a#==r#) a->mate(*r);  // okay (I use a# to express typeof(*a))
>    }

This won't fly.  Dog and Wolf may be distinct classes, yet they can
mate.  If RTTI is to work in this case, it would have to be a
conditional cast (or an "iskindof" test, which amounts to the same
thing).
--
Chip Salzenberg at Teltronics/TCT  <chip@tct.com>, <73717.366@compuserve.com>
   "Do Rush place weird subliminal backmasked messages in their songs to
 compel unwilling geeks to commit evil .sig atrocities?"  -- Dean Engelhardt




Author: shang@corp.mot.com (David (Lujun) Shang)
Date: Tue, 4 Aug 92 13:42:33 GMT
Raw View
In article <2A7D97FF.7C38@tct.com> chip@tct.com (Chip Salzenberg) writes:
> According to shang@corp.mot.com:
> >    void foo (Animal *a, Reptile *r)
> >    {
> >      if (a#==r#) a->mate(*r);  // okay (I use a# to express typeof(*a))
> >    }
>
> This won't fly.  Dog and Wolf may be distinct classes, yet they can
> mate.  If RTTI is to work in this case, it would have to be a
> conditional cast (or an "iskindof" test, which amounts to the same
> thing).

"mate" is written only for padagogical purpose. Here we simply assume that
"mate" can only be performed on the animals of the same type. The concept of
"same type" is relative. At the level of DogFamily, wolf and dog are same
types, but they are different types according to further division. If wolf and
dog can mate, they must mate at the DogFamily, i.e. use the "mate" function
defined in DogFamily class. Therefore, if you want dog and wolf mate by calling
the function:

    void foo (Animal *a, Mammal *m)
    {
      if (a#==r#) a->mate(*m);  // okay (I use a# to express typeof(*a))
    }

you must cast them into DogFamily first:

    foo ((DogFamily*)aWolf, (DogFamily*)aDog);

Surely you can list many many exceptions on "mate" because the real world is
not so simple. For those exceptions, as you said, we have to make special
checks for those individuals, for example, if (a# == some_animal1 && b# ==
some_animal2) ... [sorry for my poor knowledge on zoology].

There is a heavy overhead to implement "iskindof". It is possible, however, to
implement it in a library [if a full and simple RTTI is incorperated into the
language] along with other useful functions on types. We can call it Type
Management Library (TML). You get the panelty when you use them.

Again, this is only a padagogical example. When I said that RTTI is necessary,
I didn't say that RTTI is a panacea to all problems.

David Shang




Author: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Date: Tue, 4 Aug 1992 16:26:48 GMT
Raw View
In article <1992Aug3.165159.11968@cadsun.corp.mot.com> shang@corp.mot.com writes:
>In article <1992Aug2.121522.19096@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU (John
>MAX Skaller) writes:
>>  class X {
>>  protected:
>>   int x;
>>  public:
>>   int get()const {return x;}
>>  };
>>
>>  f(X&);
>>
>>  class Y : public X {
>>  public:
>>   void set(int y){x=y;}
>>  };
>>
>>  Y aY;
>>
>>  f(aY);
>>
>> Please consider this example carefully, because I think it summarises
>> our points of view.
>>
>> In my view f should not be allowed to modify its object.
>> To me, a proper language system would guarrantee that.
>> If it does, I can take advantage of the fact that i know
>> that aY has not changed.
>>
>
>You cannot take this advantage in C++ since it allows unsafe downcast.

 This is true. However, if the derivation was made virtual,
downcasting would be illegal. So that is the constraint at present.
I would advocated banning ALL downcasting, whereas I believe instead
Bjarne's proposal will allow ALL downcasting, including when virtual
derivation is used.

 At the moment, programmers are wary of downcasting because
it is potentially unsafe. If it were made safe by a language change,
then it would be considered a desirable feature. I oppose this
at present, and instead would like to see downcasting made illegal,
precisely because I cannot take advantage of knowing there are no
downcasts *even* at present.

>
>> In your view, it would be allowed to (by downcasting the X to a Y,
>> then using the 'set' method.)
>
>The programmer has already been allowed to do this as long as downcast, safe or
>not safe, sound or not sound, *constrained* or *not constrained*,  is enabled.
Could you please write shorter lines?

>It is NEVER my view that the programer should abuse downcast to access derived
>interfaces when the design prohibits to do that.

 Sure, but we differ perhaps in that I would not leave that
decision to the programmer, I would leave it up to the compiler.
>
>> In your view, the explict interface with which f is declared
>> need not constrain what behaviours the X object may exhibit.
>>
>
>On the contrary, I advocate a style of more strict constraint.

 Yes, you are quite correct, you have been advocating potentially
sounder mechanisms than downcasting.

>"thisclass" is a
>kind of constraint, "type set" is another kind of constraint. There is a more
>general constraint: the type dependency constraint. All of these should be
>reflected in the interfaces.
>
 IMHO we desire a constraint as close as possible to that which
ensures soundness while allowing maximal flexibility. Whether a
constraint is sufficiently tight is not a matter of opinion but of
fact: either the constraint preserves various agreed upon principles
or not or it doesn't.

 Whether is is too strong or not is another issue, and unless
we can find a constraint which can be proved to be *exactly* tight
enough, there may be some debate on the best form of constraint.
>
>> >If you think "downcasting is OK when you already knew what the type is",
>> >then we have no disagreement. I said from the begining that the downcast
>> >should only be allowed when you already know that the cast is on the exact
>> >type you want.
>>
>>  But you dont know the type without asking the object.
>
>This is an implementation issue. Why should I ask the object for its type?
>Instead, I can ask the polymorphic argument who represents the object, say, the
>polymorphic refrence, the polymorphic parameter, or the polymorphic pointer.

 This is all the same. You are asking the object. In the case of
tagged pointers, for example, this is not the case, with tagged
pointers you ask the pointer, you only access the object AFTER you
have ascertained its type.
>
>Does
> dog::fight(animal&)
>imply
>  dog::fight(animal[dog]&)?

 Yes, in the downcasting proposal where the constraint is merely
to list the classes to which downcasting can be done, the above is
precisely what I meant.

>I doubt that a compiler can be intelligent enough even to understand the
>meaning of an operation's name.

 Sorry, I miss your point here.
>
>> >What is the purpose of such contraint? It cannot eliminates or even reducing
>> >the necessity of run time type check.
>>
>>  The purpose was to make the donwcastability explicit
>> in the interface. As I pointed out, as it stands, it fails to even
>> do this.
>>
>
>Is it reasonable to restrict a base pointer to B[D1,D2]? If I need to add a new
>derived class "Dn" into the list, should I modify the interface that I already
>implementated and used by other components of the system?

 Yes, precisely. That would force recompilation of all
dependent classes.
>
>In general, by listing a set of derived classes to ensure the downcastability
>in the interface is not practicable.

 Why not? You can just run through your code and add the
class names you downcast to in each function to the interface
for that function. That can *almost* be done automatically
with a little scanner program :-) Or you can just compile the program,
and when you get an error due to a missing class name in the list
(ERROR-illegal downcast of Base to Derived on line 333)  you just
add the class to the list and try again.

>Besides, it helps nothing for the so
>called sound downcast because you still need RTTI to check the validation of
>the downcast. The interface cannot ensure that the downcast is always valid.

 So what? I am not arguing against using RTTI to ensure sound
downcasts are also safe. I am arguing against unsound downcasts.
Since unconstrained downcasting is unsound, I search for proper
compiler enforcible constraints which will only allow sound
downcasts. (The above example constraint, listing the downcast classes
in the interface of a function, however is not such an animal,
it fails trivially)
>
>However, I am not agianst using "type set" constraint in the interface. As I
>said already, I advocate it, but not for the purpose of "sound" downcast.

>
>>  You can use the constrained downcasting to implement
>> "thisclass".
>
>No, I cannot.
>
>>
>>  virtual animal::mate(animal&);
>>
>>  dog::mate(animal& a) // Implies mate(animal[dog]&);
>>       // note: NOT COVARIANT.
>
>I cannot see any constraint specified in the interface!

 It is implicit, this one time. I assumed it would be
nice to allow a member to always be able to downcast from a pointer
to one of its bases to the class of which it was a member.

 Actually, it couldbe made implicit for all friend functions
too. Thats because it is made *explicit* by the friend declaration.
>
>>  {
>>   ... code using animal only, might return
>>   dog other&=a; // exception on type error
>
>Aha! the constraint is implied in the implementation! Therefore, type errors
>like cat.mate(dog), bird.mate(fish) can only be found at run time, and other
>disadvantages ... [ I've already pointed out ]

 Quite correct, type inference cannot help. But it is true in
that some type errors for 'thisclass' will only EVER be found at run-time.
Your method definitely has the advantage that the check can be done
at the point of call, and perhaps optimised to a NO-OP. The member
function then assumes that by the time it is entered it has the right
type.

 However your method has the disadvantage that it is hard to
disambiguate

 dog::fight(animal&);
 dog::fight(thisclass&);

which might both be desired. My mechanism makes that possible at run-time
under programmer control. This is not to say I favour my method,
BTW, just a comment.

 I am currently working on an informal proposal for tagged
pointers (and Uncle :-) Thomas is perhaps looking at tagged unions.

 Tagged pointers provide a reasonably general sound solution to
heterogenous collections. They are VERY interesting. The more I
examine them the more I like the power they offer, especially
in conjunction with templates.

 How do you like

 [D1, D2, D3]* p;

 p->f();

 calls D1::f, D2::f, D3::f, selecting at run time automatically,
but fully type safe and checked statically at compile time?
(The run-time operation is a vectored jump---very fast. MUCH faster
than using RTTI)

 Now consider you might also allow f to be a template function.


--
;----------------------------------------------------------------------
        JOHN (MAX) SKALLER,         maxtal@extro.ucc.su.oz.au
 Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------




Author: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Date: Tue, 4 Aug 1992 16:37:36 GMT
Raw View
In article <1992Aug3.170341.12062@cadsun.corp.mot.com> shang@corp.mot.com writes:
>In article <1992Aug2.170051.7534@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU (John
>MAX Skaller) writes:
>>
>> [other good examples deleted]
>>
>>  Yes. Not all cases can be checked though, some must be
>> deferred to run-time. Then I suppose you get a warning from the
>> compiler.
>>
>>  f(animal& a1, animal& a2) { a1.mate(a2); }
>>  // must be checked at run time
>>
>
>What you gave here was already included in my examples. You just deleted my
>examples of the cases which YOU DON'T THINK could be checked by the compiler!
>

 I'm sorry, you gave a lot of examples. Could you explain
how the above can be checked at compile time given that f
is a separately compiled (not inline) function?

 I do not see how the call a1.mate(a2) can be checked,
since the compiler does not know the exact types of a1 and a2
at compile time. I agree the compiler can *generate* code to
ensure that run-time checks are done BEFORE a1.mate(a2) is
actually called, so that, as you claim, when mate is entered
it must have the right types for arguments. But there is
still a check to be done at run-time.

 (Incidentally, your argument provides an EXCELLENT reason
for us to want proper preconditions and postconditions
specified in the interface. The preconditions can be executed before
the appropiate call every time by a dumb compiler, but a smart
compiler can use an inference engine to optimise some or all
of the precondtion away. Your 'thisclass' is just a special case
of that (which might need RTTI to resolve some cases).)


--
;----------------------------------------------------------------------
        JOHN (MAX) SKALLER,         maxtal@extro.ucc.su.oz.au
 Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------




Author: shang@corp.mot.com (David (Lujun) Shang)
Date: Wed, 5 Aug 92 00:51:16 GMT
Raw View
In article <1992Aug4.162648.8899@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU (John
MAX Skaller) writes:
> >I doubt that a compiler can be intelligent enough even to understand the
> >meaning of an operation's name.
>
>  Sorry, I miss your point here.

I mean, can a compiler be intelligent enough to know that

    animal::mate(animal&)

imply

    dog::mate(animal[dog]&)

while

    animal::fight(animal&)

does not imply

    dog::fight(animal[dog]&)

just according to the meaning of the function name: "mate" and "fight"?

>  However your method has the disadvantage that it is hard to
> disambiguate
>
>  dog::fight(animal&);
>  dog::fight(thisclass&);
>
> which might both be desired. My mechanism makes that possible at run-time
> under programmer control. This is not to say I favour my method,
> BTW, just a comment.

This is exactly the disadvantage of your method: you can not distinguish the
two types of the fight because you have nothing different in their interfaces:

  dog::fight(animal&);  // imply fight(animal&) in implementation
  dog::fight(animal&);  // imply fight(animal[dog]&) in implementation

On the contary, this is the advantage of my method because they are two
functions with same name but different signatures:

  dog::fight(animal&);
  dog::fight(thisclass&);

The exact match will have the higher priority than non-exact match. Therefore,
"a_dog.fight(another_dog)" will call

  dog::fight(thisclass&);

while "a_dog.fight(a_cat)" will call

  dog::fight(animal&);


>
>  I am currently working on an informal proposal for tagged
> pointers (and Uncle :-) Thomas is perhaps looking at tagged unions.
>
I'm looking forword to reading your proposal. I just have not time to prepare
any formal proposals.

David Shang





Author: shang@corp.mot.com (David (Lujun) Shang)
Date: Wed, 5 Aug 92 00:59:24 GMT
Raw View
In article <1992Aug4.163736.9225@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU (John
MAX Skaller) writes:
> In article <1992Aug3.170341.12062@cadsun.corp.mot.com> shang@corp.mot.com
writes:
> >In article <1992Aug2.170051.7534@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU
(John
> >MAX Skaller) writes:
> >>
> >> [other good examples deleted]
> >>
> >>  Yes. Not all cases can be checked though, some must be
> >> deferred to run-time. Then I suppose you get a warning from the
> >> compiler.
> >>
> >>  f(animal& a1, animal& a2) { a1.mate(a2); }
> >>  // must be checked at run time
> >>
> >
> >What you gave here was already included in my examples. You just deleted my
> >examples of the cases which YOU DON'T THINK could be checked by the
> >compiler!
> >
>
>  I'm sorry, you gave a lot of examples. Could you explain
> how the above can be checked at compile time given that f
> is a separately compiled (not inline) function?
>

   f(animal& a1, animal& a2)
   { a1.mate(a2);  // the compiler will tell you an error here
                   // (or just give you a warning)
   }

   f(animal& a1, animal& a2)
   { if (typeof(a1)==typeof(a2)) a1.mate(a2);
     else a1.fight(a2);
   } // okay, compiler will let you pass and ensure you that
     // there will be no run-time type error.

David Shang





Author: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Date: Thu, 6 Aug 1992 00:01:29 GMT
Raw View
In article <1992Aug4.134233.18613@cadsun.corp.mot.com> shang@corp.mot.com writes:
>In article <2A7D97FF.7C38@tct.com> chip@tct.com (Chip Salzenberg) writes:
>> According to shang@corp.mot.com:
>> >    void foo (Animal *a, Reptile *r)
>> >    {
>> >      if (a#==r#) a->mate(*r);  // okay (I use a# to express typeof(*a))
>> >    }
>>
>> This won't fly.  Dog and Wolf may be distinct classes, yet they can
>> mate.  If RTTI is to work in this case, it would have to be a
>> conditional cast (or an "iskindof" test, which amounts to the same
>> thing).
>

 David---could you PLEASE reduce the length of your lines
so that when I quote you they dont overflow?

>"mate" is written only for padagogical purpose.

 Yes, and that purpose is making the assumption that the
actual type of an object is meaningful. The fact that "real world"
methods to solve this problem invariably require proper understanding
of the problem (in this case zoology) and that to use polymorphism
REQUIRES proper virtualisation indicates to me that the assumption
that the actual type is useful or meaningful is invalid.

 Those "real world" problems can be solved without
compiler supplied RTTI using polymorphism, so why shouldn't
the much simpler pedagogical example be solved the same way?
Particularly as the compiler supplied RTTI CANT be used for the
real world solution.

>Here we simply assume that
>"mate" can only be performed on the animals of the same type. The concept of
>"same type" is relative.

 The concept of "type" here belongs to the problem domain:
to zoology or even micro-biology. Looking at it this way we see
that the question of being able to mate in fact is complex and
a general solution is NOT to do with classification criteria
at all.


>At the level of DogFamily, wolf and dog are same
>types, but they are different types according to further division. If wolf and
>dog can mate, they must mate at the DogFamily, i.e. use the "mate" function
>defined in DogFamily class. Therefore, if you want dog and wolf mate by calling
>the function:
>
>    void foo (Animal *a, Mammal *m)
>    {
>      if (a#==r#) a->mate(*m);  // okay (I use a# to express typeof(*a))
>    }
>
>you must cast them into DogFamily first:
>
>    foo ((DogFamily*)aWolf, (DogFamily*)aDog);
>
>Surely you can list many many exceptions on "mate" because the real world is
>not so simple.

 NO. This is my argument against downcasting or type knowledge.
ANY such mechanism can ONLY list finite exceptions to potentially
infinite derived classes. As you consider more derived classes
you will have to 'patch up' the functions to take the new classes
into account. This is wrong, it is against polymorphism.

 Exhaustive listing of cases is never polymorphism, it
is heterogeneity. If you need to provide exhaustive tests of cases
do not use polymorphism.

>For those exceptions, as you said, we have to make special
>checks for those individuals, for example, if (a# == some_animal1 && b# ==
>some_animal2) ... [sorry for my poor knowledge on zoology].
>
>There is a heavy overhead to implement "iskindof". It is possible, however, to
>implement it in a library [if a full and simple RTTI is incorperated into the
>language] along with other useful functions on types. We can call it Type
>Management Library (TML). You get the panelty when you use them.

 You get a SPACE penalty even if you DONT use it.
There is probably no speed penalty, on the contrary, a compiler supported
RTTI mechanism must surely be faster than  a manually coded one.

>
>Again, this is only a padagogical example.
>When I said that RTTI is necessary,
>I didn't say that RTTI is a panacea to all problems.
>
>David Shang

 RTTI is not necessary for domestic systems.
It is required for foreign interfacing maybe, and it is required
to implement exceptions as their definition stands at the moment.

 It can be PROVED that compiler supplied
RTTI is not required domestically.
Rather, it can be proved DOWNCASTING is not required.
First, domestic means:

 a) A single statically linked program
 b) No foreign interfaces---all objects are created by
the program, and there are no predefined class libraries that
have been ill-defined and thus require RTTI.
 c) No exception handling.

Given these constraints, the proof is trivial. There are
many ways to do it. Here is one way.

Let B be a base and D a fixed derived class.
We keep a linked list of elements of the form

 struct {
  B *b;
  D *d;
 };

Now whenever  we want to downcast B to a D, we can scan the
linked list and extract the d*, which is already the correct
static type.

We keep one of these lists for every interesting derived class.
There can only be a finite number of these in a domestic
program and they can all be known in the
program.

Of course what we are doing here is implementing heterogeneity.
It works EVEN if there is no common base. So you see it
has nothing to do with polymorphism.

--
;----------------------------------------------------------------------
        JOHN (MAX) SKALLER,         maxtal@extro.ucc.su.oz.au
 Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------




Author: shang@corp.mot.com (David (Lujun) Shang)
Date: Thu, 6 Aug 92 14:36:41 GMT
Raw View
In article <1992Aug6.000129.25587@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU (John
MAX Skaller) writes:
> In article <1992Aug4.134233.18613@cadsun.corp.mot.com> shang@corp.mot.com
writes:
> >Therefore, if you want dog and wolf mate by calling
> >the function:
> >
> >    void foo (Animal *a, Mammal *m)
> >    {
> >      if (a#==r#) a->mate(*m);  // okay (I use a# to express typeof(*a))
> >    }
> >
> >you must cast them into DogFamily first:
> >
> >    foo ((DogFamily*)aWolf, (DogFamily*)aDog);
> >
>  NO. This is my argument against downcasting or type knowledge.

This is not a downcast, a safe upcast instead.

> >
> >There is a heavy overhead to implement "iskindof". It is possible, however,
> >to implement it in a library [if a full and simple RTTI is incorperated into
> >the language] along with other useful functions on types. We can call it
> >Type Management Library (TML). You get the panelty when you use them.
>
>  You get a SPACE penalty even if you DONT use it.
> There is probably no speed penalty, on the contrary, a compiler supported
> RTTI mechanism must surely be faster than  a manually coded one.
>

The space penalty is on the secondary storage.

>
>  RTTI is not necessary for domestic systems.
> It is required for foreign interfacing maybe, and it is required
> to implement exceptions as their definition stands at the moment.
>
Well, John, don't you think your statements contradictory in your
different posts? As far as I can remember, your constrained pointer
proposal for heterogeneity do use a type switch. And heterogeneity
is not necessarily foreign. Even for the homogeneity case -- the
"mate" function -- you still have not provide a better solution
than language supported RTTI. Tell me please, are you willing to
put your beloved pet into an animal box to mate even without opening
the box to see what animal it is? How do you think if the animal
is not a nice animal (its virtual function may swallow your pet)?!

>  It can be PROVED that compiler supplied
> RTTI is not required domestically.
> Rather, it can be proved DOWNCASTING is not required.
> First, domestic means:
>
>  a) A single statically linked program
>  b) No foreign interfaces---all objects are created by
> the program, and there are no predefined class libraries that
> have been ill-defined and thus require RTTI.
>  c) No exception handling.
>

We certainly have different opinions on what is domestic and what
is foreign. By your definition, surely RTTI and DOWNCAST is not
required. Even the argument is not required to be TYPED. Many ML
based languages can automatically infer the type of the argument.

But your definition of domestic system is too narrow to support
general purpose programming, to support software engineering and
large scale systems. Your domestic definition will exclude
systems like servers and libraries, exclude a large scale system
that have to be divided into servral parts to be developed
independently.

In the following post, you said,

       you should stop trying to convince me of the advantages
       of RTTI

If you said it is not necessary, where are the advantages? and for
what purpose on earth should we make a sound one?

       and try to help me calculate what limitations
       must be imposed on it to make its use SOUND.

If the SOUND RTTI and DOWNCAST is the one in which all the type
errors can be detected at COMPILE TIME and the one ensuring NO
VIOLATION at run time, I've already suggested one.

David Shang











Author: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Date: Thu, 6 Aug 1992 22:09:46 GMT
Raw View
In article <1992Aug5.005924.20859@cadsun.corp.mot.com> shang@corp.mot.com writes:
>In article <1992Aug4.163736.9225@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU (John
>MAX Skaller) writes:
>> >>
>> >>  Yes. Not all cases can be checked though, some must be
>> >> deferred to run-time. Then I suppose you get a warning from the
>> >> compiler.
>> >>
>> >>  f(animal& a1, animal& a2) { a1.mate(a2); }
>> >>  // must be checked at run time
>> >>
>> >
>> >What you gave here was already included in my examples. You just deleted my
>> >examples of the cases which YOU DON'T THINK could be checked by the
>> >compiler!
>> >
>>
>>  I'm sorry, you gave a lot of examples. Could you explain
>> how the above can be checked at compile time given that f
>> is a separately compiled (not inline) function?
>>
>
>   f(animal& a1, animal& a2)
>   { a1.mate(a2);  // the compiler will tell you an error here
>                   // (or just give you a warning)
>   }

 This is precisely the example I gave: either you make this
illegal, or the check must be done at run-time. If you made it illegal,
then IMHO 'thisclass' would be useless, so we have a run-time test.
>
>   f(animal& a1, animal& a2)
>   { if (typeof(a1)==typeof(a2)) a1.mate(a2);
>     else a1.fight(a2);
>   } // okay, compiler will let you pass and ensure you that
>     // there will be no run-time type error.

 This is more difficult because you rely on the compiler being
smart enough to do type inference. I dont know enough about this
subject, but you would have to have rules about minimum capabilities
of the compiler to do type inference: we dont want programs to
compile on one system and not another, do we?


--
;----------------------------------------------------------------------
        JOHN (MAX) SKALLER,         maxtal@extro.ucc.su.oz.au
 Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------




Author: jrobie@netmbx.netmbx.de (Jonathan Robie)
Date: Tue, 21 Jul 1992 13:53:36 GMT
Raw View
shang@corp.mot.com (David (Lujun) Shang) writes:

>In article <1992Jul20.143900.23087@merlin.hgc.edu> jcm@hgc.edu (James McKim)
>writes:
>> If I store a bunch of animals on the disk, I'm probably doing so
>> because I don't expect that I will need to access features specific
>> to a dog.

>I belive many OO database users are much more critical than you.
Absolutely!  If I store an object, then I expect that I can retrieve
that same object and have it act just exactly like the object that I
stored--it should have the same state and behavior.

After all, if I use polymorphy, then I am using the base class as an
interface to derived classes which may not even exist at the time I
design my interface.

Take the above statement and try to apply it to a CAD system:  If I
draw a bunch of shapes on the screen, I'm probably doing so because
I don't expect that I will need to draw things specific to a circle.
Is this really the way we want to approach polymorphy?


Jonathan

===========================================================================

Jonathan Robie  jrobie@netmbx.UUCP
Arnold-Zweig-Str. 44 jrobie@netmbx.in-berlin.de
O-1100 Berlin
Deutschland  Phone:  +37 (2) 472 04 19  (Home, East Berlin)
    +49 (30) 342 30 66 (Work, West Berlin)


--
Jonathan

===========================================================================





Author: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Date: Tue, 21 Jul 1992 15:03:32 GMT
Raw View
In article <1992Jul20.141712.4622@cadsun.corp.mot.com> shang@corp.mot.com writes:
>In article <1992Jul17.154635.27331@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU
>(John MAX Skaller) writes:
>>  Certainly there are het aggregates out there, say
>> Windows 3.0 window lists. But the can be converted to C++
>> objects without loss of type info: they don't have to
>> be collected in the same form as they are presented to
>> you.
>>
>
>You need, at least, a pre-compiler to analize the type system and gather the
>information for this case.

 If you try to use C++ in an evironment where the system
must be dynamically extended with completely new types, you
will certainly need run-time type info. In fact, you had better
be able to incrementally compile and dynamically link as well.
So you need a proper reflexive system.

 C++ is way off being able to do that, even if it had
run-time type-info.

 Any how for this sort of facility:: one where you
try to use C++ for something way outside its original purpose,
and way outside the boundaries of any statically compiled
system, well, yes, run-time type info is needed, and much
much more.

 But you will note: we're not talking about mere
downcasting here---*that* was the original issue.
Here you're talking about dynamism---dynamic typing,
dynamic compilation, dynamic loading, etc etc---
all those things that Lisp and Stalk already do,
I might add.


>Let me give you some more examples. (1) OO operating
>systems written in C++: most systems must have their own pre-compiler that
>gathers the type information in order to provide a high level OO user
>interface. (2) OO database systems: they must have their own languages, at
>least a pre-compiler if they chose C++ as their master language, and most of
>the work for the pre-compiler is to gather the type information.

 Here the precompiler is a nasty compromise.  A proper
reflexive (self-extensible) system is what is really needed.

>And see
>another good example that Jonathan Robie gives us:
>
>In article <5W0422J@netmbx.netmbx.de> jrobie@netmbx.netmbx.de (Jonathan Robie)
>writes:
>> Think of stream I/O.  Don't you feel stupid coding an << operator for
>> each type you want to be able to print?  An intelligent stream facility
>> would be able to print any object with a sensible default format which
>> could optionally include the name and type of each field.  Try to code
>> this without run time information.  Since the I/O stream is coded first
>> and can impose no restrictions on user objects I do not know of any way
>> to solve this without adding my own precompiler.
>>

 Well, in this case there is no need at all for dynamic typing,
a mere virtual function returning a string does the job just fine.
>
>In the same article <1992Jul17.154635.27331@ucc.su.OZ.AU>
>maxtal@extro.ucc.su.OZ.AU (John MAX Skaller) writes:
>> Lets take feeding of animals. You must spec. the problem better.
>> Does each animal know the types (finite list) of food they eat?
>> E.g, you have list of base classes for each animal.
>>
>> Now you have list of animals and list of foods, and feed each
>> available food to animal until food is eaten. Is this
>> what you want?
>>
>>
>
>No, never. What I want is a spec.-- a spec. of a food type which is dependent
>on the animal type, and such dependency can be checked statically at compile
>time.

 Sure, but I don't understand *exactly* what you mean.
I think you will have trouble formulating the problem.
The reason I say this is that in general the solution
requires multiple dispatch, in other words, a matrix of
animal x food entries.

 Can you *reduce* the problem to less than a matrix?

>
>David Shang
>
>
>
>
>> --
>> ;----------------------------------------------------------------------
>>         JOHN (MAX) SKALLER,         maxtal@extro.ucc.su.oz.au
>>  Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
>> ;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------


--
;----------------------------------------------------------------------
        JOHN (MAX) SKALLER,         maxtal@extro.ucc.su.oz.au
 Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------




Author: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Date: Tue, 21 Jul 1992 16:26:59 GMT
Raw View
In article <1992Jul20.215058.1216@cadsun.corp.mot.com> shang@corp.mot.com writes:
>Secondly, we both
>agree that it is necessary to allow rediscovery of a type.

 In general C++ programming I do not agree that it is
necessary to recover arbitrary type information. That is because,
quite simply, you CANNOT use it.

>What I disagree is
>the argument that the design of heterogeneous collection is fundamentally
>wrong, which, of cause, is not your statement.

 I think I should clarify here: I am arguing against
DOWNCASTING.

 I am not against collections of objects all derived
from a common base provided you access them via the base.
That is the vey point of inheritance, after all.
In this case the objects are all of the same type:
it is homogeneous at the level it is accessed and
heterogeneous at the actual object level, but the actual
object level is not accessible.

 I am not against collections of objects of completely
unrelated types provided the types are drawn from a finite
and statically known set. In this case there is a 'type' which
is the finite union of those types, so again, the collection
is really homogeneous wrt that type.

 I am not against the two above combined either.

 I am against using, in general programming,
DOWNCASTING or run-time type information to defeat the type system.

 I agree that in Debuggers and other such programs
these facilities are essential, indeed, you need much more
than a mere safe downcast or run-time type code. In a debugger,
for example, you need in effect run-time access
to the complete declaration and definintion of each class in
the whole program!

 C++ does NOT have facilities at present to do this.
I am NOT against adding them. But I would be against using these
facilities in the general programming context for which C++
was intended and where such mechanism are *dangerous*.

 Clearly, for example, a debugger which allows you
to modify instances of a class breaks encapsulation!
It breaks almost every principle you can think of.
It is meant to: you use the debugger when a more important
thing is already broken : your program :-)

--
;----------------------------------------------------------------------
        JOHN (MAX) SKALLER,         maxtal@extro.ucc.su.oz.au
 Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------




Author: shang@corp.mot.com (David (Lujun) Shang)
Date: Wed, 22 Jul 92 02:22:18 GMT
Raw View
In article <1992Jul21.162659.25474@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU
(John MAX Skaller) writes:
>  In general C++ programming I do not agree that it is
> necessary to recover arbitrary type information. That is because,
> quite simply, you CANNOT use it.
>
"In general C programming I do not agree that it is necessary to use
inheritance of structure. That is because, quite simply, you CANNOT use it"
-- I don't like the argument. We are not talking about C++ programming. We are
talking about the enhancement of the C++. If this argument could make any
sense, why should we need C++?

In article <1992Jul20.215058.1216@cadsun.corp.mot.com> I write:
> >What I disagree is
> >the argument that the design of heterogeneous collection is fundamentally
> >wrong, which, of cause, is not your statement.
>

In article <1992Jul21.162659.25474@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU
(John MAX Skaller) replies:
>  I think I should clarify here: I am arguing against
> DOWNCASTING.
>
I am against *unsafe* downcasting too. What we are arguring about is not the
downcasting. It is the run time type checking: whether it is necessary.

>  I am not against collections of objects all derived
> from a common base provided you access them via the base.
> That is the vey point of inheritance, after all.
> In this case the objects are all of the same type:
> it is homogeneous at the level it is accessed and
> heterogeneous at the actual object level, but the actual
> object level is not accessible.
>
True. I'm always in favor of doing that. We can always add member funtions and
virtual member functions to aviod downcasting an element in a *domestic*
heterogeneous collection or structure. But this does not mean that the run time
type info is not necessary. In case of a foreign heterogeneous collection, we
often need the run time type info, need to store and export the type info
together with the object.

>  I am not against collections of objects of completely
> unrelated types provided the types are drawn from a finite
> and statically known set. In this case there is a 'type' which
> is the finite union of those types, so again, the collection
> is really homogeneous wrt that type.
>
This case exactly examplifies the necessity of runtime type info. Given an
element in this collection, you need run time type info to identify its type.
Otherwise, how do you do with the element which may be in one of the types that
are *completely unrelated*, say { int, float, doulbe }?

>
>  I am against using, in general programming,
> DOWNCASTING or run-time type information to defeat the type system.
>
I just don't understand! Run time type check is a facillity to prevent careless
downcast to defeat the type system. How can you say that it is used in general
programming to defeat the type system?

>  I agree that in Debuggers and other such programs
> these facilities are essential, indeed, you need much more
> than a mere safe downcast or run-time type code. In a debugger,
> for example, you need in effect run-time access
> to the complete declaration and definintion of each class in
> the whole program!
>
A simple type id can be a key for a debugger to access to the complete
declaration and definintion of each class in the whole program. We are not
necessary to include the complete type information at run time.

>  C++ does NOT have facilities at present to do this.
> I am NOT against adding them. But I would be against using these
> facilities in the general programming context for which C++
> was intended and where such mechanism are *dangerous*.
>
Again, I can't understand. Run time type check is not a dangerous mechanism. On
the contrary, it is used prevent potential dangers.

>  Clearly, for example, a debugger which allows you
> to modify instances of a class breaks encapsulation!
> It breaks almost every principle you can think of.
> It is meant to: you use the debugger when a more important
> thing is already broken : your program :-)
>
Here, you goes too far. A debugger can break every principle of encapsulation
and protection only through low level programming facillities (e.g. the bitwise
and bytewise read/write, enforced typecasting between different types, machine
addresses, etc.). But a debugger can never break any principle of encapsulation
and protection only through run time type check. Remember: run time type
checking does not allow to cast objects which are actually of different types.

Even we stick to homogeneous programming, we still need run time type checking.
One of your reason against the concept of "thisclass" is that it needs run time
type checking, which you view as a disadvantadge. "thisclass" is just a
facillity to describle homogeneous data structure.

Okay, let's get rid of the concept of "thisclass" for the time being. Now, is
it true that run time type checking is no longer needed for homogeneous data
structure? No. Without covariant type specification (now, we come to our
original subject) in derived classes, we still have to run time type check the
type of arguments in derived classes in order to prevent any violation. Let's
consider a homogeneous animal collection:

    class Animal;
    class AnimalGroup
    { protected:
       Animal * animals;
      public:
       virtual void accept (Animal *ap) {...};
        ...
    };

Since it is a homogeneous collection, it should contains animals of the same
type. The concept of "same type" is relatively dependend on what the class
designer means. Suppose the same type" is based on the *family* according to
the systemtic zoology. Then the collection should be of all dogs, or all cats,
etc. We have to run time type check the input of "accept" in order to ensure a
proper call:

    class Cat: public Animal;
    class CatGroup: public AnimalGroup
    { protected:
         // Animal * animals;
         // I wish I could narrowing the scope of "animals" to Cat here
         // but unfortuanetly I can't do that in C++. Well, I'll keep the
         // fact in my mind and assume that it has been narrowed.
       public:
         void accept (Animal *ap)
            // I wish I could narrow the scope of "ap" to Cat here,
            // but unfortunately, if I do, it will not the function that
            // will override the virtual function defined in the base, and
            // then if someone operates on this cat group through a AnimalGroup
            // interface, it could be totally wrong!
         {  // Okay, since we cannot present a type safe interfacewe in C++,
            // we have to run time type check here whether *ap is the right
            // animal we should accept.
            if (typeof(*ap)==Cat)
               // put this cat into the group according to the specific
               // orgnization of the cat family
            else
               // we can do nothing here
         };
      };

[people may suggest using templates for this case. But I happened to be a
person dislikes the C++-style templates, on which I have already posted my
opinions in this news group. Specifically for this topic, using templates, you
cannot achieve a polymorphic interface on AnimalGroup. For example, if I want
to define a heterogeneous collection of the animal groups, how can I implement
a general interface to access to concrete animal groups in this heter
collection? Using temaplate again?  No. It doesn't help, since templates does
not support heterogeneous data structure.].

What I give is only a very simple example. In practice, specialization in
derived classes is a very commom requirement. Without run time type check,
calling functions in derived classes through a base interface could be very
dangerous.

Even we have run time type check, it is still not safe to code the functions in
derived classes if I happened to forget the run-time type checking. We can pick
up the topic of covariant specification now. only if we can specialize the
interface in derived classes, can both calling abd coding functions in derived
classes be free of any violation.

The concept of "thsclass" is one step close to this goal.

David Shang










Author: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Date: Thu, 23 Jul 1992 15:42:54 GMT
Raw View
In article <1992Jul22.022218.1115@cadsun.corp.mot.com> shang@corp.mot.com writes:
>In article <1992Jul21.162659.25474@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU
>(John MAX Skaller) writes:
>>  In general C++ programming I do not agree that it is
>> necessary to recover arbitrary type information. That is because,
>> quite simply, you CANNOT use it.
>>
>"In general C programming I do not agree that it is necessary to use
>inheritance of structure. That is because, quite simply, you CANNOT use it"

 Sorry, I'm not sure what you mean be inheritance of structure.

> I don't like the argument. We are not talking about C++ programming. We are
>talking about the enhancement of the C++. If this argument could make any
>sense, why should we need C++?

 Well, we need C++ because it can do lots of nice things already.

>
>>  I think I should clarify here: I am arguing against
>> DOWNCASTING.
>>
>I am against *unsafe* downcasting too. What we are arguring about is not the
>downcasting.

 Ah: you misunderstand! I REALLY ARGUE AGAINST DOWNCASTING.

>It is the run time type checking: whether it is necessary.

 Given the need to do downcasting, to do it safely run-time
type information is required. But I am arguing it is not necessary
to downcast. So the question of needing run-time type info to
do it doesn't arise.

>
>>  I am not against collections of objects all derived
>> from a common base provided you access them via the base.
>> That is the vey point of inheritance, after all.
>> In this case the objects are all of the same type:
>> it is homogeneous at the level it is accessed and
>> heterogeneous at the actual object level, but the actual
>> object level is not accessible.
>>
>True. I'm always in favor of doing that. We can always add member funtions and
>virtual member functions to aviod downcasting an element in a *domestic*
>heterogeneous collection or structure.

 Ah. I like your word *domestic*. But your statement shows backwards
thoughts: we do not add virtual functions to "avoid downcasting".
We design our systems properly in the first place, and then the need
to downcast never arises, when our thoughts wander in that
direction one can be sure we are not thinking virtuously,
and perhaps the design itself is flawed.

 Inother words, in a *domestic* system, any apparent
need for downcasting should be used as an indicator of flawed design.

>But this does not mean that the run time
>type info is not necessary. In case of a foreign heterogeneous collection, we
>often need the run time type info, need to store and export the type info
>together with the object.

 Ah, now *foreign* systems. Here, in general, we cannot
store objects directly, they must be encoded somehow. That encoding
is the run-time type info.

 The problem is that you don't HAVE th objects to get the run
time info FROM. You have the encodings, and the object type info
is in the encoding --- not the object. So of course you must
extract it to create an object of the appropriate type--- and to
do that implies you KNOW the run-time type BEFORE you create
the object, so it follows you dont need to ask the object
for its type---you already knew the type when you created it.

 The exception---the time you DO need run-time type info
is when you have a *meta-domestic* system. Like a debugger,
it is a system designed to manage systems, and BOTH happen
to be already C++. THEN you could use RTTI.
>
>>  I am not against collections of objects of completely
>> unrelated types provided the types are drawn from a finite
>> and statically known set. In this case there is a 'type' which
>> is the finite union of those types, so again, the collection
>> is really homogeneous wrt that type.
>>
>This case exactly examplifies the necessity of runtime type info. Given an
>element in this collection, you need run time type info to identify its type.
>Otherwise, how do you do with the element which may be in one of the types that
>are *completely unrelated*, say { int, float, doulbe }?

 You need to know the type. But you DO NOT HAVE TO ASK THE OBJECT
WHAT TYPE IT IS. You have to store a code WITH the object that you
access separately.

 And notice there is NO issue of downcasting here. The issue
is one of SELECTION. From a FINITE number of alternatives.


Author: glw@io.uswest.com (Glenn Williams)
Date: 24 Jul 92 14:33:59 GMT
Raw View
>(John MAX Skaller) writes:
>
> Given the need to do downcasting, to do it safely run-time
>type information is required. But I am arguing it is not necessary
>to downcast. So the question of needing run-time type info to
>do it doesn't arise.
>
>We design our systems properly in the first place, and then the need
>to downcast never arises, when our thoughts wander in that
>direction one can be sure we are not thinking virtuously,
>and perhaps the design itself is flawed.
>

Ok, let's have an example of a properly designed system.

> Inother words, in a *domestic* system, any apparent
>need for downcasting should be used as an indicator of flawed design.
>

What is the basis for your conclusions?

>class Animal {
> int mate(Animal&);
>};
>class Dog : Animal {
> int mate (Animal&);
>};

>We REALLY want Dog::mate(Dog&), but we can't have it.

>This is a flaw in Object Oriented Programming IMHO.
>The solution requires multimethods which are inherently
>functional.

What?

You have stated all this before, and every time you do, you fail to provide
any examples of how you would design without downcasting.

So, as I asked for earlier, let's have an example of a properly
designed system.



Glenn Williams
US WEST


"Y'er spookin' the cattle." - Curly




Author: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Date: Fri, 24 Jul 1992 19:22:07 GMT
Raw View
In article <1992Jul24.143359.3602@advtech.uswest.com> glw@io.uswest.com (Glenn Williams) writes:
>>(John MAX Skaller) writes:
>>
>> Given the need to do downcasting, to do it safely run-time
>>type information is required. But I am arguing it is not necessary
>>to downcast. So the question of needing run-time type info to
>>do it doesn't arise.
>>
>>We design our systems properly in the first place, and then the need
>>to downcast never arises, when our thoughts wander in that
>>direction one can be sure we are not thinking virtuously,
>>and perhaps the design itself is flawed.
>
>Ok, let's have an example of a properly designed system.

 I can of course give any number of systems not using
downcasting --- LOTS of code I have written for example.

 But that wont PROVE anything. You have to give me
an example of a system that uses downcasting and then I'll
show how to write it without downcasting.

 Please restrict examples to domestic systems, since
I already agree all bets are off for foreign systems and
also for introspective ones like, say, debuggers, OODBMS,
etc.

 Lets agree for example to restrict ourselves to
text input and output and no disk storage. Lets agree
that the program must handle a fixed number of types.
And that it reads text in, does some calculations,
and then prints the answers.

 Such a program creates ALL the objects, manipulates
them, and then it terminates.

>
>> Inother words, in a *domestic* system, any apparent
>>need for downcasting should be used as an indicator of flawed design.
>>
>
>What is the basis for your conclusions?

 There are two reasons.

 1) Its NEVER necessary. Because you CREATED all the objects
 in the first place, you know their types at that point in
 time, agreed?

 So if you want to find out the type of an object, you have
 forgotten something you once knew---and that is a design
 flaw if you needed to know the type.

 2) The second reason is that downcasting---indeed ANY casting
 and not downcasting in particular---cheats the type system
 by giving you access to hidden implementation details.

 I dont want to write a book on why one should want to hide
 implementation details :-) I assume you know about this
 and agree it is proper.

 But the derived type of a base IS PRECISELY implementation
 details of the base class.

 Polymorphism, though virtual functions, provides the
 CORRECT and safe way to access the particular
 implementation of an object---by virtual function calls.

 So ESPECIALLY in the case of pointers to bases,
 casting is unnecessary and bad. There is already
 a proper way to access the derived class.

 If you cant figure how to do what you want via the base
 class---it is a design flaw.
>
>You have stated all this before, and every time you do, you fail to provide
>any examples of how you would design without downcasting.
>
>So, as I asked for earlier, let's have an example of a properly
>designed system.
>

 Sure. But you have to tell me what problem you want to solve,
else I'll just give you a copy of my, um lets see, matrix clas,
or perhaps my network emulator, or perhaps my chemical plant
Quantative Risk Analysis Program.

 Believe me, in the QRA program I was tempted to downcast.
Apparently some problems couldn't be solved without it. But,
because it used dynamic linkage, I COULDN'T cheat by downcasting,
even if I wanted to, since athe main program had no idea
what derived classes would be used (they are loaded dynamically
under user control). By not using downcasting I am
GUARRANTEED that ANY proper implementation of the
various base classes could be used.

 As soon as I put even one downcast in, then that
module MUST be loaded. And that defeats the design spec
of having ALL the components dynamically linkable.

 QRA models hazards at chemical plants. The objects
include hazardous chemicals, and various types of fixtures
like tanks and pipes. The fixtures are 'drawn' on a map
using the mouse.

 Chemicals are put in the containers, and various
leaks occur, fires start, toxic materials are dispersed.
The accumulated affects over a million years are
then plotted on a map, and the results sold to
the chemical companies (for safety reasons, or because
governments require Environmental Impact Statements
to be done).

 The hardest part of this system was to have a few
abstractions representing 'container', 'chemical',
and 'phenomena' (leak, fire, etc).

 The actual numerical modelling of fires was easy,
because the client supplied the algorithm :-)
--
;----------------------------------------------------------------------
        JOHN (MAX) SKALLER,         maxtal@extro.ucc.su.oz.au
 Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------




Author: shang@corp.mot.com (David (Lujun) Shang)
Date: Fri, 24 Jul 92 23:46:28 GMT
Raw View
> >(John MAX Skaller) writes:
>We design our systems properly in the first place, and then the need
>to downcast never arises, when our thoughts wander in that
>direction one can be sure we are not thinking virtuously,
>and perhaps the design itself is flawed.
> ....

In article <1992Jul24.143359.3602@advtech.uswest.com> glw@io.uswest.com (Glenn
Williams) replies:
>
> You have stated all this before, and every time you do, you fail to provide
> any examples of how you would design without downcasting.
>
> So, as I asked for earlier, let's have an example of a properly
> designed system.
>
Indeed. Whenever I gave an example that demostrates the necessity of downcast,
I was always considered to introduce a bad design, but I never get an example
which is considered good and can solve the specific problem shown by my
example. So, before blaming the "bad", show the good please.

Here, I'd like to use the example given by John's own:

> >(John MAX Skaller) writes:
> >class Animal {
> > int mate(Animal&);
> >};
> >class Dog : Animal {
> > int mate (Animal&);
> >};
>
> >We REALLY want Dog::mate(Dog&), but we can't have it.
>

Therefore you need RTTI and downcast, though a little bit too late to check the
validation within the method body:

class Dog : Animal
{     int mate (Animal& a)
      { if (typeof(a) != Dog) return an_error_code;
        ...
      };
}

> >This is a flaw in Object Oriented Programming IMHO.

No. This is not the flaw of OOP. This the problem of the current C++ or some of
other specific OO langauges and we need to solve it.

> >The solution requires multimethods which are inherently
> >functional.

Multimethods can never solve this problem. It would be very absurd to have the
method "mate" multiplxed. We already know that it is impossible to have "cat
mate dog", "fish mate bird", why we still want to try to mate these impossible
conbination?

What we need is the covariant specification: to prevent the wrong combination
by interface:

class Dog : Animal
{     int mate (thisclass&)
      {  ...
      };
}

Therefore, "cat mate dog" will be prohibited by the comipler. This is much
better since we can prohibit the error earlier. RTTI is required when we do not
know the actual type of the animals at compile time:

Animal & a1;
Animal & a2;

if (typeof(a1)==typeof(a2)) a1.mate(a2);

Before you condemn this as a bad design, answer the following question first:

Is it resonable to call "mate" through Animal interface?
--If yes, how do you prevent the wrong matches without RTTI?
--If no, why do you introduce the method "mate" in class Animal?

Now I give you an example that truely needs multiple dispatch.

class Animal
{  Animal * fight (Animal * a)
   {  if (a==0 && this->dead()) retuen 0;  // I use this-> for emphasis
      if (a==0 || a->dead()) return this;
      if (this->dead()) return a;
      if (this->over(a) && a->over(this))
      { this->kill(a);
        a->kill(this);
        retuen 0;
      }
      if (this->over(a))
      { this->kill(a);
        retuen this;
      }
      if (a->over(this))
      { a->kill(this);
        retuen a;
      }
      this->escape();
      a->ascape();
      return 0;
   }
};

and we have the following fights:

Animal * victor;
..
victor = wolf.fight (jackal);
victor = lynx.fight (victor);
victor = bull.fight (victor);
..

You'll suprise me if you say, okay, I don't care who is the winner, anyway,
some animal or none. Then what's the purpose of the above codes? Just for some
thing unknown, unknown forever?  You can say it is a bad design again, as
easily as blowing off dust. But be aware, it is an example of your beloved
multiple dispatch.

Now can you see the fact: both homo. (the example is "mate") and het. (the
example is "fight") data structures need RTTI?

David Shang












Author: jss@lucid.com (Jerry Schwarz)
Date: Sat, 25 Jul 92 02:21:47 GMT
Raw View
In article <1992Jul24.192207.15267@ucc.su.OZ.AU>, maxtal@extro.ucc.su.OZ.AU (John MAX Skaller) writes:
|>
|>  But that wont PROVE anything. You have to give me
|> an example of a system that uses downcasting and then I'll
|> show how to write it without downcasting.
|>
|>  Please restrict examples to domestic systems, since
|> I already agree all bets are off for foreign systems and
|> also for introspective ones like, say, debuggers, OODBMS,
|> etc.
|>
|>  Lets agree for example to restrict ourselves to
|> text input and output and no disk storage. Lets agree
|> that the program must handle a fixed number of types.
|> And that it reads text in, does some calculations,
|> and then prints the answers.
|>
|>  Such a program creates ALL the objects, manipulates
|> them, and then it terminates.
|>

Here is a typical example of my use of downcasting. Downcasting
is not essential in this example, but I think it makes the design
cleaner.

I want to define an "indent stream" derived from ostream.
An indent stream contains an indentation level.

class indentstream : public ostream {
   public:
    int level ;  // Public only to keep example short.
    indentstream(streambuf* sb) : ostream(sb), level(0) { }
} ;

The level is used by a manipulator.

ostream& newline(ostream& os) {
    indentstream* is = CAST<indentstream*>os ;  // notation suggestive
    os.put(\n) ;
    if ( is ) {
        for ( int n = 0 ; n < is->level ; ++n ) is.put('\t') ;
    }
    os.flush() ;
}

Typical uses look like

     indentstream myout(...) ;
     ...
     myout.level++ ;
     // x and y inserters do not have to understand indentation as long
     // as they put everything on a single line.
     myout << x << newline << y << newline ;
     myout.level--

This class isn't perfect, but I hope you get the idea.  More elaborate
methods can be devised that put the newline detection in the
streambuf and avoid newline.  These also require downcasting so
that you can be sure the streambuf is the right type before you tell
it to change level.

|> >
|> >> Inother words, in a *domestic* system, any apparent
|> >>need for downcasting should be used as an indicator of flawed design.
|> >>

indentstream can be done without derivation and without downcasting by use
of the iword facility of streams, but that feature of streams is generally
regarded as baroque and unappealing.  It was included in the stream
interface precisely because safe downcasting wasn't available.
People frequently just go ahead and use an unsafe cast in "newline"
(or its equivalent).

   -- Jerry Schwarz




Author: fjh@munta.cs.mu.OZ.AU (Fergus James HENDERSON)
Date: Sat, 25 Jul 1992 09:04:38 GMT
Raw View
shang@corp.mot.com (David (Lujun) Shang) writes:

>Now I give you an example that truely needs multiple dispatch.
>
>class Animal
>{  Animal * fight (Animal * a)
    // simplified version for brevity
    {  return this->strength() > a->strength ? this : a; }
>};
>
>and we have the following fights:
>
>Animal * victor;
>..
>victor = wolf.fight (jackal);
>victor = lynx.fight (victor);
>victor = bull.fight (victor);
>..
>
>You'll suprise me if you say, okay, I don't care who is the winner, anyway,
>some animal or none. Then what's the purpose of the above codes? Just for
>some thing unknown, unknown forever?  You can say it is a bad design again, as
>easily as blowing off dust. But be aware, it is an example of your beloved
>multiple dispatch.

I don't quite understand you example.
It seems to be incomplete.
Presumeably after all the fights, you are going to do something like

 if (typeof(victor) == typeof(wolf))
  cout << "The wolf won!\n";
 else if (typeof(victor) == typeof(lynx))
  cout << "The lynx won!\n";
 else ...

To me that looks like horrible design.
Why not just have

 cout << "The " << victor->name() << " won!\n";

instead? Then there is no need for downcasting (and you also have a better
design!).

--
Fergus Henderson             fjh@munta.cs.mu.OZ.AU
This .signature VIRUS is a self-referential statement that is true - but
you will only be able to consistently believe it if you copy it to your own
.signature file!




Author: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Date: Sat, 25 Jul 1992 17:20:45 GMT
Raw View
In article <1992Jul24.234628.21196@cadsun.corp.mot.com> shang@corp.mot.com writes:
>> >(John MAX Skaller) writes:
>>We design our systems properly in the first place, and then the need
>>to downcast never arises, when our thoughts wander in that
>>direction one can be sure we are not thinking virtuously,
>>and perhaps the design itself is flawed.
>> ....
>
>In article <1992Jul24.143359.3602@advtech.uswest.com> glw@io.uswest.com (Glenn
>Williams) replies:
>>
>> You have stated all this before, and every time you do, you fail to provide
>> any examples of how you would design without downcasting.
>>
>> So, as I asked for earlier, let's have an example of a properly
>> designed system.
>>
>Indeed. Whenever I gave an example that demostrates the necessity of downcast,
>I was always considered to introduce a bad design, but I never get an example
>which is considered good and can solve the specific problem shown by my
>example. So, before blaming the "bad", show the good please.

 I am working on one. I did give a skeletal outline here
on my reply bu the !@#$%^&*() (sorry) system logged me out,
and this is the second attempt.

>
>Here, I'd like to use the example given by John's own:
>
>> >(John MAX Skaller) writes:
>> >class Animal {
>> > int mate(Animal&);
>> >};
>> >class Dog : Animal {
>> > int mate (Animal&);
>> >};
>>
>> >We REALLY want Dog::mate(Dog&), but we can't have it.
>>
>
>Therefore you need RTTI and downcast,

 No, we dont. I stuffed up the design. (I can do it too :-)

>though a little bit too late to check the
>validation within the method body:
>
>class Dog : Animal
>{     int mate (Animal& a)
>      { if (typeof(a) != Dog) return an_error_code;
>        ...
>      };
>}

 OK. My design is flawed. Why? Because I didnt write the
spec properly.

 Spec1: All animals can have a go at mating.
 If they are of one species (and opposite sex) then
 the female is flagged as pregnant.

This spec REQUIRES the interface:

 void Animal::mate(Animal&);

because the spec says 'All animals can have a go at mating'.

To check if the animals are of the same type, I provide
type information:

 virtual char* Animal::Species()=0;

and the sex:

 enum {male,female,infertile} sex;

Now I can write:

 void Animal::mate(Animal& x)
 {
  if(sex==infertile || x.sex==infertile)return;
  if(strcmp(Species(),x.Species())) return;
  if(sex==x.sex)return;
  if(sex==female) pregnant=TRUE;
  else x.mate(*this);
 }

NOW: I want to demonstrate why using RTTI is WRONG in this case,
and why the Species() function is required.


The demonstration is so simple, and applies so generally, I wonder
why I didnt think of it before.

 class DOG : Animal { ....
 class HUND : Animal { .. // german programmer

See: BOTH classes are IMPLEMENTATIONS of the very same animal
species. But their exact types are different.
So they could never mate. Tell that to German Shepard
and an Alsatian :-)


[Note: I answer the other bits of your post in my previous
attempt butgot logged out, this time I will just have to
pass. For the moment :-)

--
;----------------------------------------------------------------------
        JOHN (MAX) SKALLER,         maxtal@extro.ucc.su.oz.au
 Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------




Author: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Date: Sat, 25 Jul 1992 17:42:52 GMT
Raw View
In article <1992Jul25.022147.24152@lucid.com> jss@lucid.com (Jerry Schwarz) writes:
>
>Here is a typical example of my use of downcasting. Downcasting
>is not essential in this example, but I think it makes the design
>cleaner.

 Perhaps:
 a) stream design is flawed (I happen to think this anyway)
 b) where you use the downcast something else would be
 even better.
>
>I want to define an "indent stream" derived from ostream.
>An indent stream contains an indentation level.

 You would pick streams, which I never use :-)
 But the problem seems to be well specified :-)
>
>class indentstream : public ostream {
>   public:
>    int level ;  // Public only to keep example short.
>    indentstream(streambuf* sb) : ostream(sb), level(0) { }
>} ;
>
>The level is used by a manipulator.
>
>ostream& newline(ostream& os) {
>    indentstream* is = CAST<indentstream*>os ;  // notation suggestive
>    os.put(\n) ;
>    if ( is ) {
>        for ( int n = 0 ; n < is->level ; ++n ) is.put('\t') ;
>    }
>    os.flush() ;
>}
>
>Typical uses look like
>
>     indentstream myout(...) ;
>     ...
>     myout.level++ ;
>     // x and y inserters do not have to understand indentation as long
>     // as they put everything on a single line.
>     myout << x << newline << y << newline ;
>     myout.level--
>
>This class isn't perfect, but I hope you get the idea.  More elaborate
>methods can be devised that put the newline detection in the
>streambuf and avoid newline.  These also require downcasting so
>that you can be sure the streambuf is the right type before you tell
>it to change level.

 I cant comment for sure on this, I dont have enough experience
with streams.

 Perhaps streams have to regarded as a foreign interface,
in which case downcasting might be ok.

 Perhaps streams are badly designed (the flaw is in them, not
 your code).

 And perhaps the fact that you are using a downcast indicates
 that you are cheating the system:

I get one of your streams (but I only know it is an ordinary stream).
I write out a file, then read it back in and ---oh, where
did those tabs come from---there's a BUG in the stream package!

Mm, is there, I say, and look at the class semantics
(I read the source code). No, I say, there is NO WAY that
this can happen, there is NO virtual function that would allow
that freedom.

You see this is an example then of how you could do ANYTHING to
something I thought was an implementation fo a stream,
you have invalidated the semantics by downcasting.

>indentstream can be done without derivation and without downcasting by use
>of the iword facility of streams, but that feature of streams is generally
>regarded as baroque and unappealing.

 And I checked this out and found iword was NOT being fiddled!


>It was included in the stream
>interface precisely because safe downcasting wasn't available.

 Perhaps it was provided because ANY casting is bad news :-)

>People frequently just go ahead and use an unsafe cast in "newline"
>(or its equivalent).
>   -- Jerry Schwarz

Your example may be a valid use of downcasting, I'm sorry that
I just dont know enough about streams to argue it.

--
;----------------------------------------------------------------------
        JOHN (MAX) SKALLER,         maxtal@extro.ucc.su.oz.au
 Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------




Author: rhbartel@watcgl.waterloo.edu (Richard Bartels)
Date: Sat, 25 Jul 1992 19:10:38 GMT
Raw View
As long as the downcasting discussion is open again,
the example we are confronting which requires something like
that, we think, is the issue of input from disk.

I would love to have a solution that did not require the cop-out of
isA() and cast/convert.

Consider the classic "List" class and "ListNode" class.
ListNode is a base class, and assume there are two flavors
of derived node.  We write a List that contains a mix of
these nodes out to a file; now we want to read the List back
in and reconstruct it.

Can it be done without resorting to an isA() member function,
some member data to identify node type, and possibly some
casting/conversion?

What's the authoritative answer?

-Richard





Author: shang@corp.mot.com (David (Lujun) Shang)
Date: Mon, 27 Jul 92 14:50:18 GMT
Raw View
In article <1992Jul25.172045.24675@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU
(John MAX Skaller) writes:
>
>  OK. My design is flawed. Why? Because I didnt write the
> spec properly.
>
>  Spec1: All animals can have a go at mating.
>  If they are of one species (and opposite sex) then
>  the female is flagged as pregnant.
>
Your spec. is still wrong. The proper spec. should be:

  Spec.: All animals of the same species can have a go at mating.

> This spec REQUIRES the interface:
>
>  void Animal::mate(Animal&);
>

This spec REQUIRES the interface:

        void Animal::mate(thiscalss&);

> because the spec says 'All animals can have a go at mating'.
>
because the spec should prohibit possible mismatch as much as possible.

> To check if the animals are of the same type, I provide
> type information:
>
>  virtual char* Animal::Species()=0;
>
Here you provide your own RTTI! We can always do that when we need RTTI.

> and the sex:
>
>  enum {male,female,infertile} sex;
>
> Now I can write:
>
>  void Animal::mate(Animal& x)
>  {
>   if(sex==infertile || x.sex==infertile)return;
>   if(strcmp(Species(),x.Species())) return;
>   if(sex==x.sex)return;
>   if(sex==female) pregnant=TRUE;
>   else x.mate(*this);
>  }
>
If we can provide a presice specification, why should we leave so many troubles
to the implementation?! If the implementor happened to forget to check the
species, what is the result?! And in most cases, we can check the species at
compile-time, why should we leave it to run-time?

What I asked above just illustrates the difference of your own RTTI and the
language supported RTTI. The latter is much better and safer.

> NOW: I want to demonstrate why using RTTI is WRONG in this case,
> and why the Species() function is required.
>
Remenber, your function Species() IS JUST FOR THE PURPOSE TO PROVIDE RTTI.
>
> The demonstration is so simple, and applies so generally, I wonder
> why I didnt think of it before.
>
>  class DOG : Animal { ....
>  class HUND : Animal { .. // german programmer
>
> See: BOTH classes are IMPLEMENTATIONS of the very same animal
> species. But their exact types are different.
> So they could never mate. Tell that to German Shepard
> and an Alsatian :-)
>
This is never an execuse. If it is, your Species() function has the same
problem:

  char* DOG::Species()
        { return "DOG"; }
  char* DOG::Species()
        { return "HOUND"; }   // german programmer

I'm rather confused: first, you provide your own RTTI function for this case,
then you try to prove that using RTTI is wrong. What on earth are your trying
to say?

David Shang






Author: shang@corp.mot.com (David (Lujun) Shang)
Date: Mon, 27 Jul 92 16:14:41 GMT
Raw View
> shang@corp.mot.com (David (Lujun) Shang) writes:
>
> >Now I give you an example that truely needs multiple dispatch.
> >
> >class Animal
> >{  Animal * fight (Animal * a)
>     // simplified version for brevity
>     {  return this->strength() > a->strength ? this : a; }
> >};
> >
> >and we have the following fights:
> >
> >Animal * victor;
> >..
> >victor = wolf.fight (jackal);
> >victor = lynx.fight (victor);
> >victor = bull.fight (victor);
> >..
> >.....

In article <9220719.894@mulga.cs.mu.OZ.AU> fjh@munta.cs.mu.OZ.AU (Fergus James
HENDERSON) replies:

> I don't quite understand you example.
> It seems to be incomplete.
> Presumeably after all the fights, you are going to do something like
>
>  if (typeof(victor) == typeof(wolf))
>   cout << "The wolf won!\n";
>  else if (typeof(victor) == typeof(lynx))
>   cout << "The lynx won!\n";
>  else ...
>
> To me that looks like horrible design.
> Why not just have
>
>  cout << "The " << victor->name() << " won!\n";
>
> instead? Then there is no need for downcasting (and you also have a better
> design!).

Sorry that I did not complete my program, which caused confusion. I'm going to
write something like:

        count << victor->name() << ", the " << type_name(typeof(*victor))
              << " won!\n";

[note that from the animal name, you may not know what kind of the animal is]
the result may be:

        Pooh, the bear won!

or, I may write:

        bestow_on (victor).

where bestow_on is a function which gives a female peer to the victor (suppose
only the male can fight):

       void bestow_on (Animal * victor)
       {    Animal * f = new typeof(*victor)(female));
            // suppose we need do some work for the female here
            // ...
            victor->mate(f); // type inference here ensure that f is the
                             // correct type and the downcast is safe
       };

here both RTTI and downcast are needed, though I did not write an explicit
downcast which is actually happen in the call of "mate":

        victor->mate( (typeof(victor)) f);

since the interface of mate specifies the input of animal must be the same type
of animal as the victor [see my previous poster]:

       void Animal::mate (thisclass *);

The downcast is performed implicitly and checked by the compiler to ensure each
downcast is CORRECT and SAFE at run time. For example, if write:

       Animal * f = new bear;
       dog->mate(f);

The compiler won't let you pass the above program because when you call
dog->mate(f), f is the type of bear. The (implicit) downcast cannot be
performed. The compiler won't let you pass either if it cannot infer whether it
is the correct type, in this case, the programmer must present an explicit
run-time type check to ensuure the correct downcast:

      if (typeof(*f)==Dog) dog->mate(f);

However, if you write an explicit downcast here:

       dog->mate((Dog*) f);

that means you are deliberately to break the type system. The compiler will
give you a warning when it either knows the downcast is not correct or does not
know whether it is correct.

I wonder if you can figure out a better design that needs not RTTI and downcast
for "bestow_on" and "mate". Downcast may not needed if you provide your own
virtual function for the type id, say "Species()" as suggested by John. But I
don't think it is a better design because it is not safe for the first two of
following three reasons and not efficeint for the last reason:

(1) You must remember to override the virtual function. If you forget, errors
happen. The compiler has no way the essure a correct type id distribution.

(2) You must remember to run-time check of the type id. The compiler has no way
to help you to enssure the correct match. If you forget to check, you produce
the error.

(3) You let down the run time performance since you have much work of type
checking that can be done at compile time done at run time.

Downcast is not safe only when the compiler can not ensure that the downcast
will not produce a cast between two different types. With language supported
RTTI, all language supported automatic downcast will be deadly safe.

David Shang




Author: shang@corp.mot.com (David (Lujun) Shang)
Date: Mon, 27 Jul 92 17:02:55 GMT
Raw View
> In article <1992Jul24.143359.3602@advtech.uswest.com> glw@io.uswest.com
(Glenn Williams) writes:
> >
> >Ok, let's have an example of a properly designed system.

In article <1992Jul24.192207.15267@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU
(John MAX Skaller) replies:
>
>  But that wont PROVE anything. You have to give me
> an example of a system that uses downcasting and then I'll
> show how to write it without downcasting.
>
Your example "mate" is already a good example! And you did not provide a good
solution without downcasting. Using virtual function "Species()" can certainly
eliminates the necessity of downcast, but it introduces severe problems that a
safe downcast would never have.

First of all, you must remember to override the virtual function in each
derived class, and give the proper return value. If a forget to override, or if
a give a name that happened to be the same name already exists, you make a
trouble. The compiler has no way to help you to establish a proper name
distribution.

Secondly, you must remember to run time check the character strings returned by
the virtual function. If you forget, you may put a cat and a rat together! The
compiler has no way to help you essure the correct match!

Lastly, you leave much checking work that should be done at compile time to be
done at run time. On one head, you let down the run time performance. On the
other head, you leave errors that should be detected at compile time to the run
time.

Type safe downcast with RTTI will not have the above problems:

Firstly, the compiler will generate a proper type identifier for each type. The
detailed type information can be stored in files generated by the compiler, and
the library can provide a set of suitable functions to access these detail type
information through the type id. Howerver, for the purpose of downcast, only
the simple type id is needed. The programmer is never bothered by overriding a
virtual function that returns a character string or an interger that serves as
the type id. No error will be concerned about a mis-produced type id at run
time.

Secondly, If you forget to run time type check a type id for a type that is
unknown to the compiler, the compiler will tell you there is a potential error
in your program. Thus, if you pass your program, no error will be concerned
about a mis-match, and the proper downcast will be automaticaly performed (the
programmer is not required to write the explicit downcast) at run time.

Lastly, much check work can be done at compile time. When the type is known to
the compiler, the compiler will detect all the mismatch before the program is
put into running. For example: the compiler will not let "cat->mate(rat)" pass.
This is an evident error, why should we leave it to run time?

>  Please restrict examples to domestic systems, since
> I already agree all bets are off for foreign systems and
> also for introspective ones like, say, debuggers, OODBMS,
> etc.
>
Your "mate" example is definitely a domestic system.

>  Sure. But you have to tell me what problem you want to solve,
> else I'll just give you a copy of my, um lets see, matrix clas,
> or perhaps my network emulator, or perhaps my chemical plant
> Quantative Risk Analysis Program.
>
I want a version of "mate" that does not use downcast and works as well as (I
even do not require the better!) the TYPE SAFE downcast as I suggested (do not
confuse my argument with unsafe downcast, I'm stronly against unsafe downcast).

David Shang





Author: tmb@arolla.idiap.ch (Thomas M. Breuel)
Date: 28 Jul 92 16:28:57 GMT
Raw View
In article <1992Jul27.161441.7898@cadsun.corp.mot.com> shang@corp.mot.com (David (Lujun) Shang) writes:

   I may write:

    bestow_on (victor).

   where bestow_on is a function which gives a female peer to the victor (suppose
   only the male can fight):

   void bestow_on (Animal * victor)
   {    Animal * f = new typeof(*victor)(female));
        // suppose we need do some work for the female here
        // ...
        victor->mate(f); // type inference here ensure that f is the
    // correct type and the downcast is safe
   };

   here both RTTI and downcast are needed, though I did not write an explicit
   downcast which is actually happen in the call of "mate":

They are not "needed". You can simply make "bestow_on" a virtual
member function of "Animal" and have it do the right thing for each
kind of animal.

Is this pedestrian? You bet it is, but no more so than many other
aspects of C++ (e.g., memory management has to be handled on a
per-class basis, value semantics have to re-implemented on a
case-by-case basis, etc.).

If you think you really need those features, there is always CLOS...

     Thomas.




Author: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Date: Tue, 28 Jul 1992 18:37:46 GMT
Raw View
In article <BryL9q.K5I@watcgl.waterloo.edu> rhbartel@watcgl.waterloo.edu (Richard Bartels) writes:
>As long as the downcasting discussion is open again,
>the example we are confronting which requires something like
>that, we think, is the issue of input from disk.
>
>I would love to have a solution that did not require the cop-out of
>isA() and cast/convert.
>
>Consider the classic "List" class and "ListNode" class.
>ListNode is a base class, and assume there are two flavors
>of derived node.  We write a List that contains a mix of
>these nodes out to a file; now we want to read the List back
>in and reconstruct it.
>
>Can it be done without resorting to an isA() member function,
>some member data to identify node type, and possibly some
>casting/conversion?

 a) without some type information, it cant be done.
 b) without casting it can (and should :-) be done

 union hetero {
  type1 *p1;
  type2 *p2;
 };


And the list is homogeneous--all the elements are of type 'hetero'.

>
>What's the authoritative answer?

 Dont know. Who's the authority?



--
;----------------------------------------------------------------------
        JOHN (MAX) SKALLER,         maxtal@extro.ucc.su.oz.au
 Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------




Author: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Date: Tue, 28 Jul 1992 21:45:28 GMT
Raw View
In article <1992Jul27.170255.8185@cadsun.corp.mot.com> shang@corp.mot.com writes:
>> In article <1992Jul24.143359.3602@advtech.uswest.com> glw@io.uswest.com
>(Glenn Williams) writes:
>> >
>> >Ok, let's have an example of a properly designed system.
>
>In article <1992Jul24.192207.15267@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU
>(John MAX Skaller) replies:
>>
>>  But that wont PROVE anything. You have to give me
>> an example of a system that uses downcasting and then I'll
>> show how to write it without downcasting.
>>
>Your example "mate" is already a good example! And you did not provide a good
>solution without downcasting. Using virtual function "Species()" can certainly
>eliminates the necessity of downcast, but it introduces severe problems that a
>safe downcast would never have.
>
>First of all, you must remember to override the virtual function in each
>derived class, and give the proper return value. If a forget to override, or if
>a give a name that happened to be the same name already exists, you make a
>trouble. The compiler has no way to help you to establish a proper name
>distribution.

 The first is WRONG. Havent you ever head of PURE VIRTUAL FUNCTIONS????

 The second is a CONSEQUENCE of being able to
 chose the species name for each class. Thats
 DESIRABLE if you want to support multiple
 implementation of the same class.

>
>Secondly, you must remember to run time check the character strings returned by
>the virtual function. If you forget, you may put a cat and a rat together! The
>compiler has no way to help you essure the correct match!

 The proposed RTTI downcast is WORSE: it return a NULL POINTER
if the cast fails due to type mismatch on pointers.

>
>Lastly, you leave much checking work that should be done at compile time to be
>done at run time.

 In this case maybe thats true. I dont see how
the proposed RTTI/downcast is any different. It may have slightly
better optimistaion oportunities, like all built in features.


>On one head, you let down the run time performance. On the
>other head, you leave errors that should be detected at compile time to the run
>time.
>
>Type safe downcast with RTTI will not have the above problems:
>
>Firstly, the compiler will generate a proper type identifier for each type. The
>detailed type information can be stored in files generated by the compiler, and
>the library can provide a set of suitable functions to access these detail type
>information through the type id. Howerver, for the purpose of downcast, only
>the simple type id is needed. The programmer is never bothered by overriding a
>virtual function that returns a character string or an interger that serves as
>the type id. No error will be concerned about a mis-produced type id at run
>time.

 WRONG. You get a system crash if you dont check for 0 pointer.
--
;----------------------------------------------------------------------
        JOHN (MAX) SKALLER,         maxtal@extro.ucc.su.oz.au
 Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------




Author: jrobie@netmbx.netmbx.de (Jonathan Robie)
Date: Wed, 29 Jul 1992 07:48:19 GMT
Raw View
chip@tct.com (Chip Salzenberg) writes:

>But once you've frozen a C++ object into a dead array of bytes, it's
>no longer a C++ object, so the C++ type system is no longer operative.

>So RTTI and persistence are only marginally related subjects.

RTTI is needed to properly freeze a C++ object into a "dead array of
bytes" and to turn a "dead array of bytes" into a C++ object.  It is
also needed to follow references among objects in RAM.

Unfortunately, the proposed standard will not provide enough info to
do this, according to Mat Mol.  He referred to a second standard for
extended type information that might have enough infor but which he
suggests might not make it into the standard.  I am trying to get my
hands on these proposals, but it may take a few weeks...

RTTI and persistence are related enough that all of the major C++
database systems use precompilers to get at it.  Since a precompiler
requires real programming effort I don't think we would do that if
it weren't needed...



Jonathan

===========================================================================

Jonathan Robie  jrobie@netmbx.UUCP
Arnold-Zweig-Str. 44 jrobie@netmbx.in-berlin.de
O-1100 Berlin
Deutschland  Phone:  +37 (2) 472 04 19  (Home, East Berlin)
    +49 (30) 342 30 66 (Work, West Berlin)


--
Jonathan

===========================================================================





Author: shang@corp.mot.com (David (Lujun) Shang)
Date: Wed, 29 Jul 92 16:24:40 GMT
Raw View
In article <1992Jul28.210755.3548@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU (John
MAX Skaller) writes:
> >
> >> NOW: I want to demonstrate why using RTTI is WRONG in this case,
> >> and why the Species() function is required.
> >>
> >Remenber, your function Species() IS JUST FOR THE PURPOSE TO PROVIDE RTTI.
>
>  Yes. I'm not against having run-time-type information
> available.

See your original statement -- what you want to demonstrate.

> >>
> >> The demonstration is so simple, and applies so generally, I wonder
> >> why I didnt think of it before.
> >>
> >>  class DOG : Animal { ....
> >>  class HUND : Animal { .. // german programmer
> >>
> >This is never an execuse. If it is, your Species() function has the same
> >problem:
> >
> >  char* DOG::Species()
> >        { return "DOG"; }
> >  char* DOG::Species()
> >        { return "HOUND"; }   // german programmer
>
>  What?? The compiler catches this error: it is a duplicate
> definition.
>
The class DOG can be declared as static in different modules and it is highly
possible if an Australian and a German work for the same project that is
separated into many different modules.

> Actually, I think downcasting is OK when you already knew what the
> type was, or could have. Then it is just a convenience. And safe.
>
Then we have not disagreement any more.


David Shang





Author: shang@corp.mot.com (David (Lujun) Shang)
Date: Wed, 29 Jul 92 16:05:46 GMT
Raw View
In article <1992Jul28.214528.6360@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU (John
MAX Skaller) writes:
> In article <1992Jul27.170255.8185@cadsun.corp.mot.com> shang@corp.mot.com
writes:
> >First of all, you must remember to override the virtual function in each
> >derived class, and give the proper return value. If a forget to override, or
> >if a give a name that happened to be the same name already exists, you make
> >a trouble. The compiler has no way to help you to establish a proper name
> >distribution.
>
>  The first is WRONG. Havent you ever head of PURE VIRTUAL FUNCTIONS????
>
Of cause I have. But remember, once you override a pure virtual function in a
derived class, you may have further classes derived from the derived class
where "Species" is no longer pure virtual! In fact I've already used what you
suggested in my C++ programs, and I do have the trouble.

> >Secondly, you must remember to run time check the character strings returned
> >by the virtual function. If you forget, you may put a cat and a rat
> >together! The compiler has no way to help you essure the correct match!
>
>  The proposed RTTI downcast is WORSE: it return a NULL POINTER
> if the cast fails due to type mismatch on pointers.
>
No. The RTTI I suggested does not return any NULL pointers. Quite simple and
safe, the compiler does not let you program pass if there is a potential type
error. It seems that you did not read what I wrote carefully.
> >
> >Lastly, you leave much checking work that should be done at compile time to
> >be done at run time.
>
>  In this case maybe thats true. I dont see how
> the proposed RTTI/downcast is any different. It may have slightly
> better optimistaion oportunities, like all built in features.
>
Not just slightly. The compiler can know the specific type in many cases and
hence can detect the type error at compile time. For those types unknown to the
compiler, the compiler can also detect the type error through RTTI.
>
> >Type safe downcast with RTTI will not have the above problems:
> >
>  WRONG. You get a system crash if you dont check for 0 pointer.
>
Angain, RTTI has nothing to do with null pointer. To return a null pointer for
an RTT error is an irresponsible manner. At least, an exception should be
raised for the error. I'm even not satisfied with the exception handling
because you leave type error at run time! What I suggested is to detect all the
type errors at compile time just by explicit run time type check. Sounds
strange? I've mentioned this several times in my previous posters and examples
are also given and I'm tired of repeating.

David Shang


 --
> ;----------------------------------------------------------------------
>         JOHN (MAX) SKALLER,         maxtal@extro.ucc.su.oz.au
>  Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
> ;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------




Author: mat@mole-end.matawan.nj.us
Date: Wed, 29 Jul 1992 21:00:19 GMT
Raw View
In article <2TJ53BQ@netmbx.netmbx.de>, jrobie@netmbx.netmbx.de (Jonathan Robie) writes:
> chip@tct.com (Chip Salzenberg) writes:

> >But once you've frozen a C++ object into a dead array of bytes, it's
> >no longer a C++ object, so the C++ type system is no longer operative.

> >...
> Unfortunately, the proposed standard will not provide enough info to
> do this, according to Mat Mol.  ...

I ain't Mat and I ain't nobody's moll!  I got a poifekly good .sig down
below and youse guys oughta loin ta read it.

>        ....  He referred to a second standard for
> extended type information that might have enough infor but which he
> suggests might not make it into the standard.  I am trying to get my
> hands on these proposals, but it may take a few weeks...

The Extended Type Information proposal is only in its formative stages now.
ETI will be done if it is practical.
--
 (This man's opinions are his own.)
 From mole-end    Mark Terribile

 mat@mole-end.matawan.nj.us, Somewhere in Matawan, NJ




Author: shang@corp.mot.com (David (Lujun) Shang)
Date: Thu, 16 Jul 92 23:59:52 GMT
Raw View
In article <1992Jul16.132749.8407@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU (John
MAX Skaller) writes:
>  C++ is not 'a better LISP' or 'a better Smalltalk'.
> I guess you have to weigh up the advantages and disadvantages.
> Perhaps you could prototype in one language and code the
> final product in the other :-)
>

Languages that stick to static type checking and languages without types like
SmallTalk are two extremes. The formmer is good for developing commercial
products which should be as error-free as possible at run-time. The later is
good for rapid prototyping. A prototype should be finally refined to a product.
Unfortunately, it is not a good idea to develop the prototype in SmallTalk and
then shift the prototype into another language like C++.

It is necessary to have a single language to do the both. The langauge should
be scalable between the two extremes. When the scale is adjusted to the static
extreme, there should be no overhead (at least no significant overhead) that
enforced by the requirement of the dynamic stuff. And when the scale is
adjusted to the dynamic extreme, there should be no limitation that enforced by
the static stuff.

David Shang





Author: shang@corp.mot.com (David (Lujun) Shang)
Date: Fri, 17 Jul 92 14:02:00 GMT
Raw View
In article <TMB.92Jul16223822@arolla.idiap.ch> tmb@arolla.idiap.ch (Thomas M.
Breuel) writes:
>
> Frankly, I don't see why the language should make more effort to have
> built-in support for dynamic typing (a feature that most people use
> relatively rarely) before it has built-in support for arrays (meaning,
> array bounds checking and multidimensional arrays).
>

The language should provide run time type checking as well as array bounds
checking. You can also have a switch to turn off them if you like to.

>    > The language cannot prevent "accidents"
>    > while still allowing valid uses.
>
>    The current C++ can't.
>
> Actually, no language that uses dynamic typing (as you suggest) to
> implement heterogeneous collections can "prevent 'accidents' while
> still allowing valid uses".  It is in the nature of dynamic typing
> that runtime type errors can occur (although they need not lead to the
> catastrophic consequences they leed to in C++).
>

The run time type checking that I suggested is different from those applied in
dynamic typing languages where a type error is caught at run time. My proposal
is specially made for strongly typed language (any type error should be caught
at compile time ). There is no automatic run time type check. Programmers are
responsible to write the run time type check code when they try to do something
which usually violates the conventional static type system, for example, they
try to use type downcast. The explicit run time type check will enssure that
run time type error will NEVER happen, while this can be checked at compile
time through type inference.

> SML in my opinion solves the problem more satisfactorily than either
> Lisp or C++: it forces you to make sure that you have at least thought
> about all the different cases of types that may occur when you build
> heterogeneous collections. It does this by requiring you to declare
> tagged union types for the objects that can occur in a heterogeneous
> collection, and by warning you if not all the possible type cases of a
> union type are handled.
>

This is also incorporated in my proposal, though I did not memtioned. Take an
example of class Number and its operator+, x+y may return an object of either
type of x (thisclass) or type of y. Therefore, the operator+ should be defined
as <Number InputType> Number{thisclass,InputType} operator+(InputType y); The
output type is a union type tagged with Number and a list of possible type
cases, i.e. Number{thisclass,InputType}.

> This is not quite as general as what Lisp provides, but the
> differences are only important when new datatypes can be created after
> the program has been started (which is not a problem in C++, since C++
> development is non-incremental). Furthermore, providing Lisp-style
> dynamic typing breaks encapsulation and abstraction, which you may or
> may not care about.
>

I never advocate Lisp-style dynamic typing for strongly typed languages.

David.





Author: shang@corp.mot.com (David (Lujun) Shang)
Date: Fri, 17 Jul 92 23:17:00 GMT
Raw View
In article <1992Jul17.120013.9296@irisa.fr> jezequel@irisa.fr (Jean-Marc
Jezequel) writes:
> Indeed, if you want to put heterogeneous objects in the same collection,
> there is usaully a very good reason for that (you want to perform a given set
> of operation on each element, or something like that). Then the solution is
> simple: build a Class common which factorize the common  behavior of the >
> collection elements,  defining all its methods as virtual. Then make every
> collection element inherit from common, and redefine there the common methods
> to match the expected behavior. Dynamic binding makes it work.
>

Then, once you put an object into a heterogeneous collection, the object will
lose its specific characteristics forever. If you put a dog into a animal
collection, you can never recognize it as a dog.

Suppose that your system live on an object server, once you put an object into
the server, you'll never get it back as the original one. Now, are you willing
to rely your system on this object server?


David Shang





Author: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Date: Fri, 17 Jul 1992 17:53:30 GMT
Raw View
In article <1992Jul16.235952.10269@cadsun.corp.mot.com> shang@corp.mot.com writes:
>In article <1992Jul16.132749.8407@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU (John
>MAX Skaller) writes:
>>  C++ is not 'a better LISP' or 'a better Smalltalk'.
>> I guess you have to weigh up the advantages and disadvantages.
>> Perhaps you could prototype in one language and code the
>> final product in the other :-)
>>
>
>Languages that stick to static type checking and languages without types like
>SmallTalk are two extremes. The formmer is good for developing commercial
>products which should be as error-free as possible at run-time. The later is
>good for rapid prototyping. A prototype should be finally refined to a product.
>Unfortunately, it is not a good idea to develop the prototype in SmallTalk and
>then shift the prototype into another language like C++.
>
>It is necessary to have a single language to do the both. The langauge should
>be scalable between the two extremes. When the scale is adjusted to the static
>extreme, there should be no overhead (at least no significant overhead) that
>enforced by the requirement of the dynamic stuff. And when the scale is
>adjusted to the dynamic extreme, there should be no limitation that enforced by
>the static stuff.
>
>David Shang

 I will perhaps not disagree with this.
 Rapid prototyping IS an argument for run-time facilities.

 NEED for het. aggregates is not.




--
;----------------------------------------------------------------------
        JOHN (MAX) SKALLER,         maxtal@extro.ucc.su.oz.au
 Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------




Author: maxtal@extro.ucc.su.OZ.AU (John MAX Skaller)
Date: Sat, 18 Jul 1992 07:55:02 GMT
Raw View
In article <1992Jul17.231700.17298@cadsun.corp.mot.com> shang@corp.mot.com writes:
>In article <1992Jul17.120013.9296@irisa.fr> jezequel@irisa.fr (Jean-Marc
>Jezequel) writes:
>> Indeed, if you want to put heterogeneous objects in the same collection,
>> there is usaully a very good reason for that (you want to perform a given set
>> of operation on each element, or something like that). Then the solution is
>> simple: build a Class common which factorize the common  behavior of the >
>> collection elements,  defining all its methods as virtual. Then make every
>> collection element inherit from common, and redefine there the common methods
>> to match the expected behavior. Dynamic binding makes it work.
>>
>
>Then, once you put an object into a heterogeneous collection, the object will
>lose its specific characteristics forever. If you put a dog into a animal
>collection, you can never recognize it as a dog.

 Of course. Thats the way is SHOULD BE. Algorithms that
operate on animals MUST NOT KNOW about the type of the animals.
If this is the case, you can add new types of animals and the
algorithm keeps working. Otherwise your algorithm fails.
Thats why it MUST not know. If it did, you would not be
able to add new animals.

 This is the OPEN/CLOSED principle, it explains
WHY you can't and shouldn't downcast.
>
>Suppose that your system live on an object server, once you put an object into
>the server, you'll never get it back as the original one. Now, are you willing
>to rely your system on this object server?
>
 Why would I use such a stupid server that forgot the types
of the objects? If I give the server a Mammal and say, 'here is an
object, it is a Mammal' I expect the SERVER to remember that fact.
NOT to forget and have me rely on asking the object 'OH, object,
what type are you?'

>
>David Shang
>


--
;----------------------------------------------------------------------
        JOHN (MAX) SKALLER,         maxtal@extro.ucc.su.oz.au
 Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------




Author: shang@corp.mot.com (David (Lujun) Shang)
Date: Mon, 20 Jul 92 14:17:12 GMT
Raw View
In article <1992Jul17.154635.27331@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU
(John MAX Skaller) writes:
>  Certainly there are het aggregates out there, say
> Windows 3.0 window lists. But the can be converted to C++
> objects without loss of type info: they don't have to
> be collected in the same form as they are presented to
> you.
>

You need, at least, a pre-compiler to analize the type system and gather the
information for this case. Let me give you some more examples. (1) OO operating
systems written in C++: most systems must have their own pre-compiler that
gathers the type information in order to provide a high level OO user
interface. (2) OO database systems: they must have their own languages, at
least a pre-compiler if they chose C++ as their master language, and most of
the work for the pre-compiler is to gather the type information. And see
another good example that Jonathan Robie gives us:

In article <5W0422J@netmbx.netmbx.de> jrobie@netmbx.netmbx.de (Jonathan Robie)
writes:
> Think of stream I/O.  Don't you feel stupid coding an << operator for
> each type you want to be able to print?  An intelligent stream facility
> would be able to print any object with a sensible default format which
> could optionally include the name and type of each field.  Try to code
> this without run time information.  Since the I/O stream is coded first
> and can impose no restrictions on user objects I do not know of any way
> to solve this without adding my own precompiler.
>

In the same article <1992Jul17.154635.27331@ucc.su.OZ.AU>
maxtal@extro.ucc.su.OZ.AU (John MAX Skaller) writes:
> Lets take feeding of animals. You must spec. the problem better.
> Does each animal know the types (finite list) of food they eat?
> E.g, you have list of base classes for each animal.
>
> Now you have list of animals and list of foods, and feed each
> available food to animal until food is eaten. Is this
> what you want?
>
>

No, never. What I want is a spec.-- a spec. of a food type which is dependent
on the animal type, and such dependency can be checked statically at compile
time.

David Shang




> --
> ;----------------------------------------------------------------------
>         JOHN (MAX) SKALLER,         maxtal@extro.ucc.su.oz.au
>  Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
> ;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------




Author: jcm@hgc.edu (James McKim)
Date: Mon, 20 Jul 1992 14:39:00 GMT
Raw View
In article <1992Jul17.231700.17298@cadsun.corp.mot.com> shang@corp.mot.com writes:
>In article <1992Jul17.120013.9296@irisa.fr> jezequel@irisa.fr (Jean-Marc
>Jezequel) writes:
>> Indeed, if you want to put heterogeneous objects in the same collection,
>> there is usaully a very good reason for that (you want to perform a given set
>> of operation on each element, or something like that). Then the solution is
>> simple: build a Class common which factorize the common  behavior of the >
>> collection elements,  defining all its methods as virtual. Then make every
>> collection element inherit from common, and redefine there the common methods
>> to match the expected behavior. Dynamic binding makes it work.
>>
>
>Then, once you put an object into a heterogeneous collection, the object will
>lose its specific characteristics forever. If you put a dog into a animal
>collection, you can never recognize it as a dog.

Well, as someone else pointed out, the reason for constructing
the collection in the first place is that you are interested in
features that all animals have. However, you have not lost the
specific features of the dog object for two reasons:

  1) Through the magic of polymorphism, if I ask for the number of
   legs of an animal that happens to be a dog, I'll get 4; of a
   robin, 2; of an ant, 6; of a snake, 0.

  2) Many languages allow rediscovery of a type. In Eiffel, if I
   have a list L of animals and I want to check whether the third
   animal is a dog, I can do so by declaring d : DOG and use a
   reverse assignment attempt:

       d ?= L.item( 3 ) ;

   If the third item is a dog I now have a reference to it of type DOG,
   otherwise d is Void.
>
>Suppose that your system live on an object server, once you put an object into
>the server, you'll never get it back as the original one. Now, are you willing
>to rely your system on this object server?

If I store a bunch of animals on the disk, I'm probably doing so
because I don't expect that I will need to access features specific
to a dog. If I'm wrong, I can recover the dogs as above.

>
>
>David Shang
>

Regards,
-- Jim
--

*------------------------------------------------------------------------------*
Jim McKim  (203)-548-2458     In exactly 3.2 seconds it will be a few
Internet:  jcm@hgc.edu   minutes to 5:00.




Author: ericg@ucschu.ucsc.edu (Eric Goodman)
Date: 20 Jul 92 21:46:50 GMT
Raw View
In article <1992Jul17.231700.17298@cadsun.corp.mot.com> shang@corp.mot.com
(David (Lujun) Shang) writes:
> Then, once you put an object into a heterogeneous collection, the object
> will lose its specific characteristics forever. If you put a dog into a
> animal collection, you can never recognize it as a dog.

There are plenty of instances where this wouldn't be a problem. For
example, let's say I'm writing code for a user interface with windows,
buttons, menus, text fields, etc. In my example  the only way objects get
acted upon is by being clicked on/passed over/etc. with the mouse. The
interface class (in this example) can safely create a list of
"interfaceObjects", where interfaceObjects is a(n abstract) base class for
classes menu, window, button, etc, with appropriate virtual functions such
as respondToClick(), respondToRelease(), dtor, etc.

Since the virtual functions know what types of objects they are operating
on it is actually not important (from the interface class' point of view)
to know anything about the specific types of objects in this collection.
The objects in the collection are still free to communicate among
themselves using the specifics of their interfaces that are known (for
instance, a menu title/button can pass messages to a menu like
"showMenu()" ), but these details are transparent to the system. The
objects (again, from the interface class' point of view) have lost their
specific characteristics (forever) and its no problem - its part of the
design.

> Suppose that your system live on an object server, once you put an
object into
> the server, you'll never get it back as the original one.

Here you'd have to have some sort of "copyMe()" function that encapsulates
the appropriate constructor, but you would still get the original object
back.

Of course you wouldn't do any of this if because of the situation the
actual type of the object was important, but - as pointed out elsewhere -
that's what polymorphism is all about: the ability to NOT know what type
something is and still use it.

Some more general comments:
A bunch of messages in this thread have commented about how unusual it is
to need heterogeneous lists as though it's a proven fact. I don't accept
this axiom. Any time there are two sufficiently dissimilar objects there
is the potential for them to be of dissimilar types. Is a circle of the
same class as a *filled* circle with a "filled" flag set, or is it a
different type altogether? The answer is it depends.

Flags, hidden pointers etc. are so strongly ingrained in most C
programmers that I suspect many opportunities for (reasonable) new classes
pass unnoticed because it is so "obvious" to us (and I do include myself
in "us") that the difference is e.g. "just a flag", even though that flag
being set may completely change the nature of the object. Of course, for
procticality's (oops! "practicality's", but I like the typo :-) ) and
efficiency's sake we often go with the flags, but there are times when new
classes lead to a "better" (more extensible, less complex and comparable
efficiency) implementation.

Ultimately, if you require that all collections be homogenous, then you're
still left creating your own tables of functions or switch statments or
flag checkers or whatever to make sufficiently different objects (appear
to) be members of the "same" class.  In many cases creating such an
implementation is going counter to object-oriented design philosophy. This
is not necessarily *bad*, just not object-oriented. The decision affects
the efficiency, portability, readability, extensibility and
maintainability of the code, however, and those have to be considered as
well as the "compile time control" of the program (that is, the
programmer's ability to control all aspects of the program's behaviour
directly through control structures).

Eric Goodman
Humanities Computing
UC Santa Cruz
ericg@hum.ucsc.edu  or  @ucschu.bitnet




Author: shang@corp.mot.com (David (Lujun) Shang)
Date: Mon, 20 Jul 92 21:50:58 GMT
Raw View
In article <1992Jul20.143900.23087@merlin.hgc.edu> jcm@hgc.edu (James McKim)
writes:
> If I store a bunch of animals on the disk, I'm probably doing so
> because I don't expect that I will need to access features specific
> to a dog.

I belive many OO database users are much more critical than you.

> If I'm wrong, I can recover the dogs as above.
>

You can recover it only it is stored in a heterogeneous collection of which
some one disagree on the usefulness, or even the exisitence.

In fact, we do not have fundamental disareements. First of all, I agree to your
argument that the reason for constructing the collection in the first place is
that you are interested in features that all animals have. Secondly, we both
agree that it is necessary to allow rediscovery of a type. What I disagree is
the argument that the design of heterogeneous collection is fundamentally
wrong, which, of cause, is not your statement.

David Shang