comp.lang.ada
 help / color / mirror / Atom feed
From: "Matthew Heaney" <mheaney@on2.com>
Subject: Re: When to use 'Class in a parameter list
Date: Mon, 29 Oct 2001 17:52:46 -0500
Date: 2001-10-29T17:52:46-05:00	[thread overview]
Message-ID: <ttrn8615ttbj5f@corp.supernews.com> (raw)
In-Reply-To: 9ji1b3$4pi$1@nh.pace.co.uk


"Marin David Condic" <marin.condic.auntie.spam@pacemicro.com> 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







      parent reply	other threads:[~2001-10-29 22:52 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2001-07-23 20:27 When to use 'Class in a parameter list Marin David Condic
2001-07-23 21:39 ` Ehud Lamm
2001-07-24 12:49   ` Marin David Condic
2001-07-24 14:39     ` Dmitry A. Kazakov
2001-07-24 15:16     ` Ehud Lamm
2001-07-24 17:16       ` Marin David Condic
2001-07-23 22:55 ` Stephen Leake
2001-07-25 19:20   ` Deligation with Ada95 Hans-Olof Danielsson
2001-07-26  2:06     ` Lao Xiao Hai
2001-07-24  2:22 ` When to use 'Class in a parameter list Vincent Marciante
2001-07-24 12:52   ` Marin David Condic
2001-07-24 14:36     ` Ed Falis
2001-07-24 15:29       ` Ehud Lamm
2001-10-29 22:52 ` Matthew Heaney [this message]
replies disabled

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox