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

3.4 Derived Types and Classes

3.4 Derived Types and Classes

1
A derived_type_definition defines a new type (and its first subtype) whose characteristics are derived from those of a parent type.

1.a
Glossary entry: A derived type is a type defined in terms of another type, which is the parent type of the derived type. Each class containing the parent type also contains the derived type. The derived type inherits properties such as components and primitive operations from the parent. A type together with the types derived from it (directly or indirectly) form a derivation class.

Syntax

2
derived_type_definition ::= [abstract] new parent_subtype_indication [record_extension_part]

Legality Rules

3
The parent_subtype_indication defines the parent subtype; its type is the parent type.

4
A type shall be completely defined (see 3.11.1) prior to being specified as the parent type in a derived_type_definition - [the full_type_declarations for the parent type and any of its subcomponents have to precede the derived_type_definition.]

4.a
Discussion:  This restriction does not apply to the ancestor type of a private extension - see 7.3; such a type need not be completely defined prior to the private_extension_declaration.  However, the restriction does apply to record extensions, so the ancestor type will have to be completely defined prior to the full_type_declaration corresponding to the private_extension_declaration.

4.b
Reason: We originally hoped we could relax this restriction. However, we found it too complex to specify the rules for a type derived from an incompletely defined limited type that subsequently became nonlimited.

5
If there is a record_extension_part, the derived type is called a record extension of the parent type. A record_extension_part shall be provided if and only if the parent type is a tagged type.

5.a
Implementation Note: We allow a record extension to inherit discriminants; a previous version of Ada 9X did not. If the parent subtype is unconstrained, it can be implemented as though its discriminants were repeated in a new known_discriminant_part and then used to constrain the old ones one-for-one. However, in an extension aggregate, the discriminants in this case do not appear in the component association list.

5.b
Ramification: This rule needs to be rechecked in the visible part of an instance of a generic unit.

Static Semantics

6
The first subtype of the derived type is unconstrained if a known_discriminant_part is provided in the declaration of the derived type, or if the parent subtype is unconstrained. Otherwise, the constraint of the first subtype corresponds to that of the parent subtype in the following sense: it is the same as that of the parent subtype except that for a range constraint (implicit or explicit), the value of each bound of its range is replaced by the corresponding value of the derived type.

6.a
Discussion:  A digits_constraint in a subtype_indication for a decimal fixed point subtype always imposes a range constraint, implicitly if there is no explicit one given. See 3.5.9, "Fixed Point Types".

7
The characteristics of the derived type are defined as follows:

8 ·
Each class of types that includes the parent type also includes the derived type.

8.a
Discussion:  This is inherent in our notion of a "class" of types.  It is not mentioned in the initial definition of "class" since at that point type derivation has not been defined.  In any case, this rule ensures that every class of types is closed under derivation.

9 ·
If the parent type is an elementary type or an array type, then the set of possible values of the derived type is a copy of the set of possible values of the parent type. For a scalar type, the base range of the derived type is the same as that of the parent type.

9.a
Discussion:  The base range of a type defined by an integer_type_definition or a real_type_definition is determined by the _definition, and is not necessarily the same as that of the corresponding root numeric type from which the newly defined type is implicitly derived.  Treating numerics types as implicitly derived from one of the two root numeric types is simply to link them into a type hierarchy; such an implicit derivation does not follow all the rules given here for an explicit derived_type_definition.

10 ·
If the parent type is a composite type other than an array type, then the components, protected subprograms, and entries that are declared for the derived type are as follows:

11 ·   
The discriminants specified by a new known_discriminant_part, if there is one; otherwise, each discriminant of the parent type (implicitly declared in the same order with the same specifications) -  in the latter case, the discriminants are said to be inherited, or if unknown in the parent, are also unknown in the derived type;
12 ·   
Each nondiscriminant component, entry, and protected subprogram of the parent type, implicitly declared in the same order with the same declarations; these components, entries, and protected subprograms are said to be inherited;
12.a
Ramification: The profiles of entries and protected subprograms do not change upon type derivation, although the type of the "implicit" parameter identified by the prefix of the name in a call does.

12.b
To be honest: Any name in the parent type_declaration that denotes the current instance of the type is replaced with a name denoting the current instance of the derived type, converted to the parent type.

