3.9 Tagged Types and Type Extensions
types and type extensions support object-oriented programming, based
on inheritance with extension and run-time polymorphism via dispatching
Language Design Principles
The intended implementation model is for the static portion of 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. Tags for nested type extensions must also have a dynamic
part that identifies the particular elaboration of the type.
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.
Primitive operations of type extensions that are declared at a level
deeper than the level of the ultimate ancestor from which they are derived
can be represented by wrappers that use the dynamic part of the tag to
call the actual primitive operation. The dynamic part would generally
be some way to represent the static link or display necessary for making
a nested call. One implementation strategy would be to store that information
in the extension part of such nested type extensions, and use the dynamic
part of the tag to point at it. (That way, the “dynamic”
part of the tag could be static, at the cost of indirect access.)
If the tagged type is descended from any interface types, it also will
need to include “subtags” (one for each interface) that describe
the mapping of the primitive operations of the interface to the primitives
of the type. These subtags could directly reference the primitive operations
(for faster performance), or simply provide the tag “slot”
numbers for the primitive operations (for easier derivation). In either
case, the subtags would be used for calls that dispatch through a class-wide
type of the interface.
Other implementation models are possible.
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.
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 although
a call which is dispatching on an interface may require a lookup of the
appropriate interface subtag.
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).
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.
All views of a type should share the same type
descriptor and the same tag.
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.
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.
When a type extension is declared, if there is any place within its immediate
scope where a certain subprogram of the parent or progenitor 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.
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.
A record type or private type that has the reserved
in its declaration is called a tagged
In addition, an interface type is a tagged type, as is a task or protected
type derived from an interface (see 3.9.4
[When deriving from a tagged type, as for any derived type, additional
primitive subprograms may be defined, and inherited primitive subprograms
may be overridden.]
type is called an extension
of its ancestor types, or simply a
extension is also a tagged type, and is a record extension
a private extension
of some other tagged type, or a noninterface
synchronized tagged type (see 3.9.4
). A record
extension is defined by a derived_type_definition
with a record_extension_part
)[, which may include the definition
of additional components]. A private extension, which is a partial view
of a record extension or of a synchronized tagged type, can be declared
in the visible part of a package (see 7.3
or in a generic formal part (see 12.5.1
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.
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. Similarly, by using an overriding_indicator
), the user can declare that a subprogram
is intended to be overriding, and get an error message when they made
a mistake. The use of overriding_indicator
is highly recommended in new code that does not need to be compatible
with Ada 95.
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
to the operand (see 3.9.2
a tag to control which body to execute is called dispatching
The tag of a specific tagged type identifies the full_type_declaration
of the type, and for a type extension, is sufficient to uniquely identify
the type among all descendants of the same ancestor. 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 and with all of its ancestors
(if any) also local to the generic body, the language does not specify
whether repeated instantiations of the generic body result in distinct
In most cases, a tag need only identify a particular tagged type declaration,
and can therefore be a simple link-time-known address. However, for tag
checks (see 3.9.2
) it is essential that each
descendant (that currently exists) of a given type have a unique tag.
Hence, for types declared in shared generic bodies where an ancestor
comes from outside the generic, or for types declared at a deeper level
than an ancestor, the tag needs to be augmented with some kind of dynamic
descriptor (which may be a static link, global display, instance descriptor
pointer, or combination). This implies that type Tag may need to be two
words, the second of which is normally null, but in these identified
special cases needs to include a static link or equivalent. Within an
object of one of these types with a two-word tag, the two parts of the
tag would typically be separated, one part as the first word of the object,
the second placed in the first extension part that corresponds to a type
declared more nested than its parent or declared in a shared generic
body when the parent is declared outside. Alternatively, by using an
extra level of indirection, the type Tag could remain a single-word.
For types that are not type extensions (even for ones declared in nested
scopes), we do not require that repeated elaborations of the same full_type_declaration
correspond to distinct tags. This was done so that Ada 2005 implementations
of tagged types could maintain representation compatibility with Ada
95 implementations. Only type extensions that were not allowed in Ada
95 require additional information with the tag.
To be honest:
The wording “is sufficient to uniquely identify the type among
all descendants of the same ancestor” only applies to types that
currently exist. It is not necessary to distinguish between descendants
that currently exist, and descendants of the same type that no longer
exist. For instance, the address of the stack frame of the subprogram
that created the tag is sufficient to meet the requirements of this rule,
even though it is possible, after the subprogram returns, that a later
call of the subprogram could have the same stack frame and thus have
an identical tag.
The following language-defined
library package exists:
Tag is private
Expanded_Name(T : Tag) return
Wide_Expanded_Name(T : Tag) return
Wide_Wide_Expanded_Name(T : Tag) return
External_Tag(T : Tag) return
Internal_Tag(External : String) return
Descendant_Tag(External : String; Ancestor : Tag) return
Is_Descendant_At_Same_Level(Descendant, Ancestor : Tag)
Tag_Array is array
Interface_Ancestor_Tags (T : Tag) return
Is_Abstract (T : Tag) return
... -- not specified by the language
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.
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.
This is similar to the requirement that all access values be initialized
The function Wide_Wide_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
The result of
Tags.Wide_Wide_Expanded_Name for types declared within an unnamed block_statement
The function Expanded_Name (respectively, Wide_Expanded_Name) returns
the same sequence of graphic characters as that defined for Wide_Wide_Expanded_Name,
if all the graphic characters are defined in Character (respectively,
Wide_Character); otherwise, the sequence of characters is implementation
defined, but no shorter than that returned by Wide_Wide_Expanded_Name
for the same value of the argument.
Implementation defined: The sequence
of characters of the value returned by Tags.Expanded_Name (respectively,
Tags.Wide_Expanded_Name) when some of the graphic characters of Tags.Wide_Wide_Expanded_Name
are not defined in Character (respectively, Wide_Character).
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
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 specified via an attribute_definition_clause
The string returned by the functions Expanded_Name, Wide_Expanded_Name,
Wide_Wide_Expanded_Name, and External_Tag has lower bound 1.
The function Internal_Tag returns a 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. Tag_Error is also raised
if the specific type identified is a library-level type whose tag has
not yet been created (see 13.14
The check for uncreated library-level types prevents a reference to the
type before execution reaches the freezing point of the type. This is
important so that T'Class'Input or an instance of Tags.Generic_Dispatching_Constructor
do not try to create an object of a type that hasn't been frozen (which
might not have yet elaborated its constraints). We don't require this
behavior for non-library-level types as the tag can be created multiple
times and possibly multiple copies can exist at the same time, making
the check complex.
The function Descendant_Tag returns the (internal) tag for the type that
corresponds to the given external tag and is both a descendant of the
type identified by the Ancestor tag and has the same accessibility level
as the identified ancestor. Tag_Error is raised if External is not the
external tag for such a type. Tag_Error is also raised if the specific
type identified is a library-level type whose tag has not yet been created,
or if the given external tag identifies more than one type that has the
appropriate Ancestor and accessibility level.
Reason: Descendant_Tag is used by T'Class'Input
to identify the type identified by an external tag. Because there can
be multiple elaborations of a given type declaration, Internal_Tag does
not have enough information to choose a unique such type. Descendant_Tag
does not return the tag for types declared at deeper accessibility levels
than the ancestor because there could be ambiguity in the presence of
recursion or multiple tasks. Descendant_Tag can be used in constructing
a user-defined replacement for T'Class'Input.
Rules for specifying external tags will usually prevent an external tag
from identifying more than one type. However, an external tag can identify
multiple types if a generic body contains a derivation of a tagged type
declared outside of the generic, and there are multiple instances at
the same accessibility level as the type. (The Standard allows default
external tags to not be unique in this case.)
The function Is_Descendant_At_Same_Level returns True if the Descendant
tag identifies a type that is both a descendant of the type identified
by Ancestor and at the same accessibility level. If not, it returns False.
Reason: Is_Descendant_At_Same_Level (or
something similar to it) is used by T'Class'Output to determine whether
the item being written is at the same accessibility level as T. It may
be used to determine prior to using T'Class'Output whether Tag_Error
will be raised, and also can be used in constructing a user-defined replacement
For the purposes of the dynamic semantics of functions Descendant_Tag
and Is_Descendant_At_Same_Level, a tagged type T2 is a descendant
of a type T1 if it is the same as T1, or if its parent type or one of
its progenitor types is a descendant of type T1 by this rule[, even if
at the point of the declaration of T2, one of the derivations in the
chain is not visible].
In other contexts, “descendant”
is dependent on visibility, and the particular view a derived type has
of its parent type. See 7.3.1
The function Parent_Tag returns the tag of the parent type of the type
whose tag is T. If the type does not have a parent type (that is, it
was not defined declared
by a derived_type_definition derived_type_declaration
then No_Tag is returned.
The parent type is always the parent of the full type; a private extension
appears to define a parent type, but it does not (only the various forms
of derivation do that). As this is a run-time operation, ignoring privacy privateness
The function Interface_Ancestor_Tags returns an array containing the
tag of each interface ancestor type of the type whose tag is T, other
than T itself. The lower bound of the returned array is 1, and the order
of the returned tags is unspecified. Each tag appears in the result exactly
once.[ If the type whose tag is T has no interface ancestors, a null
array is returned.]
Ramification: The result of Interface_Ancestor_Tags
includes the tag of the parent type, if the parent is an interface.
Indirect interface ancestors are included in
the result of Interface_Ancestor_Tags. That's because where an interface
appears in the derivation tree has no effect on the semantics of the
type; the only interesting property is whether the type has an interface
as an ancestor.
The function Is_Abstract returns True if the type whose tag is T is abstract,
and False otherwise.
For every subtype S of a tagged type T (specific
or class-wide), the following attributes are defined:
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).
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.
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.
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.
The Class attribute is not defined for untagged subtypes (except for
incomplete types and private types whose full view is tagged —
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
S'Tag denotes the tag of the
(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.
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.
Given a prefix
X that is of a class-wide tagged type [(after any implicit dereference)],
the following attribute is defined:
X'Tag denotes the tag of X. The
value of this attribute is of type Tag.
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
T (<>) is abstract tagged limited private
Parameters (<>) is limited private
Constructor (Params : not null access
T is abstract
(The_Tag : Tag;
Params : not null access
Convention => Intrinsic;
Tags.Generic_Dispatching_Constructor provides a mechanism to create an
object of an appropriate type from just a tag value. The function Constructor
is expected to create the object given a reference to an object of type
Discussion: This specification is designed
to make it easy to create dispatching constructors for streams; in particular,
this can be used to construct overridings for T'Class'Input.
Note that any tagged type will match T (see
The tag associated
with an object of a tagged type is determined as follows:
The tag of a stand-alone object,
a component, or an aggregate
of a specific tagged type T
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.
The tag of an object created
by an allocator for an access type with a specific designated tagged
, identifies T
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.
The tag of an object of a class-wide
tagged type is that of its initialization expression.
The tag of an object (even
a class-wide one) cannot be changed after it is initialized, since a
raises Constraint_Error if the tags don't match, and a “specific”
does not affect the tag.
The tag of the result returned
by a function whose result type is a specific tagged type T
For a limited tagged type, the return object is “built in place”
in the ultimate result object with the appropriate tag. For a nonlimited
type, a new anonymous object with the appropriate tag is created as part
of the function return. See 6.5
The tag of the result returned by a function with
a class-wide result type is that of the return object.
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
Tag_Error is raised by a call of Descendant_Tag, Expanded_Name, External_Tag,
Interface_Ancestor_Tags, Is_Abstract, Is_Descendant_At_Same_Level, Parent_Tag,
Wide_Expanded_Name, or Wide_Wide_Expanded_Name if any tag passed is No_Tag.
An instance of Tags.Generic_Dispatching_Constructor raises Tag_Error
if The_Tag does not represent a concrete descendant of T or if the innermost
master (see 7.6.1
) of this descendant is
not also a master of the instance. Otherwise, it dispatches to the primitive
function denoted by the formal Constructor for the type identified by
The_Tag, passing Params, and returns the result. Any exception raised
by the function is propagated.
The tag check checks both
that The_Tag is in T'Class, and that it is not abstract. These checks
are similar to the ones required by streams for T'Class'Input (see 13.13.2
In addition, there is a check that the tag identifies a type declared
on the current dynamic call chain, and not a more nested type or a type
declared by another task. This check is not necessary for streams, because
the stream attributes are declared at the same dynamic level as the type
If an internal tag provided to an instance of Tags.Generic_Dispatching_Constructor
or to any subprogram declared in package Tags identifies either a type
that is not library-level and whose tag has not been created (see 13.14
or a type that does not exist in the partition at the time of the call,
then execution is erroneous.
One reason that a type
might not exist in the partition is that the tag refers to a type whose
declaration was elaborated as part of an execution of a subprogram_body
which has been left (see 7.6.1
We exclude tags of library-level types from
the current execution of the partition, because misuse of such tags should
always be detected. T'Tag freezes the type (and thus creates the tag),
and Internal_Tag and Descendant_Tag cannot return the tag of a library-level
type that has not been created. All ancestors of a tagged type must be
frozen no later than the (full) declaration of a type that uses them,
so Parent_Tag and Interface_Ancestor_Tags cannot return a tag that has
not been created. Finally, library-level types never cease to exist while
the partition is executing. Thus, if the tag comes from a library-level
type, there cannot be erroneous execution (the use of Descendant_Tag
rather than Internal_Tag can help ensure that the tag is of a library-level
type). This is also similar to the rules for T'Class'Input (see 13.13.2
Ada 95 allowed Tag_Error in this case, or expected the functions to work.
This worked because most implementations used tags constructed at link-time,
and each elaboration of the same type_declaration
produced the same tag. However, Ada 2005 requires at least part of the
tags to be dynamically constructed for a type derived from a type at
a shallower level. For dynamically constructed tags, detecting the error
can be expensive and unreliable. To see this, consider a program containing
two tasks. Task A creates a nested tagged type, passes the tag to task
B (which saves it), and then terminates. The nested tag (if dynamic)
probably will need to refer in some way to the stack frame for task A.
If task B later tries to use the tag created by task A, the tag's reference
to the stack frame of A probably is a dangling pointer. Avoiding this
would require some sort of protected tag manager, which would be a bottleneck
in a program's performance. Moreover, we'd still have a race condition;
if task A terminated after the tag check, but before the tag was used,
we'd still have a problem. That means that all of these operations would
have to be serialized. That could be a significant performance drain,
whether or not nested tagged types are ever used. Therefore, we allow
execution to become erroneous as we do for other dangling pointers. If
the implementation can detect the error, we recommend that Tag_Error
The implementation of Internal_Tag and Descendant_Tag may raise Tag_Error
if no specific type corresponding to the string External passed as a
parameter exists in the partition at the time the function is called,
or if there is no such type whose innermost master is a master of the
point of the function call.
Locking would be required to ensure that the mapping of strings to tags
never returned tags of types which no longer exist, because types can
cease to exist (because they belong to another task, as described above)
during the execution of these operations. Moreover, even if these functions
did use locking, that would not prevent the type from ceasing to exist
at the instant that the function returned. Thus, we do not require the
overhead of locking; hence the word “may” in this rule.
Internal_Tag should return the tag of a type, if one exists, whose innermost
master is a master of the point of the function call.
Implementation Advice: Tags.Internal_Tag
should return the tag of a type, if one exists, whose innermost master
is a master of the point of the function call..
It's not helpful if Internal_Tag returns the tag of some type in another
task when one is available in the task that made the call. We don't require
this behavior (because it requires the same implementation techniques
we decided not to insist on previously), but encourage it.
There is no Advice for the result of Internal_Tag if no such type exists.
In most cases, the Implementation Permission can be used to raise Tag_Error,
but some other tag can be returned as well.
70 A type declared with the reserved word
should normally be declared in a package_specification
so that new primitive subprograms can be declared for it.
71 Once an object has been created, its
tag never changes.
72 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 aggregate
have to be explicitly qualified with a specific type when their expected
type is class-wide.
The capability provided by Tags.Generic_Dispatching_Constructor is sometimes
known as a factory
Examples of tagged
type Point is tagged
X, Y : Real := 0.0;
type Expression is tagged null record;
-- Components will be added by each extension
Extensions to Ada 83
Tagged types are a new concept.
Inconsistencies With Ada 95
Added wording specifying
that Internal_Tag must raise Tag_Error if the tag of a library-level
type has not yet been created. Ada 95 gave an Implementation Permission
to do this; we require it to avoid erroneous execution when streaming
in an object of a library-level type that has not yet been elaborated.
This is technically inconsistent; a program that used Internal_Tag outside
of streaming and used a compiler that didn't take advantage of the Implementation
Permission would not have raised Tag_Error, and may have returned a useful
tag. (If the tag was used in streaming, the program would have been erroneous.)
Since such a program would not have been portable to a compiler that
did take advantage of the Implementation Permission, this is not a significant
We now define the lower bound of the string returned from [[Wide_]Wide_]Expanded_Name
and External_Name. This makes working with the returned string easier,
and is consistent with many other string-returning functions in Ada.
This is technically an inconsistency; if a program depended on some other
lower bound for the string returned from one of these functions, it could
fail when compiled with Ada 2005. Such code is not portable even between
Ada 95 implementations, so it should be very rare.
Incompatibilities With Ada 95
Constant No_Tag, and functions Parent_Tag, Interface_Ancestor_Tags,
Descendant_Tag, Is_Descendant_At_Same_Level, Wide_Expanded_Name, and
Wide_Wide_Expanded_Name are added to Ada.Tags. If Ada.Tags is referenced
in a use_clause
and an entity E
with the same defining_identifier
as a new entity in Ada.Tags is defined in a package that is also referenced
in a use_clause
the entity E
may no longer be use-visible, resulting in errors.
This should be rare and is easily fixed if it does occur.
Extensions to Ada 95
Generic function Tags.Generic_Dispatching_Constructor is new.
Wording Changes from Ada 95
Added wording to define that tags for all descendants of a tagged type
must be distinct. This is needed to ensure that more nested type extensions
will work properly. The wording does not require implementation changes
for types that were allowed in Ada 95.
Inconsistencies With Ada 2005
Added wording specifying that
Dependent_Tag must raise Tag_Error if there is more than one type which
matches the requirements. If an implementation had returned a random
tag of the matching types, a program may have worked properly. However,
such a program would not be portable (another implementation may return
a different tag) and the conditions that would cause the problem are
unlikely (most likely, a tagged type extension declared in a generic
body with multiple instances in the same scope).
Incompatibilities With Ada 2005
Function Is_Abstract is added to Ada.Tags. If Ada.Tags
is referenced in a use_clause
and an entity E
with the defining_identifier
Is_Abstract is defined in a package that is also referenced in a use_clause
the entity E
may no longer be use-visible, resulting in errors.
This should be rare and is easily fixed if it does occur.
Wording Changes from Ada 2005
We explicitly define the meaning of "descendant"
at runtime, so that it does not depend on visibility as does the usual
Ada 2005 and 2012 Editions sponsored in part by Ada-Europe