[Home] [Prev] [Next] [Index]

3.9 Tagged Types and Type Extensions

3.9 Tagged Types and Type Extensions

1
[Tagged types and type extensions support object-oriented programming, based on inheritance with extension and run-time polymorphism via dispatching operations.]

Language Design Principles

1.a
The intended implementation model is for a tag to be represented as a pointer to a statically allocated and link-time initialized type descriptor.  The type descriptor contains the address of the code for each primitive operation of the type.  It probably also contains other information, such as might make membership tests convenient and efficient.

1.b
The primitive operations of a tagged type are known at its first freezing point; the type descriptor is laid out at that point. It contains linker symbols for each primitive operation; the linker fills in the actual addresses.

1.c
Other implementation models are possible.

1.d
The rules ensure that "dangling dispatching" is impossible; that is, when a dispatching call is made, there is always a body to execute.  This is different from some other object-oriented languages, such as Smalltalk, where it is possible to get a run-time error from a missing method.

1.e
Dispatching calls should be efficient, and should have a bounded worst-case execution time.  This is important in a language intended for real-time applications.  In the intended implementation model, a dispatching call involves calling indirect through the appropriate slot in the dispatch table.  No complicated "method lookup" is involved.

1.f
The programmer should have the choice at each call site of a dispatching operation whether to do a dispatching call or a statically determined call (i.e. whether the body executed should be determined at run time or at compile time).

1.g
The same body should be executed for a call where the tag is statically determined to be T'Tag as for a dispatching call where the tag is found at run time to be T'Tag.  This allows one to test a given tagged type with statically determined calls, with some confidence that run-time dispatching will produce the same behavior.

1.h
All views of a type should share the same type descriptor and the same tag.

1.i
The visibility rules determine what is legal at compile time; they have nothing to do with what bodies can be executed at run time. Thus, it is possible to dispatch to a subprogram whose declaration is not visible at the call site.  In fact, this is one of the primary facts that gives object-oriented programming its power.  The subprogram that ends up being dispatched to by a given call might even be designed long after the call site has been coded and compiled.

1.j
Given that Ada has overloading, determining whether a given subprogram overrides another is based both on the names and the type profiles of the operations.

1.k
When a type extension is declared, if there is any place within its immediate scope where a certain subprogram of the parent is visible, then a matching subprogram should override.  If there is no such place, then a matching subprogram should be totally unrelated, and occupy a different slot in the type descriptor.  This is important to preserve the privacy of private parts; when an operation declared in a private part is inherited, the inherited version can be overridden only in that private part, in the package body, and in any children of the package.

1.l
If an implementation shares code for instances of generic bodies, it should be allowed to share type descriptors of tagged types declared in the generic body, so long as they are not extensions of types declared in the specification of the generic unit.

Static Semantics

2
A record type or private type that has the reserved word tagged in its declaration is called a tagged type. [When deriving from a tagged type, additional components may be defined. As for any derived type, additional primitive subprograms may be defined, and inherited primitive subprograms may be overridden.] The derived type is called an extension of the ancestor type, or simply a type extension. Every type extension is also a tagged type, and is either a record extension or a private extension of some other tagged type. A record extension is defined by a derived_type_definition with a record_extension_part. A private extension, which is a partial view of a record extension, can be declared in the visible part of a package (see 7.3) or in a generic formal part (see 12.5.1).

2.a
Glossary entry: The objects of a tagged type have a run-time type tag, which indicates the specific type with which the object was originally created. An operand of a class-wide tagged type can be used in a dispatching call; the tag indicates which subprogram body to invoke. Nondispatching calls, in which the subprogram body to invoke is determined at compile time, are also allowed. Tagged types may be extended with additional components.

2.b
Ramification: If a tagged type is declared other than in a package_specification, it is impossible to add new primitive subprograms for that type, although it can inherit primitive subprograms, and those can be overridden. If the user incorrectly thinks a certain subprogram is primitive when it is not, and tries to call it with a dispatching call, an error message will be given at the call site.

2.c
Note that the accessibility rules imply that a tagged type declared in a library package_specification cannot be extended in a nested subprogram or task body.

