3.9.3 Abstract Types and Subprograms
1/2
{
AI95-00345-01}
[
{abstract type} {abstract
data type (ADT): See also abstract type} {ADT
(abstract data type): See also abstract type} {concrete
type: See nonabstract type} An
abstract
type is a tagged type intended for use as
an
ancestor of other types a parent type for
type extensions, but which is not allowed to have objects of its
own.
{abstract subprogram}
{concrete subprogram:
See nonabstract subprogram} 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.]
1.a.1/2
Glossary entry: {Abstract
type} 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.
Language Design Principles
1.a
An abstract subprogram has no body, so the rules
in this clause are designed to ensure (at compile time) that the body
will never be invoked. We do so primarily by disallowing the creation
of values of the abstract type. Therefore, since type conversion and
parameter passing don't change the tag, we know we will never get a class-wide
value with a tag identifying an abstract type. This means that we only
have to disallow nondispatching calls on abstract subprograms (dispatching
calls will never reach them).
Syntax
1.1/2
Static Semantics
1.2/2
{
AI95-00345-01}
{abstract type}
{type (abstract)}
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
2/2
{
AI95-00345-01}
Only a tagged type shall have {abstract
type} {type
(abstract)} An abstract type is
a specific type that has the reserved word
abstract in
its declaration.
Only a tagged type is allowed to
be declared abstract.
2.a
Ramification: Untagged types are never
abstract, even though they can have primitive abstract subprograms. Such
subprograms cannot be called, unless they also happen to be dispatching
operations of some tagged type, and then only via a dispatching call.
2.b
Class-wide types are never abstract. If T is
abstract, then it is illegal to declare a stand-alone object of type
T, but it is OK to declare a stand-alone object of type T'Class; the
latter will get a tag from its initial value, and this tag will necessarily
be different from T'Tag.
3/2
{
AI95-00260-02}
{
AI95-00348-01}
{abstract subprogram} {subprogram
(abstract)} A subprogram declared by an
abstract_subprogram_declaration or
a formal_abstract_subprogram_declaration (see
12.6)(see 6.1)
is an
abstract subprogram. If it is a primitive subprogram of
a tagged type, then the tagged type shall be abstract.
3.a
Ramification:
Note that for a private type, this applies to both views. The following
is illegal:
3.b
package P is
type T is abstract tagged private;
function Foo (X : T) return Boolean is abstract; -- Illegal!
private
type T is tagged null record; -- Illegal!
X : T;
Y : Boolean := Foo (T'Class (X));
end P;
3.c
The full view of T is not abstract, but has
an abstract operation Foo, which is illegal. The two lines marked "-- Illegal!" are illegal when taken together.
3.d/2
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
(if not terribly useful). For example:
3.e/1
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 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 is abstract;
end Q;
3.f
It seemed silly to make the derivative of My_Field_Size
illegal, just because there was an implicitly declared abstract subprogram
that was not primitive on some tagged type. Other rules could be formulated
to solve this problem, but the current ones seem like the simplest.
3.g/2
{
AI95-00310-01}
In Ada 2005, abstract primitive subprograms of
an untagged type may be used to “undefine” an operation.
3.h/2
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.
4/2
{
AI95-00251-01}
{
AI95-00334-01}
{
AI95-00391-01}
If a type has an implicitly declared primitive
subprogram that is inherited or is the predefined equality operator,
and the corresponding primitive subprogram of For
a derived type, if the parent or ancestor type
is
abstract or is a function with a controlling access result, or if a type
other than a null extension inherits a has
an abstract primitive subprogram, or a primitive function with
a controlling result, then:
5/2
- {AI95-00251-01}
{AI95-00334-01}
If the derived type is abstract or untagged,
the implicitly declared inherited
subprogram is abstract.
5.a
Ramification: Note that it is possible
to override a concrete subprogram with an abstract one.
6/2
- {AI95-00391-01}
Otherwise, the subprogram shall be overridden with a nonabstract subprogram or, in the case of a private extension inheriting a 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.{require
overriding} However, if the type
is a generic formal type, the subprogram need not be overridden for the
formal type itself; [a nonabstract version will necessarily be provided
by the actual type.]
6.a/2
Reason: {
AI95-00228-01}
{
AI95-00391-01}
A function that returns the parent type
requires
overriding becomes abstract for
a an
abstract type extension
(or becomes abstract
for an abstract type)(if not overridden)
because conversion from a parent type to a type extension is not defined,
and function return semantics is defined in terms of conversion
(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.)
6.b
Note that the
overriding required above can be in the private part, which allows the
following:
6.c
package Pack1 is
type Ancestor is abstract ...;
procedure Do_Something(X : in Ancestor) is abstract;
end Pack1;
6.d
with Pack1; use Pack1;
package Pack2 is
type T1 is new Ancestor with record ...;
-- A concrete type.
procedure Do_Something(X : in T1); -- Have to override.
end Pack2;
6.e
with Pack1; use Pack1;
with Pack2; use Pack2;
package Pack3 is
type T2 is new Ancestor with private;
-- A concrete type.
private
type T2 is new T1 with -- Parent different from ancestor.
record ... end record;
-- Here, we inherit Pack2.Do_Something.
end Pack3;
6.f/2
{
AI95-00228-01}
T2 inherits an abstract Do_Something, but
T2 T
is not abstract, so Do_Something has to be overridden. However, it is
OK to override it in the private part. In this case, we override it by
inheriting a concrete version from a different type. Nondispatching calls
to Pack3.Do_Something are allowed both inside and outside package Pack3
,
as the client “knows” that the subprogram was necessarily
overridden somewhere.
6.g/2
{
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.
6.h/2
{
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.
7
A call on an abstract subprogram shall be a dispatching
call; [nondispatching calls to an abstract subprogram are not allowed.]
7.a/2
Ramification: {
AI95-00310-01}
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).
8
The type of an
aggregate,
or of an object created by an
object_declaration
or an
allocator, or a generic formal object
of mode
in, shall not be abstract. The type of the target of an
assignment operation (see
5.2) shall not be
abstract. The type of a component shall not be abstract. If the result
type of a function is abstract, then the function shall be abstract.
8.a
Reason: This ensures that values of an
abstract type cannot be created, which ensures that a dispatching call
to an abstract subprogram will not try to execute the nonexistent body.
8.b
Generic formal objects of mode in are
like constants; therefore they should be forbidden for abstract types.
Generic formal objects of mode in out are like renamings; therefore,
abstract types are OK for them, though probably not terribly useful.
9
If a partial view is not abstract, the corresponding
full view shall not be abstract. If a generic formal type is abstract,
then for each primitive subprogram of the formal that is not abstract,
the corresponding primitive subprogram of the actual shall not be abstract.
9.a
Discussion: By contrast, we allow the
actual type to be nonabstract even if the formal type is declared abstract.
Hence, the most general formal tagged type possible is "type
T(<>) is abstract tagged limited private;".
9.b
For an abstract private extension declared in
the visible part of a package, it is only possible for the full type
to be nonabstract if the private extension has no abstract dispatching
operations.
9.c/2
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:
9.d/2
package P1 is
type T1 is abstract tagged null record;
procedure P (X : T1); -- (1)
end P1;
9.e/2
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;
9.f/2
generic
type D is abstract new P1.T1 with private;
-- procedure P (X : D); -- (4)
procedure G (X : D);
9.g/2
procedure I is new G (P2.T2); -- Illegal.
9.h/2
Type T2 inherits a non-abstract
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 non-abstract 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.
10
For an abstract type declared in a visible part,
an abstract primitive subprogram shall not be declared in the private
part, unless it is overriding an abstract subprogram implicitly declared
in the visible part. For a tagged type declared in a visible part, a
primitive function with a controlling result shall not be declared in
the private part, unless it is overriding a function implicitly declared
in the visible part.
10.a
Reason: The
“visible part” could be that of a package or a generic package.
This rule is needed because a non-abstract type extension declared outside
the package would not know about any abstract primitive subprograms or
primitive functions with controlling results declared in the private
part, and wouldn't know that they need to be overridden with non-abstract
subprograms. The rule applies to a tagged record type or record extension
declared in a visible part, just as to a tagged private type or private
extension. The rule applies to explicitly and implicitly declared abstract
subprograms:
10.b
package Pack is
type T is abstract new T1 with private;
private
type T is abstract new T2 with record ... end record;
...
end Pack;
10.c
The above example would be illegal if T1 has
a non-abstract primitive procedure P, but T2 overrides P with an abstract
one; the private part should override P with a non-abstract version.
On the other hand, if the P were abstract for both T1 and T2, the example
would be legal as is.
11/2
{
AI95-00260-02}
A generic actual subprogram shall not be an abstract subprogram
unless
the generic formal subprogram is declared by a formal_abstract_subprogram_declaration.
The
prefix of an
attribute_reference
for the Access, Unchecked_Access, or Address attributes shall not denote
an abstract subprogram.
11.a
Ramification: An abstract_subprogram_declaration
is not syntactically a subprogram_declaration.
Nonetheless, an abstract subprogram is a subprogram, and an abstract_subprogram_declaration
is a declaration of a subprogram.
11.b/2
{
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
11.1/2
{
AI95-00348-01}
{elaboration (abstract_subprogram_declaration)
[partial]} The elaboration of an abstract_subprogram_declaration
has no effect.
12
77 Abstractness is not inherited; to declare
an abstract type, the reserved word abstract has to be used in
the declaration of the type extension.
12.a
Ramification: A derived type can be abstract
even if its parent is not. Similarly, an inherited concrete subprogram
can be overridden with an abstract subprogram.
13
78 A class-wide type is never abstract.
Even if a class is rooted at an abstract type, the class-wide type for
the class is not abstract, and an object of the class-wide type can be
created; the tag of such an object will identify some nonabstract type
in the class.
Examples
14
Example of an abstract
type representing a set of natural numbers:
15
package Sets is
subtype Element_Type is Natural;
type Set is abstract tagged null record;
function Empty return Set is abstract;
function Union(Left, Right : Set) return Set is abstract;
function Intersection(Left, Right : Set) return Set is abstract;
function Unit_Set(Element : Element_Type) return Set is abstract;
procedure Take(Element : out Element_Type;
From : in out Set) is abstract;
end Sets;
16
79 Notes on the example: Given the
above abstract type, one could then derive various (nonabstract) extensions
of the type, representing alternative implementations of a set. One might
use a bit vector, but impose an upper bound on the largest element representable,
while another might use a hash table, trading off space for flexibility.
16.a
Discussion: One way to export a type
from a package with some components visible and some components private
is as follows:
16.b
package P is
type Public_Part is abstract tagged
record
...
end record;
type T is new Public_Part with private;
...
private
type T is new Public_Part with
record
...
end record;
end P;
16.c
The fact that Public_Part is abstract tells
clients they have to create objects of type T instead of Public_Part.
Note that the public part has to come first; it would be illegal to declare
a private type Private_Part, and then a record extension T of it, unless
T were in the private part after the full declaration of Private_Part,
but then clients of the package would not have visibility to T.
Extensions to Ada 95
16.d/2
{
AI95-00391-01}
{extensions to Ada 95} 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
16.e/2
16.f/2
{
AI95-00260-02}
Updated the wording to reflect the addition of
abstract formal subprograms (see 12.6).
16.g/2
{
AI95-00334-01}
The wording of shall-be-overridden was clarified
so that it clearly applies to abstract predefined equality.
16.h/2
{
AI95-00348-01}
Moved the syntax and elaboration rule for abstract_subprogram_declaration
here, so the syntax and most of the semantics are together (which is
consistent with null procedures).
16.i/2
{
AI95-00391-01}
We define the term require overriding to
make other wording easier to understand.