13 ·   
Each component declared in a record_extension_part, if any.
14
Declarations of components, protected subprograms, and entries, whether implicit or explicit, occur immediately within the declarative region of the type, in the order indicated above, following the parent subtype_indication.

14.a
Discussion:  The order of declarations within the region matters for record_aggregates and extension_aggregates.

14.b
Ramification: In most cases, these things are implicitly declared immediately following the parent subtype_indication. However, 7.3.1, "Private Operations" defines some cases in which they are implicitly declared later, and some cases in which the are not declared at all.

14.c
Discussion:  The place of the implicit declarations of inherited components matters for visibility - they are not visible in the known_discriminant_part nor in the parent subtype_indication, but are usually visible within the record_extension_part, if any (although there are restrictions on their use). Note that a discriminant specified in a new known_discriminant_part is not considered "inherited" even if it has the same name and subtype as a discriminant of the parent type.

15 ·
The derived type is limited if and only if the parent type is limited.

15.a
To be honest: The derived type can become nonlimited if the derivation takes place in the visible part of a child package, and the parent type is nonlimited as viewed from the private part of the child package - see 7.5.

16 ·
[For each predefined operator of the parent type, there is a corresponding predefined operator of the derived type.]

16.a
Proof: This is a ramification of the fact that each class that includes the parent type also includes the derived type, and the fact that the set of predefined operators that is defined for a type, as described in 4.5, is determined by the classes to which it belongs.

16.b
Reason: Predefined operators are handled separately because they follow a slightly different rule than user-defined primitive subprograms.  In particular the systematic replacement described below does not apply fully to the relational operators for Boolean and the exponentiation operator for Integer.  The relational operators for a type derived from Boolean still return Standard.Boolean.  The exponentiation operator for a type derived from Integer still expects Standard.Integer for the right operand. In addition, predefined operators "reemerge" when a type is the actual type corresponding to a generic formal type, so they need to be well defined even if hidden by user-defined primitive subprograms.

17 ·
For each user-defined primitive subprogram (other than a user-defined equality operator - see below) of the parent type that already exists at the place of the derived_type_definition, there exists a corresponding inherited primitive subprogram of the derived type with the same defining name. Primitive user-defined equality operators of the parent type are also inherited by the derived type, except when the derived type is a nonlimited record extension, and the inherited operator would have a profile that is type conformant with the profile of the corresponding predefined equality operator; in this case, the user-defined equality operator is not inherited, but is rather incorporated into the implementation of the predefined equality operator of the record extension (see 4.5.2).

17.a
Ramification: We say "...already exists..." rather than "is visible" or "has been declared" because there are certain operations that are declared later, but still exist at the place of the derived_type_definition, and there are operations that are never declared, but still exist. These cases are explained in 7.3.1.

17.b
Note that nonprivate extensions can appear only after the last primitive subprogram of the parent - the freezing rules ensure this.

17.c
Reason: A special case is made for the equality operators on nonlimited record extensions because their predefined equality operators are already defined in terms of the primitive equality operator of their parent type (and of the tagged components of the extension part).  Inheriting the parent's equality operator as is would be undesirable, because it would ignore any components of the extension part. On the other hand, if the parent type is limited, then any user-defined equality operator is inherited as is, since there is no predefined equality operator to take its place.

17.d
Ramification: Because user-defined equality operators are not inherited by record extensions, the formal parameter names of = and /= revert to Left and Right, even if different formal parameter names were used in the user-defined equality operators of the parent type.

18
The profile of an inherited subprogram (including an inherited enumeration literal) is obtained from the profile of the corresponding (user-defined) primitive subprogram of the parent type, after systematic replacement of each subtype of its profile (see 6.1) that is of the parent type with a corresponding subtype of the derived type. For a given subtype of the parent type, the corresponding subtype of the derived type is defined as follows:

19 ·   
If the declaration of the derived type has neither a known_discriminant_part nor a record_extension_part, then the corresponding subtype has a constraint that corresponds (as defined above for the first subtype of the derived type) to that of the given subtype.
20 ·   
If the derived type is a record extension, then the corresponding subtype is the first subtype of the derived type.
21 ·   
If the derived type has a new known_discriminant_part but is not a record extension, then the corresponding subtype is constrained to those values that when converted to the parent type belong to the given subtype (see 4.6).
21.a
Reason: An inherited subprogram of an untagged type has an Intrinsic calling convention, which precludes the use of the Access attribute. We preclude 'Access because correctly performing all required constraint checks on an indirect call to such an inherited subprogram was felt to impose an undesirable implementation burden.

