3.9.3 Abstract Types and Subprograms
{
AI95-00345-01}
[
An
abstract type is a tagged type intended for use as an ancestor
of other types, 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.]
Term entry: abstract type —
tagged type intended for use as an ancestor of other types, but which
is not allowed to have objects of its own
Language Design Principles
{
AI05-0299-1}
An abstract subprogram has no body, so the rules in this subclause 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).
Syntax
Static Semantics
{
AI95-00345-01}
Interface types (see
3.9.4)
are abstract types. In addition, a tagged type that has the reserved
word
abstract in its declaration is an abstract type. The class-wide
type (see
3.4.1) rooted at an abstract type
is not itself an abstract type.
Legality Rules
{
AI95-00345-01}
Only a tagged type shall have the reserved word
abstract in its
declaration.
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.
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.
Ramification:
Note that for a private type, this applies to both views. The following
is illegal:
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;
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.
Reason: {
AI95-00310-01}
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. For example:
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;
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.
{
AI95-00310-01}
In Ada 2005, abstract primitive subprograms of an untagged type may be
used to “undefine” an operation.
Ramification: {
AI95-00260-02}
Note that the second sentence does not apply to abstract formal subprograms,
as they are never primitive operations of a type.
{
AI95-00251-01}
{
AI95-00334-01}
{
AI95-00391-01}
{
AI05-0097-1}
{
AI05-0198-1}
If a type has an implicitly declared primitive subprogram that is inherited
or is a predefined operator, and the corresponding primitive subprogram
of the parent or ancestor type is abstract or is a function with a controlling
access result, or if a type other than a nonabstract null extension inherits
a function with a controlling result, then:
Ramification: {
AI05-0068-1}
These rules apply to each view of the type individually. That is necessary
to preserve privacy. For instance, in the following example:
package P is
type I is interface;
procedure Op (X : I) is abstract;
end P;
with P;
package Q is
type T is abstract new P.I with private;
-- Op inherited here.
private
type T is abstract new P.I with null record;
procedure Op (X : T) is null;
end Q;
with Q;
package R is
type T2 is new Q.T with null record;
-- Illegal. Op inherited here, but requires overriding.
end R;
If this did not depend on the view, this would
be legal. But in that case, the fact that Op is overridden in the private
part would be visible; package R would have to be illegal if no overriding
was in the private part.
Note that this means that whether an inherited
subprogram is abstract or concrete depends on where it inherited. In
the case of Q, Q.Op in the visible part is abstract, while Q.Op in the
private part is concrete. That is, R is illegal since it is an unrelated
unit (and thus it cannot see the private part), but if R had been a private
child of Q, it would have been legal.
Ramification: Note that it is possible
to override a concrete subprogram with an abstract one.
{
AI95-00391-01}
{
AI12-0080-1}
{
AI12-0444-1}
Otherwise, the subprogram shall be overridden with a nonabstract subprogram
or, in the case of a private extension inheriting a nonabstract function
with a controlling result, have a full type that is a null extension[;
for a type declared in the visible part of a package, the overriding
may be either in the visible or the private part]. Such a subprogram
is said to
require overriding.
However, if
the type is a generic formal type, the subprogram is allowed to be inherited
as is, without being overridden for the formal type itself; [a nonabstract
version will necessarily be provided by the actual type.]
Reason: {
AI95-00228-01}
{
AI95-00391-01}
A function that returns the parent type requires overriding for a type
extension (or becomes abstract for an abstract type) because conversion
from a parent type to a type extension is not defined, and function return
semantics is defined in terms of conversion (other than for a null extension;
see below). (Note that parameters of mode
in out or
out
do not have this problem, because the tag of the actual is not changed.)
Note that the
overriding required above can be in the private part, which allows the
following:
package Pack1 is
type Ancestor is abstract ...;
procedure Do_Something(X : in Ancestor) is abstract;
end Pack1;
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;
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;
{
AI95-00228-01}
T2 inherits an abstract Do_Something, but T2 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, as the client “knows”
that the subprogram was necessarily overridden somewhere.
{
AI95-00391-01}
For a null extension, the result of a function with a controlling result
is defined in terms of an
extension_aggregate
with a
null record extension part (see
3.4).
This means that these restrictions on functions with a controlling result
do not have to apply to null extensions.
{
AI95-00391-01}
However, functions with controlling access results still require overriding.
Changing the tag in place might clobber a preexisting object, and allocating
new memory would possibly change the pool of the object, leading to storage
leaks. Moreover, copying the object isn't possible for limited types.
We don't need to restrict functions that have an access return type of
an untagged type, as derived types with primitive subprograms have to
have the same representation as their parent type.
{
AI12-0413-1}
A call on an abstract subprogram shall be a dispatching call; nondispatching
calls to an abstract subprogram are not allowed.
In
addition to the places where Legality Rules normally apply (see
12.3),
these rules also apply in the private part of an instance of a generic
unit.
Ramification: {
AI95-00310-01}
{
AI12-0413-1}
If an abstract subprogram is not a dispatching operation of some tagged
type, then it cannot be called at all. In Ada 2005, such subprograms
are not even considered by name resolution (see
6.4).
However, this rule is still needed for cases that can arise in the instance
of a generic specification where Name Resolution Rules are not reapplied,
but Legality Rules are, when the equality operator for an untagged record
type is abstract, while the operator for the formal type is not abstract
(see
12.5).
{
AI05-0073-1}
{
AI05-0203-1}
{
AI12-0437-1}
The type of an
aggregate,
of an object created by an
object_declaration
or an
allocator,
or of 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. If a function has an access result type designating an abstract
type, then the function shall be abstract. The type denoted by a
return_subtype_indication
(see
6.5) shall not be abstract. A generic
function shall not have an abstract result type or an access result type
designating an abstract type.
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.
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.
{
AI05-0073-1}
Generic functions returning a formal abstract type are illegal because
any instance would have to be instantiated with a nonabstract type in
order to avoid violating the function rule (generic functions cannot
be declared abstract). But that would be an implied contract; it would
be better for the contract to be explicit by the formal type not being
declared abstract. Moreover, the implied contract does not add any capability.
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.
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;".
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.
To be honest:
{
AI95-00294-01}
In the sentence about primitive subprograms above, there is some ambiguity
as to what is meant by “corresponding” in the case where
an inherited operation is overridden. This is best explained by an example,
where the implicit declarations are shown as comments:
package P1 is
type T1 is abstract tagged null record;
procedure P (X : T1); -- (1)
end P1;
package P2 is
type T2 is abstract new P1.T1 with null record;
-- procedure P (X : T2); -- (2)
procedure P (X : T2) is abstract; -- (3)
end P2;
generic
type D is abstract new P1.T1 with private;
-- procedure P (X : D); -- (4)
procedure G (X : D);
procedure I is new G (P2.T2); -- Illegal.
Type T2 inherits a nonabstract procedure P (2)
from the primitive procedure P (1) of T1. P (2) is overridden by the
explicitly declared abstract procedure P (3). Type D inherits a nonabstract
procedure P (4) from P (1). In instantiation I, the operation corresponding
to P (4) is the one which is not overridden, that is, P (3): the overridden
operation P (2) does not “reemerge”. Therefore, the instantiation
is illegal.
{
AI05-0073-1}
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 or a controlling access result shall not be declared in the private
part, unless it is overriding a function implicitly declared in the visible
part.
Reason: The
“visible part” could be that of a package or a generic package.
This rule is needed because a nonabstract 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 nonabstract
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:
package Pack is
type T is abstract new T1 with private;
private
type T is abstract new T2 with record ... end record;
...
end Pack;
The above example would be illegal if T1 has
a nonabstract primitive procedure P, but T2 overrides P with an abstract
one; the private part should override P with a nonabstract version. On
the other hand, if the P were abstract for both T1 and T2, the example
would be legal as is.
{
AI95-00260-02}
The part about generic actual subprograms includes those given by default.
Of course, an abstract formal subprogram's actual subprogram can be abstract.
Dynamic Semantics
NOTE 1 {
AI12-0447-1}
Abstractness is not inherited; a type is abstract only if the reserved
word
abstract is used in the declaration of the type extension.
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.
NOTE 2 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
Example of an abstract
type representing a set of natural numbers:
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;
{
AI12-0442-1}
{
AI12-0452-1}
Given the above abstract type, one can derive various (nonabstract) extensions
of the type, representing alternative implementations of a set. One possibility
is to use a bit vector, but impose an upper bound on the largest element
representable, while another possible implementation is a hash table,
trading off space for flexibility.
Discussion: One way to export a type
from a package with some components visible and some components private
is as follows:
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;
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.
Extensions to Ada 95
{
AI95-00391-01}
It is not necessary to override functions with a
controlling result for a null extension. This makes it easier to derive
a tagged type to complete a private type.
Wording Changes from Ada 95
{
AI95-00260-02}
Updated the wording to reflect the addition of abstract formal subprograms
(see
12.6).
{
AI95-00334-01}
The wording of shall-be-overridden was clarified so that it clearly applies
to abstract predefined equality.
{
AI95-00391-01}
We define the term
require overriding to make other wording easier
to understand.
Incompatibilities With Ada 2005
{
AI05-0073-1}
Correction: Added rules to eliminate holes
with controlling access results and generic functions that return abstract
types. While these changes are technically incompatible, it is unlikely
that they could be used in a program without violating some other rule
of the use of abstract types.
{
AI05-0097-1}
Correction: Corrected a minor glitch having to do with abstract
null extensions. The Ada 2005 rule allowed such extensions to inherit
concrete operations in some rare cases. It is unlikely that these cases
exist in user code.
Extensions to Ada 2005
Wording Changes from Ada 2005
{
AI05-0198-1}
Correction: Clarified that the predefined operator corresponding
to an inherited abstract operator is also abstract. The Ada 2005 rules
caused the predefined operator and the inherited operator to override
each other, which is weird. But the effect is the same either way (the
operator is not considered for resolution).
{
AI05-0203-1}
Correction: Added wording to disallow abstract return objects.
These were illegal in Ada 2005 by other rules; the extension to support
class-wide type better opened a hole which has now been plugged.
Incompatibilities With Ada 2012
{
AI12-0413-1}
Correction: Clarified that a recheck is needed
in the case of an actual that is a record type with an abstract equality.
This is an incompatibility as the generic boilerplate was previously
omitted, meaning that such a recheck should not have been performed in
the private part of an instance. Usually, this would just change an elaboration
time raise of Program_Error into an error (a good thing, as the instance
will never be useful), but could break a working instance if the equality
usage is in a default expression that appears in the private part of
the generic unit and it is never used in a call. In that case, Ada 2022
will reject the instance while it would have worked in Ada 2012. As a
practical matter, it's more likely that a compiler already does the recheck
in the entire instance spec, or does not do it at all; thus for many
implementations there will be no practical incompatibility.
Ada 2005 and 2012 Editions sponsored in part by Ada-Europe