From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on polar.synack.me X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00 autolearn=ham autolearn_force=no version=3.4.4 X-Google-Language: ENGLISH,ASCII-7-bit X-Google-Thread: 103376,b74ec64483660e21 X-Google-Attributes: gid103376,public X-Google-ArrivalTime: 2001-10-29 14:49:42 PST Path: archiver1.google.com!news1.google.com!sn-xit-02!sn-post-02!sn-post-01!supernews.com!corp.supernews.com!not-for-mail From: "Matthew Heaney" Newsgroups: comp.lang.ada Subject: Re: When to use 'Class in a parameter list Date: Mon, 29 Oct 2001 17:52:46 -0500 Organization: Posted via Supernews, http://www.supernews.com Message-ID: References: <9ji1b3$4pi$1@nh.pace.co.uk> X-Priority: 3 X-MSMail-Priority: Normal X-Newsreader: Microsoft Outlook Express 5.50.4807.1700 X-MimeOLE: Produced By Microsoft MimeOLE V5.50.4807.1700 X-Complaints-To: newsabuse@supernews.com Xref: archiver1.google.com comp.lang.ada:15379 Date: 2001-10-29T17:52:46-05:00 List-Id: "Marin David Condic" wrote in message news:9ji1b3$4pi$1@nh.pace.co.uk... > O.K. Here's something I thought I understood but given behavior of some code > I have, now I'm questioning what is happening. > > If I have a tagged type "Base_Type" that has an operation on it, called "Op" > and I derive a new type called "Child_Type" that does not require changes to > "Op", do I make the parameter Base_Type or Base_Type'Class? Let's go back to Ada83, which already had inheritance (of "primitive" operations): package P is type T is limited private; procedure Op (O : in out T); ... end P; Now let's create a type that derives from T: with P; package Q is type NT is new P.T; end Q; The type NT inherits the operation Op from its parent type T, which means I can do this: declare O : Q.NT; begin Q.Op (O); end; The point is that at the point of declaration of NT, operation Op is "implicitly declared." This was true in Ada83, and it continues to be true in Ada95. (This is quite deliberate: Tucker's philosophy was that Ada95 build on the existing infrastructure already present in Ada83.) Now let's review what we mean by "primitive" operation. Basically, it's an operation that takes the type as a parameter or return value, and is declared in the same package as the type. The operation P.Op is primitive for type P.T, because it takes type T as a parameter. Not all operations that take the type as a parameter are primitive. For example: with P; package PP is procedure Another_Op (O : in out P.T); end; Here, operation Another_Op is NOT primitive. We care about "primitive" operations for a type, because they are inherited during a derivation. That's why type Q.NT automatically has an operation Q.Op -- because it inherited it from its parent P.T. For whatever reason, this is an aspect of Ada that few programmers seem to fully understand. Of course, type NT is free to override Op, if it doesn't like the default implementation: with P; package Q is type NT is new P.T; procedure Op (O : in out NT); end Q; Everything I've just said applies to Ada95. The only difference between Ada83 and Ada95 is that Ada95 added "type extension" to the language. The inheritance model *already* existed in Ada83. The code above is an Ada95 program, but for the example let's rewrite it to use tagged types: package P is type T is tagged limited private; procedure Op (O : in out T); ... end P; with P; package Q is type NT is new P.T with null record; end; (Note that in general, you should create a package hierarchy that mimics the type hierarchy. Here we're trying to keep the example simple.) Everything is the same as in our earlier example. Type Q.NT inherits the operation Op from its parent type P.T, so you can do this (same as before): declare O : Q.NT; begin Q.Op (O); end; Now, we talked about "primitive" operations. Operation Op is primitive for type T (and NT), because it takes type T as a parameter, and is declared in the same package as T (here, package P). You asked whether you should declare the parameter as type T'Class: the answer is NO. The reason is that were T'Class the type, then the operation doesn't satisfy the criteria for "primitiveness". The pararmeter type would be T'Class, not T, and the operation has to take type T in order for it to qualify as "primitive". Operations that are NOT primitive for the type are NOT inherited during a derivation. So if you were to do this: package P is type T is tagged limited private; procedure Op (O : in out T'Class); ... end P; with P; package Q is type NT is new P.T with null record; end; declare O : Q.NT; begin Q.Op (O); --will not compile end; This will NOT compile, because type Q.NT does NOT have an operation called Op. Op is not primitive, and therefore it is not inherited. You want to know when you should declare the operation as taking type T'Class, but your question should really be phrased as "when should the operation be primitive?", or "when should an operation be class-wide?". Does the operation apply to every type in the class, or does it make sense for derived types to provide their own type-specific implementation? One characteristic of class-wide operations is that they have a fixed algorithm, but operations called to implement the algorithm can vary across types. (As Ehud pointed out, this is a design pattern called "Template Method.") Class-wide operations are ultimately implemented by calling primitive operations of the type, which dispatch according to the tag of the object. > (My > understanding was that I could make it Base_Type, but then calls to it with > a Child_Type would require explicit type conversion. You are confused. You don't need to convert Child_Type to Base_Type in order to call Op, because Child_Type already has an operation called Op. The only reason you'd need to do a conversion (here, a "view" conversion) is to call the parent's implementation of Op. For example: package P is type Base_Type is tagged null record; procedure Op (O : in out T); end; package P.C is type Child_Type is new Base_Type with null record; end; declare O : P.C.Child_Type; begin P.C.Op (O); --OK end; This is a perfectly reasonable thing to do. No conversion is required. Now let's say Child_Type overrides Op: package P.C is type Child_Type is new Base_Type with null record; procedure Op (O : in out Child_Type); end; Now let's implement P.C.Op, by calling the parent version of Op: package body P.C is procedure Op (O : in out Child_Type) is begin Op (Base_Type(O)); --do some more stuff end; end P.C; Here we've performed a "view" conversion, in order to call the Op defined for Base_Type. You often do this when implementing a type that derives from Controlled: package P is type T is new Limited_Controlled with null record; procedure Finalize (O : in out T); end; package P.C is type NT is new T with null record; procedure Finalize (O : in out NT); end; package body P.C is procedure Finalize (O : in out NT) is begin --do type-specific clean-up Finalize (T(O)): end; ... end P.C; This technique ensures that "base class" finalization is done too. > Making it 'Class would > accept anything of that class without conversion. Apparently the compiler is > swollowing it without type conversion - which is now confusing me.) No type conversion is necessary. If the operation is primitive for Base_Type, it is inherited by Child_Type during derivation, so therefore Child_Type has the operation. > In code: > > procedure Op (Base : in out Base_Type) ; > ... > X : Child_Type ; > ... > Op (X) ; -- Why is this working without a Base_Type (X) conversion??? Because you're simply calling the Op defined for Child_Type. Op was implicitly declared at the point of declaration of Child_Type, because Op is primtive, and therefore is inherited during a derivation. > So unless I'm doing something strange that is causing some corner-case to > come up, I'm now wondering why I would need Base_Type'Class as a parameter > type? Making the operation take T'Class changes the semantics of the operation. It means "this operation applies to all types in the class." Same as a static method in C++. > I was under the impression that I would use 'Class if I wanted to make > an operation that worked on anything derived from the class without explicit > conversion. (Possible to override it in a child class, AFAIK...) You appear to be confused about when operations implicitly declared for a type. Op is implicitly declared for Child_Type, because it was primitive for Base_Type, and was therefore inherited. An operation that takes Base_Type'Class works "without conversion" because type Base_Type'Class "covers" type Child_Type. In a sense the types Base_Type'Class and Child_Type have a subtype relationship, the way Positive is related to type Integer. > My understanding of when to *NOT* use the 'Class was if I was building an > operation I expected to override (possibly calling the parent operation > within it - using a type conversion). Yes. Like the Finalize example above. > So when it is not overriden, and > control goes to the parent op without an explicit conversion, then when do > you need the 'Class? It was not overridden, but it is still defined for type Child_Type. > I must be missing something here......(I need to do > this sort of thing more often - it all evaporates if you don't use it!!!) Your confusion probably stems from an incomplete understanding of Ada83 semantics. Regards, Matt