22
The same formal parameters have default_expressions in the profile of the inherited subprogram.  [Any type mismatch due to the systematic replacement of the parent type by the derived type is handled as part of the normal type conversion associated with parameter passing - see 6.4.1.]

22.a
Reason: We don't introduce the type conversion explicitly here since conversions to record extensions or on access parameters are not generally legal.  Furthermore, any type conversion would just be "undone" since the parent's subprogram is ultimately being called anyway.

23
If a primitive subprogram of the parent type is visible at the place of the derived_type_definition, then the corresponding inherited subprogram is implicitly declared immediately after the derived_type_definition. Otherwise, the inherited subprogram is implicitly declared later or not at all, as explained in 7.3.1.

24
A derived type can also be defined by a private_extension_declaration (see 7.3) or a formal_derived_type_definition (see 12.5.1). Such a derived type is a partial view of the corresponding full or actual type.

25
All numeric types are derived types, in that they are implicitly derived from a corresponding root numeric type (see 3.5.4 and 3.5.6).

Dynamic Semantics

26
The elaboration of a derived_type_definition creates the derived type and its first subtype, and consists of the elaboration of the subtype_indication and the record_extension_part, if any. If the subtype_indication depends on a discriminant, then only those expressions that do not depend on a discriminant are evaluated.

27
For the execution of a call on an inherited subprogram, a call on the corresponding primitive subprogram of the parent type is performed; the normal conversion of each actual parameter to the subtype of the corresponding formal parameter (see 6.4.1) performs any necessary type conversion as well. If the result type of the inherited subprogram is the derived type, the result of calling the parent's subprogram is converted to the derived type.

27.a
Discussion:  If an inherited function returns the derived type, and the type is a record extension, then the inherited function is abstract, and (unless overridden) cannot be called except via a dispatching call. See 3.9.3.

NOTES

28 10
Classes are closed under derivation -any class that contains a type also contains its derivatives. Operations available for a given class of types are available for the derived types in that class.

29 11
Evaluating an inherited enumeration literal is equivalent to evaluating the corresponding enumeration literal of the parent type, and then converting the result to the derived type.  This follows from their equivalence to parameterless functions.

30 12
A generic subprogram is not a subprogram, and hence cannot be a primitive subprogram and cannot be inherited by a derived type.  On the other hand, an instance of a generic subprogram can be a primitive subprogram, and hence can be inherited.

31 13
If the parent type is an access type, then the parent and the derived type share the same storage pool; there is a null access value for the derived type and it is the implicit initial value for the type. See 3.10.

32 14
If the parent type is a boolean type, the predefined relational operators of the derived type deliver a result of the predefined type Boolean (see 4.5.2). If the parent type is an integer type, the right operand of the predefined exponentiation operator is of the predefined type Integer (see 4.5.6).

33 15
Any discriminants of the parent type are either all inherited, or completely replaced with a new set of discriminants.

34 16
For an inherited subprogram, the subtype of a formal parameter of the derived type need not have any value in common with the first subtype of the derived type.

34.a
Proof: This happens when the parent subtype is constrained to a range that does not overlap with the range of a subtype of the parent type that appears in the profile of some primitive subprogram of the parent type. For example:

