Rationale for Ada 2005

John Barnes
Table of Contents   Index   References   Search   Previous   Next 

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.

Table of Contents   Index   References   Search   Previous   Next 
© 2005, 2006 John Barnes Informatics.
Sponsored in part by:
The Ada Resource Association and its member companies: ARA Members AdaCore Polyspace Technologies Praxis Critical Systems IBM Rational Sofcheck and   Ada-Europe:
Ada-Europe