Rationale for Ada 2005
2.7 Overriding and overloading
One of the key goals in the design of Ada was to
encourage the writing of correct programs. It was intended that the structure,
strong typing, and so on should ensure that many errors which are not
detected by most languages until run time should be caught at compile
time in Ada. Unfortunately the introduction of type extension and overriding
in Ada 95 produced a situation where careless errors in subprogram profiles
lead to errors which are awkward to detect.
The Introduction described
two typical examples. The first concerns the procedure Finalize.
Consider
with Ada.Finalization; use Ada.Finalization;
package Root is
type T is new Controlled with ... ;
procedure Op(Obj: in out T; Data: in Integer);
procedure Finalise(Obj: in out T);
end Root;
We have inadvertently written Finalise
rather than Finalize. This means that Finalize
does not get overridden as expected and so the expected behaviour does
not occur on finalization of objects of type T.
In Ada 2005 we can
prefix the declaration with
overriding
overriding
procedure Finalize(Obj: in out T);
And now if we inadvertently write Finalise
then this will be detected during compilation.
Similar errors can
occur in a profile. If we write
package Root.Leaf is
type NT is new T with null record;
overriding -- overriding indicator
procedure Op(Obj: in out NT; Data: in String);
end Root.Leaf;
then the compiler will detect that the new procedure
Op has a parameter of type String
rather than Integer.
However if we do want
a new operation then we can write
not overriding
procedure Op(Obj: in out NT; Data: in String);
The overriding indicators
can also be used with abstract subprograms, null procedures, renamings,
instantiations, stubs, bodies and entries (we will deal with entries
in the chapter on tasking –
5.3). So
we can have
overriding
procedure Pap(X: TT) is abstract;
overriding
procedure Pep(X: TT) is null;
overriding
procedure Pip(Y: TT) renames Pop;
not overriding
procedure Poop is new Peep( ... );
overriding
procedure Pup(Z: TT) is separate;
overriding
procedure Pup(X: TT) is
begin ... end Pup;
We do not need to apply
an overriding indicator to both a procedure specification and body but
if we do then they naturally must not conflict. It is expected that overriding
indicators will typically only be given on specifications but they would
be appropriate in the case of a body standing alone as in the example
of Action in the previous section. So we might
have
procedure Green_To_Red(L: in List) is
type GTR_It is new Iterator with null record;
overriding
procedure Action(It: in out GTR_It; C: in out Colour) is
begin
if C = Green then C := Red; end if;
end Action;
...
The overriding indicators are optional for two reasons.
One is simply for compatibility with Ada 95. The other concerns awkward
problems with private types and generics.
Consider
package P is
type NT is new T with private;
procedure Op(X: T);
private
Now suppose the type
T does not have an operation Op.
Then clearly it would be wrong to write
package P is
type NT is new T with private; -- T has no Op
overriding -- illegal
procedure Op(X: T);
private
because that would violate the information known
in the partial view.
But suppose that in
fact it turns out that in the private part the type NT
is actually derived from TT (itself derived
from T) and that TT
does have an operation Op.
private
type NT is new TT with ... -- TT has Op
end P;
In such a case it turns out in the end that Op
is in fact overriding after all. We can then put an overriding indicator
on the body of Op since at that point we do
know that it is overriding.
Equally of course we should not specify not overriding
for Op in the visible part because that might
not be true either (since it might be that TT
does have Op). However if we did put not
overriding on the partial view then that would not in itself be an
error but would simply constrain the full view not to be overriding and
thus ensure that TT does not have Op.
Of course if T itself
has Op then we could and indeed should put
an overriding indicator in the visible part since we know that to be
the truth at that point.
The general rule is not to lie. But the rules are
slightly different for overriding and not overriding. For
overriding it must not lie at the point concerned. For not
overriding it must not lie anywhere.
This asymmetry is a bit like presuming the prisoner
is innocent until proved guilty. We sometimes start with a view in which
an operation appears not to be overriding and then later on we find that
it is overriding after all. But the reverse never happens – we
never start with a view in which it is overriding and then later discover
that it was not. So the asymmetry is real and justified.
There are other similar but more complex problems
with private types concerning implicit declarations where the implicit
declaration turns up much later and is overriding but has no physical
presence on which to hang the indicator. It was concluded that by far
the best approach to these problems was just to say that the overriding
indicator is always optional. We cannot expect to find all the bugs in
a program through syntax and static semantics; the key goal here is to
provide a simple way of finding most of them.
Similar problems arise
with generics. As is usual with generics the rules are checked in the
generic itself and then rechecked upon instantiation (in this case for
uses within both the visible part and private part of the specification).
Consider
generic
type GT is tagged private;
package GP is
type NT is new GT with private;
overriding -- illegal, GT has no Op
procedure Op(X: NT);
private
This has to be illegal because GT
has no operation Op. Of course the actual
type at instantiation might have Op but the
check has to pass both in the generic and in the instantiation.
On the other hand saying
not overriding is allowed
generic
type GT is tagged private;
package GP is
type NT is new GT with private;
not overriding -- legal, GT has no Op
procedure Op(X: NT);
private
However, in this case we cannot instantiate GP
with a type that does have an operation Op
because it would fail when checked on the instantiation. So in a sense
this imposes a further contract on the generic. If we do not want to
impose this restriction then we must not give an overriding indicator
on the procedure Op for NT.
Another situation arises
when the generic formal is derived
generic
type GT is new T with private;
package GP is
type NT is new GT with private;
overriding -- legal if T has Op
procedure Op(X: NT);
private
In this case it might be that the type T
does have an operation Op in which case we
can give the overriding indicator.
We might also try
generic
type GT is tagged private;
with procedure Op(X: GT);
package GP is
type NT is new GT with private;
overriding -- illegal, Op not primitive
procedure Op(X: NT);
private
But this is incorrect because although GT
has to have an operation corresponding to Op
as specified in the formal parameter list, nevertheless it does not have
to be a primitive operation nor does it have to be called Op
and thus it isn't inherited.
It should also be observed
that overriding indicators can be used with untagged types although they
have been introduced primarily to avoid problems with dispatching operations.
Consider
package P is
type T is private;
function "+" (Left, Right: T) return T;
private
type T is range 0 .. 100; -- "+" overrides
end P;
as opposed to
package P is
type T is private;
function "+" (Left, Right: T) return T;
private
type T is (Red, White, Blue); -- "+" does not override
end P;
The point is that the partial view does not reveal
whether overriding occurs or not – nor should it since either implementation
ought to be acceptable. We should therefore remain silent regarding overriding
in the partial view. This is similar to the private extension and generic
cases discussed earlier. Inserting overriding would be illegal
on both examples, while not overriding would be allowed only on
the second one (which would constrain the implementation as in the previous
examples). Again, it is permissible to put an overriding indicator on
the body of "+" to indicate whether
or not it does override.
It is also possible for a subprogram to be primitive
for more than one type (this cannot happen for more than one tagged type
but it can happen for untagged types or one tagged type and some untagged
types). It could then be overriding for some types and not overriding
for others. In such a case it is considered to be overriding as a whole
and any indicator should reflect this.
The possibility of having a pragma which would enforce
the use of overriding indicators (so that they too could not be inadvertently
omitted) was eventually abandoned largely because of the private type
and generic problem which made the topic very complicated.
Note the recommended layout, an overriding indicator
should be placed on the line before the subprogram specification and
aligned with it. This avoids disturbing the layout of the specification.
It is hoped that programmers will use overriding
indicators freely. As mentioned in the Introduction, they are very valuable
for preventing nasty errors during maintenance. Thus if we add a further
parameter to an operation such as Op for a root type and all type extensions
have overriding indicators then the compiler will report an error if
we do not modify the operators of all the derived types correctly.
We now turn to a minor change in the overriding rules
for functions with controlling results.
The reader may recall the general rule in Ada 95
that a function that is a primitive operation of a tagged type and returns
a value of the type, must always be overridden when the type is extended.
This is because the function for the extended type must create values
for the additional components. This rule is sometimes phrased as saying
that the function "goes abstract" and so has to be overridden
if the extended type is concrete. The irritating thing about the rule
in Ada 95 is that it applies even if there are no additional components.
Thus consider a generic
version of the set package of Section 3
generic
type Element is private;
package Sets is
type Set is tagged private;
function Empty return Set;
function Unit(E: Element) return Set;
function Union(S, T: Set) return Set;
function Intersection(S, T: Set) return Set;
...
end Sets;
Now suppose we declare
an instantiation thus
package My_Sets is new Sets(My_Type);
This results in the
type Set and all its operations being declared
inside the package My_Sets. However, for various
reasons we might wish to have the type and its operations at the current
scope. One reason could just be for simplicity of naming so that we do
not have to write My_Sets.Set and My_Sets.Union
and so on. (We might be in a regime where use clauses are forbidden.)
An obvious approach is to derive our own type locally so that we have
package My_Sets is new Sets(My_Type);
type My_Set is new My_Sets.Set with null record;
Another situation where
we might need to do this is where we wish to use the type Set
as the full type for a private type thus
type My_Set is private;
private
package My_Sets is new Sets(My_Type);
type My_Set is new My_Sets.Set with null record;
But this doesn't work
nicely in Ada 95 since all the functions have controlling results and
so "go abstract" and therefore have to be overridden with wrappers
thus
function Union(S, T: My_Set) return My_Set is
begin
return My_Set(My_Sets.Union(My_Sets.Set(S), My_Sets.Set(T)));
end Union;
This is clearly a dreadful nuisance. Ada 2005 sensibly
allows the functions to be inherited provided that the extension is visibly
null (and that there is no new discriminant part) and so no overriding
is required. This new facility will be much appreciated by users of the
new container library in Ada 2005 which has just this style of generic
packages which export tagged types.
The final topic to
be discussed concerns a problem with overloading and untagged types.
Remember that the concept of abstract subprograms was introduced into
Ada 95 largely for the purpose of tagged types. However it can also be
used with untagged types on derivation if we do not want an operation
to be inherited. This often happens with types representing physical
measurements. Consider
type Length is new Float;
type Area is new Float;
These types inherit
various undesirable operations such as multiplying a length by a length
to give a length when of course we want an area. We can overcome this
by overriding them with abstract operations. Thus
function "*" (L, R: Length) return Length is abstract;
function "*" (L, R: Area) return Area is abstract;
function "*" (L, R: Length) return Area;
We have also declared a function to multiply two
lengths to give an area. So now we have two functions multiplying two
lengths, one returns a length but is abstract and so can never be called
and the other correctly returns an area.
Now suppose we want
to print out some values of these types. We might declare a couple of
functions delivering a string image thus
function Image(L: Length) return String;
function Image(L: Area) return String;
And then we decide
to write
X: Length := 2.5;
...
Put_Line(Image(X * X)); -- ambiguous in 95
This fails to compile in Ada 95 since it is ambiguous
because both Image and "*"
are overloaded. The problem is that although the function "*"
returning a length is abstract it nevertheless is still there and is
considered for overload resolution. So we don't know whether we are calling
Image on a length or on an area because we
don't know which "*" is involved.
So declaring the operation as abstract does not really
get rid of the operation at all, it just prevents it from being called
but its ghost lives on and is a nuisance.
In Ada 2005 this is overcome by a new rule that says
"abstract nondispatching subprograms are ignored during overload
resolution". So the abstract "*"
is ignored and there is no ambiguity in Ada 2005.
Note that this rule does not apply to dispatching
operations of tagged types since we might want to dispatch to a concrete
operation of a descendant type. But it does apply to operations of a
class-wide type.
© 2005, 2006 John Barnes Informatics.
Sponsored in part by: