3.4 Derived Types and Classes
Glossary entry: A derived type is a type
defined in terms of one or more other types given in a derived type definition.
The first of those types is the parent type of the derived type and any
others are progenitor types. Each class containing the parent type or
a progenitor type also contains the derived type. The derived type inherits
properties such as components and primitive operations from the parent
and progenitors. A type together with the types derived from it (directly
or indirectly) form a derivation class.
{
AI95-00442-01}
A
class of types is a
set of types that is closed under derivation; that is, if the parent
or a progenitor type of a derived type belongs to a class, then so does
the derived type. By saying that a particular group of types forms a
class, we are saying that all derivatives of a type in the set inherit
the characteristics that define that set. The more general term
category
of types is used for a set of types whose defining characteristics
are not necessarily inherited by derivatives; for example, limited, abstract,
and interface are all categories of types, but not classes of types.
Ramification: A class of types is also
a category of types.
Syntax
Legality Rules
Glossary entry: The parent of a derived
type is the first type given in the definition of the derived type. The
parent can be almost any kind of type, including an interface type.
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.
Implementation Note: We allow a record
extension to inherit discriminants; an early 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.
Ramification:
{
AI95-00114-01}
This rule needs to be rechecked in the visible part of an instance of
a generic unit because of the “only if” part of the rule.
For example:
generic
type T is private;
package P is
type Der is new T;
end P;
package I is new P (Some_Tagged_Type); -- illegal
{
AI95-00114-01}
The instantiation is illegal because a tagged type is being extended
in the visible part without a
record_extension_part.
Note that this is legal in the private part or body of an instance, both
to avoid a contract model violation, and because no code that can see
that the type is actually tagged can also see the derived type declaration.
No recheck is needed for derived types with
a
record_extension_part,
as that has to be derived from something that is known to be tagged (otherwise
the template is illegal).
{
AI95-00419-01}
{
AI05-0096-1}
If the reserved word
limited appears in a
derived_type_definition,
the parent type shall be a limited type. If the parent type is a tagged
formal type, then in addition to the places where Legality Rules normally
apply (see
12.3), this rule applies also in
the private part of an instance of a generic unit.
Reason: We allow limited because
we don't inherit limitedness from interfaces, so we must have a way to
derive a limited type from interfaces. The word limited has to
be legal when the parent could be an interface, and that includes
generic formal abstract types. Since we have to allow it in this case,
we might as well allow it everywhere as documentation, to make it explicit
that the type is limited.
However, we do not want to allow limited
when the parent is nonlimited: limitedness cannot change in a derivation
tree.
If the parent type is an untagged limited formal
type with an actual type that is nonlimited, we allow derivation as a
limited type in the private part or body as no place could have visibility
on the resulting type where it was known to be nonlimited (outside of
the instance). (See the previous paragraph's annotations for an explanation
of this.) However, if the parent type is a tagged limited formal type
with an actual type that is nonlimited, it would be possible to pass
a value of the limited type extension to a class-wide type of the parent,
which would be nonlimited. That's too weird to allow (even though all
of the extension components would have to be nonlimited because the rules
of
3.9.1 are rechecked), so we have a special
rule to prevent that in the private part (type extension from a formal
type is illegal in a generic package body).
Static Semantics
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.
{
AI95-00231-01}
The first subtype of the derived type excludes null (see
3.10)
if and only if the parent subtype excludes null.
{
AI05-0110-1}
The
characteristics and implicitly declared
primitive subprograms of the derived type are defined as follows:
Ramification: {
AI05-0110-1}
The characteristics of a type do not include its primitive subprograms
(primitive subprograms include predefined operators). The rules governing
availability/visibility and inheritance of characteristics are separate
from those for primitive subprograms.
{
AI95-00251-01}
{
AI95-00401-01}
{
AI95-00442-01}
[If the parent type or a progenitor type belongs to a class of types,
then the derived type also belongs to that class.] The following sets
of types, as well as any higher-level sets composed from them, are classes
in this sense[, and hence the characteristics defining these classes
are inherited by derived types from their parent or progenitor types]:
signed integer, modular integer, ordinary fixed, decimal fixed, floating
point, enumeration, boolean, character, access-to-constant, general access-to-variable,
pool-specific access-to-variable, access-to-subprogram, array, string,
non-array composite, nonlimited, untagged record, tagged, task, protected,
and synchronized tagged.
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.
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.
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.
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:
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;
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;
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.
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.
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.
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.
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.
[For each predefined operator of the parent type,
there is a corresponding predefined operator of the derived type.]
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.
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.
{
AI95-00401-01}
For each user-defined primitive subprogram (other
than a user-defined equality operator — see below) of the parent
type or of a progenitor 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 and any progenitor
types 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).
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.
Note that nonprivate extensions can appear only
after the last primitive subprogram of the parent — the freezing
rules ensure this.
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.
Ramification: {
AI95-00114-01}
Because user-defined equality operators are not inherited by nonlimited
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.
Discussion: {
AI95-00401-01}
This rule only describes what operations are inherited; the rules that
describe what happens when there are conflicting inherited subprograms
are found in
8.3.
{
AI95-00401-01}
{
AI05-0164-1}
{
AI05-0240-1}
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 or progenitor type, after systematic
replacement of each subtype of its profile (see
6.1)
that is of the parent or progenitor type, other than those subtypes found
in the designated profile of an
access_definition,
with a
corresponding subtype of the derived type.
For
a given subtype of the parent or progenitor type, the corresponding subtype
of the derived type is defined as follows:
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.
If the derived type is a record
extension, then the corresponding subtype is the first subtype of the
derived type.
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).
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.
{
AI05-0164-1}
Note that the exception to substitution of the parent or progenitor type
applies only in the profiles of anonymous access-to-subprogram types.
The exception is necessary to avoid calling an access-to-subprogram with
types and/or constraints different than expected by the actual routine.
{
AI95-00401-01}
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 or progenitor type by the derived
type is handled as part of the normal type conversion associated with
parameter passing — see
6.4.1.]
Reason: {
AI95-00401-01}
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 subprogram of the parent or progenitor is ultimately being called
anyway. (Null procedures can be inherited from a progenitor without being
overridden, so it is possible to call subprograms of an interface.)
{
AI95-00401-01}
If a primitive subprogram of the parent or progenitor 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.
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
{
AI95-00391-01}
{
AI95-00401-01}
For the execution of a call on an inherited subprogram,
a call on the corresponding primitive subprogram of the parent or progenitor
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
subprogram of the parent or progenitor is converted to the derived type,
or in the case of a null extension, extended to the derived type using
the equivalent of an
extension_aggregate
with the original result as the
ancestor_part
and
null record as the
record_component_association_list.
Discussion: {
AI95-00391-01}
If an inherited function returns the derived type, and the type is a
nonnull record extension, then the inherited function shall be overridden,
unless the type is abstract (in which case the function is abstract,
and (unless overridden) cannot be called except via a dispatching call).
See
3.9.3.
16
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.
17 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.
18 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.
19 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.
20 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).
21 Any discriminants of the parent type
are either all inherited, or completely replaced with a new set of discriminants.
22 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.
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:
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
23 If the reserved word
abstract
is given in the declaration of a type, the type is abstract (see
3.9.3).
Proof: {
AI05-0299-1}
3.9.1 prohibits record extensions whose parent
type is a synchronized tagged type, and this subclause requires tagged
types to have a record extension. Thus there are no legal derivations.
Note that a synchronized interface can be used as a progenitor in an
interface_type_definition
as well as in task and protected types, but we do not allow concrete
extensions of any synchronized tagged type.
Examples
Examples of derived
type declarations:
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
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
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
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:
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 95 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 95.
begin
...
if F > 1 then ... -- legal in Ada 83, ambiguous in Ada 95
Extensions to Ada 83
A derived type may override the discriminants
of the parent by giving a new
discriminant_part.
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
We now talk about the classes to which a type
belongs, rather than a single class.
Extensions to Ada 95
{
AI95-00419-01}
A derived type may specify that it is a limited type. This is required
for interface ancestors (from which limitedness is not inherited), but
it is generally useful as documentation of limitedness.
Wording Changes from Ada 95
{
AI95-00391-01}
Defined the result of functions for null extensions (which we no longer
require to be overridden - see
3.9.3).
{
AI95-00442-01}
Defined the term “category of types” and used it in wording
elsewhere; also specified the language-defined categories that form classes
of types (this was never normatively specified in Ada 95).
Incompatibilities With Ada 2005
{
AI05-0096-1}
Correction: Added a (re)check that limited
type extensions never are derived from nonlimited types in generic private
parts. This is disallowed as it would make it possible to pass a limited
object to a nonlimited class-wide type, which could then be copied. This
is only possible using Ada 2005 syntax, so examples in existing programs
should be rare.
Wording Changes from Ada 2005
{
AI05-0110-1}
Correction: Added wording to clarify that the characteristics
of derived types are formally defined here. (This is the only place in
the Standard that actually spells out what sorts of things are actually
characteristics, which is rather important.)
{
AI05-0164-1}
Correction: Added wording to ensure that anonymous access-to-subprogram
types don't get modified on derivation.
Ada 2005 and 2012 Editions sponsored in part by Ada-Europe