3
An object of a tagged type has an associated (run-time) tag that identifies the specific tagged type used to create the object originally. [The tag of an operand of a class-wide tagged type T'Class controls which subprogram body is to be executed when a primitive subprogram of type T is applied to the operand (see 3.9.2); using a tag to control which body to execute is called dispatching.]

4
The tag of a specific tagged type identifies the full_type_declaration of the type. If a declaration for a tagged type occurs within a generic_package_declaration, then the corresponding type declarations in distinct instances of the generic package are associated with distinct tags. For a tagged type that is local to a generic package body, the language does not specify whether repeated instantiations of the generic body result in distinct tags.

4.a
Reason: This eases generic code sharing.

4.b
Implementation Note: The language does not specify whether repeated elaborations of the same full_type_declaration correspond to distinct tags.  In most cases, we expect that all elaborations will correspond to the same tag, since the tag will frequently be the address (or index) of a statically allocated type descriptor.  However, with shared generics, the type descriptor might have to be allocated on a per-instance basis, which in some implementation models implies per-elaboration of the instantiation.

5
The following language-defined library package exists:

6
package Ada.Tags is

    type Tag is private;

7
    function Expanded_Name(T : Tag) return String;
    function External_Tag(T : Tag) return String;
    function Internal_Tag(External : String) return Tag;

8
    Tag_Error : exception;

9
private
   ... -- not specified by the language
end Ada.Tags;

9.a
Reason: Tag is a nonlimited, definite subtype, because it needs the equality operators, so that tag checking makes sense. Also, equality, assignment, and object declaration are all useful capabilities for this subtype.

9.b
For an object X and a type T, "X'Tag = T'Tag" is not needed, because a membership test can be used. However, comparing the tags of two objects cannot be done via membership. This is one reason to allow equality for type Tag.

10
The function Expanded_Name returns the full expanded name of the first subtype of the specific type identified by the tag, in upper case, starting with a root library unit. The result is implementation defined if the type is declared within an unnamed block_statement.

10.a
To be honest: This name, as well as each prefix of it, does not denote a renaming_declaration.

10.b
Implementation defined:  The result of Tags.Expanded_Name for types declared within an unnamed block_statement.

11
The function External_Tag returns a string to be used in an external representation for the given tag.  The call External_Tag(S'Tag) is equivalent to the attribute_reference S'External_Tag (see 13.3).

11.a
Reason: It might seem redundant to provide both the function External_Tag and the attribute External_Tag. The function is needed because the attribute can't be applied to values of type Tag. The attribute is needed so that it can be specifiable via an attribute_definition_clause.

12
The function Internal_Tag returns the tag that corresponds to the given external tag, or raises Tag_Error if the given string is not the external tag for any specific type of the partition.

13
For every subtype S of a tagged type T (specific or class-wide), the following attributes are defined:

14 S'Class  S'Class denotes a subtype of the class-wide type (called T'Class in this International Standard) for the class rooted at T (or if S already denotes a class-wide subtype, then S'Class is the same as S).



S'Class is unconstrained.  However, if S is constrained, then the values of S'Class are only those that when converted to the type T belong to S.

15.a
Ramification: This attribute is defined for both specific and class-wide subtypes.  The definition is such that S'Class'Class is the same as S'Class.

15.b
Note that if S is constrained, S'Class is only partially constrained, since there might be additional discriminants added in descendants of T which are not constrained.

15.c
Reason: The Class attribute is not defined for untagged subtypes (except for incomplete types and private types whose full view is tagged - see 3.10.1 and 7.3.1) so as to preclude implicit conversion in the absence of run-time type information.  If it were defined for untagged subtypes, it would correspond to the concept of universal types provided for the predefined numeric classes.

16 S'Tag  S'Tag denotes the tag of the type T (or if T is class-wide, the tag of the root type of the corresponding class). The value of this attribute is of type Tag.

16.a
Reason: S'Class'Tag equals S'Tag, to avoid generic contract model problems when S'Class is the actual type associated with a generic formal derived type.

17
Given a prefix X that is of a class-wide tagged type [(after any implicit dereference)], the following attribute is defined:

18 X'Tag  X'Tag denotes the tag of X. The value of this attribute is of type Tag.

18.a
Reason: X'Tag is not defined if X is of a specific type. This is primarily to avoid confusion that might result about whether the Tag attribute should reflect the tag of the type of X, or the tag of X.  No such confusion is possible if X is of a class-wide type.

Dynamic Semantics

19
The tag associated with an object of a tagged type is determined as follows:

20 ·
The tag of a stand-alone object, a component, or an aggregate of a specific tagged type T identifies T.

20.a
Discussion:  The tag of a formal parameter of type T is not necessarily the tag of T, if, for example, the actual was a type conversion.

21 ·
The tag of an object created by an allocator for an access type with a specific designated tagged type T, identifies T.

21.a
Discussion:  The tag of an object designated by a value of such an access type might not be T, if, for example, the access value is the result of a type conversion.

22 ·
The tag of an object of a class-wide tagged type is that of its initialization expression.

22.a
Ramification: The tag of an object (even a class-wide one) cannot be changed after it is initialized, since a "class-wide" assignment_statement raises Constraint_Error if the tags don't match, and a "specific" assignment_statement does not affect the tag.

23 ·
The tag of the result returned by a function whose result type is a specific tagged type T identifies T.

23.a
Implementation Note: This requires a run-time check for limited tagged types, since they are returned "by-reference."  For a nonlimited type, a new anonymous object with the appropriate tag is created as part of the function return, and then assigned the value of the return expression. See 6.5, "Return Statements".

24 ·
The tag of the result returned by a function with a class-wide result type is that of the return expression.

25
The tag is preserved by type conversion and by parameter passing. The tag of a value is the tag of the associated object (see 6.2).

Implementation Permissions

26
The implementation of the functions in Ada.Tags may raise Tag_Error if no specific type corresponding to the tag passed as a parameter exists in the partition at the time the function is called.

26.a
Reason: In most implementations, repeated elaborations of the same type_declaration will all produce the same tag. In such an implementation, Tag_Error will be raised in cases where the internal or external tag was passed from a different partition. However, some implementations might create a new tag value at run time for each elaboration of a type_declaration. In that case, Tag_Error could also be raised if the created type no longer exists because the subprogram containing it has returned, for example. We don't require the latter behavior; hence the word "may" in this rule.

NOTES

27 62
A type declared with the reserved word tagged should normally be declared in a package_specification, so that new primitive subprograms can be declared for it.

28 63
Once an object has been created, its tag never changes.

29 64
Class-wide types are defined to have unknown discriminants (see 3.7).  This means that objects of a class-wide type have to be explicitly initialized (whether created by an object_declaration or an allocator), and that aggregates have to be explicitly qualified with a specific type when their expected type is class-wide.

30 65
If S denotes an untagged private type whose full type is tagged, then S'Class is also allowed before the full type definition, but only in the private part of the package in which the type is declared (see 7.3.1). Similarly, the Class attribute is defined for incomplete types whose full type is tagged, but only within the library unit in which the incomplete type is declared (see 3.10.1).

Examples

31
Examples of tagged record types:

32
type Point is tagged
  record
    X, Y : Real := 0.0;
  end record;

33
type Expression is tagged null record;
  -- Components will be added by each extension

Extensions to Ada 83

33.a
Tagged types are a new concept.

3.9.1 Type Extensions

1
[Every type extension is a tagged type, and is either a record extension or a private extension of some other tagged type.]

Language Design Principles

1.a
We want to make sure that we can extend a generic formal tagged type, without knowing its discriminants.

1.b
We don't want to allow components in an extension aggregate to depend on discriminants inherited from the parent value, since such dependence requires staticness in aggregates, at least for variants.

Syntax

2
record_extension_part ::= with record_definition

Legality Rules

3
The parent type of a record extension shall not be a class-wide type. If the parent type is nonlimited, then each of the components of the record_extension_part shall be nonlimited. The accessibility level (see 3.10.2) of a record extension shall not be statically deeper than that of its parent type. In addition to the places where Legality Rules normally apply (see 12.3), these rules apply also in the private part of an instance of a generic unit.

3.a
Reason: If the parent is a limited formal type, then the actual might be nonlimited.

3.b
A similar accessibility rule is not needed for private extensions, because in a package, the rule will apply to the full_type_declaration, and for a generic formal private extension, the actual is all that matters.

4
A type extension shall not be declared in a generic body if the parent type is declared outside that body.

4.a
Reason: This paragraph ensures that a dispatching call will never attempt to execute an inaccessible subprogram body.

4.b
The part about generic bodies is necessary in order to preserve the contract model.

4.c
Since a generic unit can be instantiated at a deeper accessibility level than the generic unit, it is necessary to prevent type extensions whose parent is declared outside the generic unit. The same is true if the parent is a formal of the generic unit. If the parent is declared in the generic_declaration (but is not a formal), we don't run afoul of the accessibility rules, because we know that the instance declaration and body will be at the same accessibility level. However, we still have a problem in that case, because it might have an unknown number of abstract subprograms, as in the following example:

4.d
package P is
    type T is tagged null record;
    function F return T; --Inherited versions will be abstract.
end P;

4.e
generic
    type TT is tagged private;
package Gp is
    type NT is abstract new TT with null record;
    procedure Q(X : in NT) is abstract;
end Gp;

4.f
package body Gp is
    type NT2 is new NT with null record; --Illegal!
    procedure Q(X : in NT2) is begin null; end Q;
    --Is this legal or not?  Can't decide because
    --we don't know whether TT had any functions that go abstract
    --on extension.
end Gp;

4.g
package I is new Gp(TT => P.T);

4.h
I.NT is an abstract type with two abstract subprograms: F (inherited as abstract) and Q (explicitly declared as abstract). But the generic body doesn't know about F, so we don't know that it needs to be overridden to make a nonabstract extension of NT. Furthermore, a formal tagged limited private type can be extended with limited components, but the actual might not be limited, which would allow assignment of limited types, which is bad. Hence, we have to disallow this case as well.

4.i
If TT were declared as abstract, then we could have the same problem with abstract procedures.

4.j
We considered disallowing all tagged types in a generic body, for simplicity. We decided not to go that far, in order to avoid unnecessary restrictions.

4.k
We also considered trying make the accessibility level part of the contract; i.e. invent some way of saying (in the generic_declaration) "all instances of this generic unit will have the same accessibility level as the generic_declaration." Unfortunately, that doesn't solve the part of the problem having to do with abstract types.

4.l
Children of generic units obviate the need for extension in the body somewhat.

Dynamic Semantics

5
The elaboration of a record_extension_part consists of the elaboration of the record_definition.

NOTES

6 66
The term "type extension" refers to a type as a whole. The term "extension part" refers to the piece of text that defines the additional components (if any) the type extension has relative to its specified ancestor type.

6.a
Discussion:  We considered other terminology, such as "extended type." However, the terms "private extended type" and "record extended type" did not convey the proper meaning.  Hence, we have chosen to uniformly use the term "extension" as the type resulting from extending a type, with "private extension" being one produced by privately extending the type, and "record extension" being one produced by extending the type with an additional record-like set of components. Note also that the term "type extension" refers to the result of extending a type in the language Oberon as well (though there the term "extended type" is also used, interchangeably, perhaps because Oberon doesn't have the concept of a "private extension").

7 67
The accessibility rules imply that a tagged type declared in a library package_specification can be extended only at library level or as a generic formal. When the extension is declared immediately within a package_body, primitive subprograms are inherited and are overridable, but new primitive subprograms cannot be added.

8 68
A name that denotes a component (including a discriminant) of the parent type is not allowed within the record_extension_part. Similarly, a name that denotes a component defined within the record_extension_part is not allowed within the record_extension_part. It is permissible to use a name that denotes a discriminant of the record extension, providing there is a new known_discriminant_part in the enclosing type declaration. (The full rule is given in 3.8.)

8.a
Reason: The restriction against depending on discriminants of the parent is to simplify the definition of extension aggregates.  The restriction against using parent components in other ways is methodological; it presumably simplifies implementation as well.

9 69
Each visible component of a record extension has to have a unique name, whether the component is (visibly) inherited from the parent type or declared in the record_extension_part (see 8.3).

Examples

10
Examples of record extensions (of types defined above in ):

11
type Painted_Point is new Point with
  record
    Paint : Color := White;
  end record;
    -- Components X and Y are inherited

12
Origin : constant Painted_Point := (X | Y => 0.0, Paint => Black);

13
type Literal is new Expression with
  record                 -- a leaf in an Expression tree
    Value : Real;
  end record;

14
type Expr_Ptr is access all Expression'Class;
                               -- see 3.10

15
type Binary_Operation is new Expression with
  record                 -- an internal node in an Expression tree
    Left, Right : Expr_Ptr;
  end record;

16
type Addition is new Binary_Operation with null record;
type Subtraction is new Binary_Operation with null record;
  -- No additional components needed for these extensions

17
Tree : Expr_Ptr :=         -- A tree representation of "5.0 + (13.0-7.0)"
   new Addition'(
      Left  => new Literal'(Value => 5.0),
      Right => new Subtraction'(
         Left  => new Literal'(Value => 13.0),
         Right => new Literal'(Value => 7.0)));

Extensions to Ada 83

17.a
Type extension is a new concept.

3.9.2 Dispatching Operations of Tagged Types

1
The primitive subprograms of a tagged type are called dispatching operations. [A dispatching operation can be called using a statically determined controlling tag, in which case the body to be executed is determined at compile time. Alternatively, the controlling tag can be dynamically determined, in which case the call dispatches to a body that is determined at run time;] such a call is termed a dispatching call. [As explained below, the properties of the operands and the context of a particular call on a dispatching operation determine how the controlling tag is determined, and hence whether or not the call is a dispatching call. Run-time polymorphism is achieved when a dispatching operation is called by a dispatching call.]

Language Design Principles

1.a
The controlling tag determination rules are analogous to the overload resolution rules, except they deal with run-time type identification (tags) rather than compile-time type resolution.  As with overload resolution, controlling tag determination may depend on operands or result context.

Static Semantics

2
A call on a dispatching operation is a call whose name or prefix denotes the declaration of a primitive subprogram of a tagged type, that is, a dispatching operation.

2.a
Ramification: This definition implies that a call through the dereference of an access-to-subprogram value is never considered a call on a dispatching operation. Note also that if the prefix denotes a renaming_declaration, the place where the renaming occurs determines whether it is primitive; the thing being renamed is irrelevant.

A controlling operand in a call on a dispatching operation of a tagged type T is one whose corresponding formal parameter is of type T or is of an anonymous access type with designated type T; the corresponding formal parameter is called a controlling formal parameter. If the controlling formal parameter is an access parameter, the controlling operand is the object designated by the actual parameter, rather than the actual parameter itself. If the call is to a (primitive) function with result type T, then the call has a controlling result -the context of the call can control the dispatching.

3
A name or expression of a tagged type is either statically tagged, dynamically tagged, or tag indeterminate, according to whether, when used as a controlling operand, the tag that controls dispatching is determined statically by the operand's (specific) type, dynamically by its tag at run time, or from context. A qualified_expression or parenthesized expression is statically, dynamically, or indeterminately tagged according to its operand.  For other kinds of names and expressions, this is determined as follows:

4 ·
The name or expression is statically tagged if it is of a specific tagged type and, if it is a call with a controlling result, it has at least one statically tagged controlling operand;

4.a
Discussion:  It is illegal to have both statically tagged and dynamically tagged controlling operands in the same call -- see below.

5 ·
The name or expression is dynamically tagged if it is of a class-wide type, or it is a call with a controlling result and at least one dynamically tagged controlling operand;

6 ·
The name or expression is tag indeterminate if it is a call with a controlling result, all of whose controlling operands (if any) are tag indeterminate.

7
[A type_conversion is statically or dynamically tagged according to whether the type determined by the subtype_mark is specific or class-wide, respectively.] For a controlling operand that is designated by an actual parameter, the controlling operand is statically or dynamically tagged according to whether the designated type of the actual parameter is specific or class-wide, respectively.

7.a
Ramification: A type_conversion is never tag indeterminate, even if its operand is.  A designated object is never tag indeterminate.

Legality Rules

8
A call on a dispatching operation shall not have both dynamically tagged and statically tagged controlling operands.

8.a
Reason: This restriction is intended to minimize confusion between whether the dynamically tagged operands are implicitly converted to, or tag checked against the specific type of the statically tagged operand(s).

9
If the expected type for an expression or name is some specific tagged type, then the expression or name shall not be dynamically tagged unless it is a controlling operand in a call on a dispatching operation. Similarly, if the expected type for an expression is an anonymous access-to-specific tagged type, then the expression shall not be of an access-to-class-wide type unless it designates a controlling operand in a call on a dispatching operation.

9.a
Reason: This prevents implicit "truncation" of a dynamically-tagged value to the specific type of the target object/formal.  An explicit conversion is required to request this truncation.

9.b
Ramification: This rule applies to all expressions or names with a specific expected type, not just those that are actual parameters to a dispatching call.  This rule does not apply to a membership test whose expression is class-wide, since any type that covers the tested type is explicitly allowed. See 4.5.2.

10
In the declaration of a dispatching operation of a tagged type, everywhere a subtype of the tagged type appears as a subtype of the profile (see 6.1), it shall statically match the first subtype of the tagged type. If the dispatching operation overrides an inherited subprogram, it shall be subtype conformant with the inherited subprogram. A dispatching operation shall not be of convention Intrinsic. If a dispatching operation overrides the predefined equals operator, then it shall be of convention Ada [(either explicitly or by default - see 6.3.1)].

10.a
Reason: These rules ensure that constraint checks can be performed by the caller in a dispatching call, and parameter passing conventions match up properly.  A special rule on aggregates prevents values of a tagged type from being created that are outside of its first subtype.

11
The default_expression for a controlling formal parameter of a dispatching operation shall be tag indeterminate. A controlling formal parameter that is an access parameter shall not have a default_expression.

11.a
Reason: The first part ensures that the default_expression always produces the "correct" tag when called with or without dispatching, or when inherited by a descendant.  If it were statically tagged, the default would be useless for a dispatching call; if it were dynamically tagged, the default would be useless for a nondispatching call.

11.b
The second part is consistent with the first part, since designated objects are never tag-indeterminate.

12
A given subprogram shall not be a dispatching operation of two or more distinct tagged types.

12.a
Reason: This restriction minimizes confusion since multiple dispatching is not provided.  The normal solution is to replace all but one of the tagged types with their class-wide types.

13
The explicit declaration of a primitive subprogram of a tagged type shall occur before the type is frozen (see 13.14). [For example, new dispatching operations cannot be added after objects or values of the type exist, nor after deriving a record extension from it, nor after a body.]

13.a Change: Rule moved here from 13.14, "Freezing Rules", as per WG9 resolution.

13.b
Reason: This rule is needed because (1) we don't want people dispatching to things that haven't been declared yet, and (2) we want to allow tagged type descriptors to be static (allocated statically, and initialized to link-time-known symbols).  Suppose T2 inherits primitive P from T1, and then overrides P.  Suppose P is called before the declaration of the overriding P.  What should it dispatch to?  If the answer is the new P, we've violated the first principle above.  If the answer is the old P, we've violated the second principle.  (A call to the new one necessarily raises Program_Error, but that's beside the point.)

13.c
Note that a call upon a dispatching operation of type T will freeze T.

13.d
We considered applying this rule to all derived types, for uniformity. However, that would be upward incompatible, so we rejected the idea. As in Ada 83, for an untagged type, the above call upon P will call the old P (which is arguably confusing).

13.e
Implementation Note: Because of this rule, the type descriptor can be created (presumably containing linker symbols pointing at the not-yet-compiled bodies) at the first freezing point of the type. It also prevents, for a tagged type declared in a package_specification, overriding in the body or by a child subprogram.

13.f
Ramification: A consequence is that for a derived_type_declaration in a declarative_part, only the first primitive subprogram can be declared by a subprogram_body.

Dynamic Semantics

14
For the execution of a call on a dispatching operation of a type T, the controlling tag value determines which subprogram body is executed. The controlling tag value is defined as follows:

15 ·
If one or more controlling operands are statically tagged, then the controlling tag value is statically determined to be the tag of T.

16 ·
If one or more controlling operands are dynamically tagged, then the controlling tag value is not statically determined, but is rather determined by the tags of the controlling operands. If there is more than one dynamically tagged controlling operand, a check is made that they all have the same tag. If this check fails, Constraint_Error is raised unless the call is a function_call whose name denotes the declaration of an equality operator (predefined or user defined) that returns Boolean, in which case the result of the call is defined to indicate inequality, and no subprogram_body is executed. This check is performed prior to evaluating any tag-indeterminate controlling operands.

16.a
Reason: Tag mismatch is considered an error (except for "=" and "/=") since the corresponding primitive subprograms in each specific type expect all controlling operands to be of the same type. For tag mismatch with an equality operator, rather than raising an exception, "=" returns False and "/=" returns True. No equality operator is actually invoked, since there is no common tag value to control the dispatch. Equality is a special case to be consistent with the existing Ada 83 principle that equality comparisons, even between objects with different constraints, never raise Constraint_Error.

17 ·
If all of the controlling operands are tag-indeterminate, then:

18 ·   
If the call has a controlling result and is itself a (possibly parenthesized or qualified) controlling operand of an enclosing call on a dispatching operation of type T, then its controlling tag value is determined by the controlling tag value of this enclosing call;
19 ·   
Otherwise, the controlling tag value is statically determined to be the tag of type T.
19.a
Ramification: This includes the cases of a tag-indeterminate procedure call, and a tag-indeterminate function_call that is used to initialize a class-wide formal parameter or class-wide object.

20
For the execution of a call on a dispatching operation, the body executed is the one for the corresponding primitive subprogram of the specific type identified by the controlling tag value. The body for an explicitly declared dispatching operation is the corresponding explicit body for the subprogram. The body for an implicitly declared dispatching operation that is overridden is the body for the overriding subprogram, [even if the overriding occurs in a private part.] The body for an inherited dispatching operation that is not overridden is the body of the corresponding subprogram of the parent or ancestor type.

20.a
To be honest: In the unusual case in which a dispatching subprogram is explicitly declared (overridden) by a body (with no preceding subprogram_declaration), the body for that dispatching subprogram is that body; that is, the "corresponding explicit body" in the above rule is the body itself.

20.b
Reason: The wording of the above rule is intended to ensure that the same body is executed for a given tag, whether that tag is determined statically or dynamically. For a type declared in a package, it doesn't matter whether a given subprogram is overridden in the visible part or the private part, and it doesn't matter whether the call is inside or outside the package. For example:

20.c
package P1 is
    type T1 is tagged null record;
    procedure Op_A(Arg : in T1);
    procedure Op_B(Arg : in T1);
end P1;

20.d
with P1; use P1;
package P2 is
    type T2 is new T1 with null record;
    procedure Op_A(Param : in T2);
private
    procedure Op_B(Param : in T2);
end P2;

20.e
with P1; with P2;
procedure Main is
    X : T2;
    Y : T1'Class := X;
begin
    P2.Op_A(Param => X); --Nondispatching call.
    P1.Op_A(Arg => Y); --Dispatching call.
    P2.Op_B(Arg => X); --Nondispatching call.
    P1.Op_B(Arg => Y); --Dispatching call.
end Main;

20.f
The two calls to Op_A both execute the body of Op_A that has to occur in the body of package P2. Similarly, the two calls to Op_B both execute the body of Op_B that has to occur in the body of package P2, even though Op_B is overridden in the private part of P2. Note, however, that the formal parameter names are different for P2.Op_A versus P2.Op_B. The overriding declaration for P2.Op_B is not visible in Main, so the name in the call actually denotes the implicit declaration of Op_B inherited from T1.

20.g
If a call occurs in the program text before an overriding, which can happen only if the call is part of a default expression, the overriding will still take effect for that call.

20.h
Implementation Note: Even when a tag is not statically determined, a compiler might still be able to figure it out and thereby avoid the overhead of run-time dispatching.

NOTES

21 70
The body to be executed for a call on a dispatching operation is determined by the tag; it does not matter whether that tag is determined statically or dynamically, and it does not matter whether the subprogram's declaration is visible at the place of the call.

22 71
This subclause covers calls on primitive subprograms of a tagged type. Rules for tagged type membership tests are described in 4.5.2. Controlling tag determination for an assignment_statement is described in 5.2.

23 72
A dispatching call can dispatch to a body whose declaration is not visible at the place of the call.

24 73
A call through an access-to-subprogram value is never a dispatching call, even if the access value designates a dispatching operation.  Similarly a call whose prefix denotes a subprogram_renaming_declaration cannot be a dispatching call unless the renaming itself is the declaration of a primitive subprogram.

Extensions to Ada 83

24.a
The concept of dispatching operations is new.

3.9.3 Abstract Types and Subprograms

1
[An abstract type is a tagged type intended for use as a parent type for type extensions, but which is not allowed to have objects of its own. An abstract subprogram is a subprogram that has no body, but is intended to be overridden at some point when inherited. Because objects of an abstract type cannot be created, a dispatching call to an abstract subprogram always dispatches to some overriding body.]

Language Design Principles

1.a
An abstract subprogram has no body, so the rules in this clause are designed to ensure (at compile time) that the body will never be invoked. We do so primarily by disallowing the creation of values of the abstract type. Therefore, since type conversion and parameter passing don't change the tag, we know we will never get a class-wide value with a tag identifying an abstract type. This means that we only have to disallow nondispatching calls on abstract subprograms (dispatching calls will never reach them).

Legality Rules

2
An abstract type is a specific type that has the reserved word abstract in its declaration. Only a tagged type is allowed to be declared abstract.

2.a
Ramification: Untagged types are never abstract, even though they can have primitive abstract subprograms. Such subprograms cannot be called, unless they also happen to be dispatching operations of some tagged type, and then only via a dispatching call.

2.b
Class-wide types are never abstract. If T is abstract, then it is illegal to declare a stand-alone object of type T, but it is OK to declare a stand-alone object of type T'Class; the latter will get a tag from its initial value, and this tag will necessarily be different from T'Tag.

3
A subprogram declared by an abstract_subprogram_declaration (see 6.1) is an abstract subprogram. If it is a primitive subprogram of a tagged type, then the tagged type shall be abstract.

3.a
Ramification: Note that for a private type, this applies to both views. The following is illegal:

3.b
package P is
    type T is abstract tagged private;
    function Foo (X : T) return Boolean is abstract; --Illegal!
private
    type T is tagged null record; --Illegal!
    X : T;
    Y : Boolean := Foo (T'Class (X));
end P;

3.c
The full view of T is not abstract, but has an abstract operation Foo, which is illegal. The two lines marked "--Illegal!" are illegal when taken together.

3.d
Reason: We considered disallowing untagged types from having abstract primitive subprograms. However, we rejected that plan, because it introduced some silly anomalies, and because such subprograms are harmless (if not terribly useful). For example:

3.e
package P is
   type Field_Size is range 0..100;
   type T is abstract tagged null record;
   procedure Print(X : in T; F : in Field_Size := 0) is abstract;
  . . .
package Q is
   type My_Field_Size is new Field_Size;
   --implicit declaration of Print(X : T; F : My_Field_Size := 0) is abstract;
end Q;

3.f
It seemed silly to make the derivative of My_Field_Size illegal, just because there was an implicitly declared abstract subprogram that was not primitive on some tagged type. Other rules could be formulated to solve this problem, but the current ones seem like the simplest.

4
For a derived type, if the parent or ancestor type has an abstract primitive subprogram, or a primitive function with a controlling result, then:

5 ·
If the derived type is abstract or untagged, the inherited subprogram is abstract.

5.a
Ramification: Note that it is possible to override a concrete subprogram with an abstract one.

6 ·
Otherwise, the subprogram shall be overridden with a nonabstract subprogram; [for a type declared in the visible part of a package, the overriding may be either in the visible or the private part.] However, if the type is a generic formal type, the subprogram need not be overridden for the formal type itself; [a nonabstract version will necessarily be provided by the actual type.]

6.a
Reason: A function that returns the parent type becomes abstract for an abstract type extension (if not overridden) because conversion from a parent type to a type extension is not defined, and function return semantics is defined in terms of conversion.  (Note that parameters of mode in out or out do not have this problem, because the tag of the actual is not changed.)

6.b
Note that the overriding required above can be in the private part, which allows the following:

6.c
package Pack1 is
    type Ancestor is abstract ...;
    procedure Do_Something(X : in Ancestor) is abstract;
end Pack1;

6.d
with Pack1; use Pack1;
package Pack2 is
    type T1 is new Ancestor with record ...;
        --A concrete type.
    procedure Do_Something(X : in T1); --Have to override.
end Pack2;

6.e
with Pack1; use Pack1;
with Pack2; use Pack2;
package Pack3 is
    type T2 is new Ancestor with private;
        --A concrete type.
private
    type T2 is new T1 with --Parent different from ancestor.
      record ... end record;
    --Here, we inherit Pack2.Do_Something.
end Pack3;

6.f
T2 inherits an abstract Do_Something, but T is not abstract, so Do_Something has to be overridden. However, it is OK to override it in the private part. In this case, we override it by inheriting a concrete version from a different type. Nondispatching calls to Pack3.Do_Something are allowed both inside and outside package Pack3.

7
A call on an abstract subprogram shall be a dispatching call; [nondispatching calls to an abstract subprogram are not allowed.]

7.a
Ramification: If an abstract subprogram is not a dispatching operation of some tagged type, then it cannot be called at all.

8
The type of an aggregate, or of an object created by an object_declaration or an allocator, or a generic formal object of mode in, shall not be abstract. The type of the target of an assignment operation (see 5.2) shall not be abstract. The type of a component shall not be abstract. If the result type of a function is abstract, then the function shall be abstract.

8.a
Reason: This ensures that values of an abstract type cannot be created, which ensures that a dispatching call to an abstract subprogram will not try to execute the nonexistent body.

8.b
Generic formal objects of mode in are like constants; therefore they should be forbidden for abstract types.  Generic formal objects of mode in out are like renamings; therefore, abstract types are OK for them, though probably not terribly useful.

9
If a partial view is not abstract, the corresponding full view shall not be abstract. If a generic formal type is abstract, then for each primitive subprogram of the formal that is not abstract, the corresponding primitive subprogram of the actual shall not be abstract.

9.a
Discussion:  By contrast, we allow the actual type to be nonabstract even if the formal type is declared abstract. Hence, the most general formal tagged type possible is "type T(<>) is abstract tagged limited private;".

9.b
For an abstract private extension declared in the visible part of a package, it is only possible for the full type to be nonabstract if the private extension has no abstract dispatching operations.

10
For an abstract type declared in a visible part, an abstract primitive subprogram shall not be declared in the private part, unless it is overriding an abstract subprogram implicitly declared in the visible part. For a tagged type declared in a visible part, a primitive function with a controlling result shall not be declared in the private part, unless it is overriding a function implicitly declared in the visible part.

10.a
Reason: The "visible part" could be that of a package or a generic package. This rule is needed because a non-abstract type extension declared outside the package would not know about any abstract primitive subprograms or primitive functions with controlling results declared in the private part, and wouldn't know that they need to be overridden with non-abstract subprograms. The rule applies to a tagged record type or record extension declared in a visible part, just as to a tagged private type or private extension. The rule applies to explicitly and implicitly declared abstract subprograms:

10.b
package Pack is
    type T is abstract new T1 with private;
private
    type T is abstract new T2 with record ... end record;
    ...
end Pack;

10.c
The above example would be illegal if T1 has a non-abstract primitive procedure P, but T2 overrides P with an abstract one; the private part should override P with a non-abstract version. On the other hand, if the P were abstract for both T1 and T2, the example would be legal as is.

11
A generic actual subprogram shall not be an abstract subprogram. The prefix of an attribute_reference for the Access, Unchecked_Access, or Address attributes shall not denote an abstract subprogram.

11.a
Ramification: An abstract_subprogram_declaration is not syntactically a subprogram_declaration. Nonetheless, an abstract subprogram is a subprogram, and an abstract_subprogram_declaration is a declaration of a subprogram.

11.b
The part about generic actual subprograms includes those given by default.

NOTES

12 74
Abstractness is not inherited; to declare an abstract type, the reserved word abstract has to be used in the declaration of the type extension.

12.a
Ramification: A derived type can be abstract even if its parent is not. Similarly, an inherited concrete subprogram can be overridden with an abstract subprogram.

13 75
A class-wide type is never abstract.  Even if a class is rooted at an abstract type, the class-wide type for the class is not abstract, and an object of the class-wide type can be created; the tag of such an object will identify some nonabstract type in the class.

Examples

14
Example of an abstract type representing a set of natural numbers:

15
package Sets is
    subtype Element_Type is Natural;
    type Set is abstract tagged null record;
    function Empty return Set is abstract;
    function Union(Left, Right : Set) return Set is abstract;
    function Intersection(Left, Right : Set) return Set is abstract;
    function Unit_Set(Element : Element_Type) return Set is abstract;
    procedure Take(Element : out Element_Type; From : in out Set) is abstract;
end Sets;

NOTES

16 76
Notes on the example: Given the above abstract type, one could then derive various (nonabstract) extensions of the type, representing alternative implementations of a set.  One might use a bit vector, but impose an upper bound on the largest element representable, while another might use a hash table, trading off space for flexibility.

16.a
Discussion:  One way to export a type from a package with some components visible and some components private is as follows:

16.b
package P is
    type Public_Part is abstract tagged
        record
            ...
        end record;
    type T is new Public_Part with private;
    ...
private
    type T is new Public_Part with
        record
            ...
        end record;
end P;

16.c
The fact that Public_Part is abstract tells clients they have to create objects of type T instead of Public_Part. Note that the public part has to come first; it would be illegal to declare a private type Private_Part, and then a record extension T of it, unless T were in the private part after the full declaration of Private_Part, but then clients of the package would not have visibility to T.



[Home] [Prev] [Next] [Index]

documentation@rational.com
Copyright © 1993-1998, Rational Software Corporation.   All rights reserved.