34.b
type T1 is range 1..100;
subtype S1 is T1 range 1..10;
procedure P(X : in S1);  -- P is a primitive subprogram
type T2 is new T1 range 11..20;
-- implicitly declared:
--
procedure P(X : in T2'Base range 1..10);
--      X cannot be in T2'First .. T2'Last

35 17
If the reserved word abstract is given in the declaration of a type, the type is abstract (see 3.9.3).

Examples

36
Examples of derived type declarations:

37
type Local_Coordinate is new Coordinate;   --  two different types
type Midweek is new Day range Tue .. Thu;  --  see 3.5.1
type Counter is new Positive;              --  same range as Positive

38
type Special_Key is new Key_Manager.Key;   --  see 7.3.1
  -- the inherited subprograms have the following specifications:
  --         procedure Get_Key(K : out Special_Key);
  --         function "<"(X, Y : Special_Key) return Boolean;

Inconsistencies With Ada 83

38.a
When deriving from a (nonprivate, nonderived) type in the same visible part in which it is defined, if a predefined operator had been overridden prior to the derivation, the derived type will inherit the user-defined operator rather than the predefined operator.  The work-around (if the new behavior is not the desired behavior) is to move the definition of the derived type prior to the overriding of any predefined operators.

Incompatibilities With Ada 83

38.b
When deriving from a (nonprivate, nonderived) type in the same visible part in which it is defined, a primitive subprogram of the parent type declared before the derived type will be inherited by the derived type.  This can cause upward incompatibilities in cases like this:

38.c
   package P is
      type T is (A, B, C, D);
      function F( X : T := A ) return Integer;
      type NT is new T;
      --inherits F as
      --function F( X : NT := A ) return Integer;
      --in Ada 9X only
      ...
   end P;
   ...
   use P;  --Only one declaration of F from P is use-visible in
           --Ada 83;  two declarations of F are use-visible in
           --Ada 9X.
begin
   ...
   if F > 1 then ... --legal in Ada 83, ambiguous in Ada 9X

Extensions to Ada 83

38.d
The syntax for a derived_type_definition is amended to include an optional record_extension_part (see 3.9.1).

38.e
A derived type may override the discriminants of the parent by giving a new discriminant_part.

38.f
The parent type in a derived_type_definition may be a derived type defined in the same visible part.

38.g
When deriving from a type in the same visible part in which it is defined, the primitive subprograms declared prior to the derivation are inherited as primitive subprograms of the derived type. See 3.2.3.

Wording Changes From Ada 83

38.h
We now talk about the classes to which a type belongs, rather than a single class.

38.i
As explained in Section 13, the concept of "storage pool" replaces the Ada 83 concept of "collection." These concepts are similar, but not the same.

3.4.1 Derivation Classes

1
In addition to the various language-defined classes of types, types can be grouped into derivation classes.

Static Semantics

2
A derived type is derived from its parent type directly; it is derived indirectly from any type from which its parent type is derived. The derivation class of types for a type T (also called the class rooted at T) is the set consisting of T (the root type of the class) and all types derived from T (directly or indirectly) plus any associated universal or class-wide types (defined below).

2.a
Discussion:  Note that the definition of "derived from" is a recursive definition. We don't define a root type for all interesting language-defined classes, though presumably we could.

2.b
To be honest: By the class-wide type "associated" with a type T, we mean the type T'Class. Similarly, the universal type associated with root_integer, root_real, and root_fixed are universal_integer, universal_real, and universal_fixed, respectively.

3
Every type is either a specific type, a class-wide type, or a universal type. A specific type is one defined by a type_declaration, a formal_type_declaration, or a full type definition embedded in a declaration for an object. Class-wide and universal types are implicitly defined, to act as representatives for an entire class of types, as follows:

3.a
To be honest: The root types root_integer, root_real, and root_fixed are also specific types.  They are declared in the specification of package Standard.

4
Class-wide types Class-wide types are defined for [(and belong to)] each derivation class rooted at a tagged type (see 3.9). Given a subtype S of a tagged type T, S'Class is the subtype_mark for a corresponding subtype of the tagged class-wide type T'Class.  Such types are called "class-wide" because when a formal parameter is defined to be of a class-wide type T'Class, an actual parameter of any type in the derivation class rooted at T is acceptable (see 8.6).

5
The set of values for a class-wide type T'Class is the discriminated union of the set of values of each specific type in the derivation class rooted at T (the tag acts as the implicit discriminant - see 3.9). Class-wide types have no primitive subprograms of their own. However, as explained in 3.9.2, operands of a class-wide type T'Class can be used as part of a dispatching call on a primitive subprogram of the type T. The only components [(including discriminants)] of T'Class that are visible are those of T. If S is a first subtype, then S'Class is a first subtype.

5.a
Reason: We want S'Class to be a first subtype when S is, so that an attribute_definition_clause like "for S'Class'Output use ...;" will be legal.

6
Universal types Universal types are defined for [(and belong to)] the integer, real, and fixed point classes, and are referred to in this standard as respectively, universal_integer, universal_real, and universal_fixed. These are analogous to class-wide types for these language-defined numeric classes. As with class-wide types, if a formal parameter is of a universal type, then an actual parameter of any type in the corresponding class is acceptable.  In addition, a value of a universal type (including an integer or real numeric_literal) is "universal" in that it is acceptable where some particular type in the class is expected (see 8.6).

7
The set of values of a universal type is the undiscriminated union of the set of values possible for any definable type in the associated class. Like class-wide types, universal types have no primitive subprograms of their own.  However, their "universality" allows them to be used as operands with the primitive subprograms of any type in the corresponding class.

7.a
Discussion:  A class-wide type is only class-wide in one direction, from specific to class-wide, whereas a universal type is class-wide (universal) in both directions, from specific to universal and back.

7.b
We considered defining class-wide or perhaps universal types for all derivation classes, not just tagged classes and these three numeric classes.  However, this was felt to overly weaken the strong-typing model in some situations.  Tagged types preserve strong type distinctions thanks to the run-time tag.  Class-wide or universal types for untagged types would weaken the compile-time type distinctions without providing a compensating run-time-checkable distinction.

7.c
We considered defining standard names for the universal numeric types so they could be used in formal parameter specifications. However, this was felt to impose an undue implementation burden for some implementations.

7.d
To be honest: Formally, the set of values of a universal type is actually a copy of the undiscriminated union of the values of the types in its class.  This is because we want each value to have exactly one type, with explicit or implicit conversion needed to go between types.  An alternative, consistent model would be to associate a class, rather than a particular type, with a value, even though any given expression would have a particular type. In that case, implicit type conversions would not generally need to change the value, although an associated subtype conversion might need to.

8
The integer and real numeric classes each have a specific root type in addition to their universal type, named respectively root_integer and root_real.

9
A class-wide or universal type is said to cover all of the types in its class.  A specific type covers only itself.

10
A specific type T2 is defined to be a descendant of a type T1 if T2 is the same as T1, or if T2 is derived (directly or indirectly) from T1.  A class-wide type T2'Class is defined to be a descendant of type T1 if T2 is a descendant of T1. Similarly, the universal types are defined to be descendants of the root types of their classes. If a type T2 is a descendant of a type T1, then T1 is called an ancestor of T2. The ultimate ancestor of a type is the ancestor of the type that is not a descendant of any other type.

10.a
Ramification: A specific type is a descendant of itself. Class-wide types are considered descendants of the corresponding specific type, and do not have any descendants of their own.

10.b
A specific type is an ancestor of itself. The root of a derivation class is an ancestor of all types in the class, including any class-wide types in the class.

10.c
Discussion:  The terms root, parent, ancestor, and ultimate ancestor are all related.  For example:

10.d ·   
Each type has at most one parent, and one or more ancestor types; each type has exactly one ultimate ancestor. In Ada 83, the term "parent type" was sometimes used more generally to include any ancestor type (e.g. RM83-9.4(14)).  In Ada 9X, we restrict parent to mean the immediate ancestor.
10.e ·   
A class of types has at most one root type; a derivation class has exactly one root type.
10.f ·   
The root of a class is an ancestor of all of the types in the class (including itself).
10.g ·   
The type root_integer is the root of the integer class, and is the ultimate ancestor of all integer types. A similar statement applies to root_real.
11
An inherited component [(including an inherited discriminant)] of a derived type is inherited from a given ancestor of the type if the corresponding component was inherited by each derived type in the chain of derivations going back to the given ancestor.

NOTES

12 18
Because operands of a universal type are acceptable to the predefined operators of any type in their class, ambiguity can result.  For universal_integer and universal_real, this potential ambiguity is resolved by giving a preference (see 8.6) to the predefined operators of the corresponding root types (root_integer and root_real, respectively). Hence, in an apparently ambiguous expression like

1 + 4 < 7
14
where each of the literals is of type universal_integer, the predefined operators of root_integer will be preferred over those of other specific integer types, thereby resolving the ambiguity.

14.a
Ramification: Except for this preference, a root numeric type is essentially like any other specific type in the associated numeric class.  In particular, the result of a predefined operator of a root numeric type is not "universal" (implicitly convertible) even if both operands were.



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

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