Reputation: 43
I am wondering how to call an overridden method from the parent class in ADA. Let consider the following example. A Parent
class has some methods which are overridden by a Child
class. There is a method in the Parent
class (i.e., Prints
) which calls some of its overridden methods. But the overridden methods are not called! Here is the example:
--- parent ---
package Parents is
type Parent is tagged null record;
procedure Prints(Self: in out Parent);
-- these will be overridden
procedure Print1(Self: in out Parent) is null;
procedure Print2(Self: in out Parent) is null;
end Parents;
...
package body Parents is
procedure Prints(Self: in out Parent) is
begin
Put_Line("Parents.Prints: calling prints...");
Self.Print1;
Self.Print2;
end;
end Parents;
--- child ---
With Parents;
package Childs is
type Child is new Parents.Parent with null record;
overriding procedure Print1(Self: in out Child);
overriding procedure Print2(Self: in out Child);
end Childs;
...
package body Childs is
procedure Print1(Self: in out Child) is
begin
Put_Line("Child.Print1 is printing...");
end;
procedure Print2(Self: in out Child) is
begin
Put_Line("Child.Print2 is printing...");
end;
end Childs;
--- main ---
procedure Main is
anyprint : access Parents.Parent'Class;
begin
anyprint := new Childs.Child;
anyprint.Prints;
end Main;
problem
What I am expecting to see is the dispatched calls to both Print1
and Print2
from Child
. But the overridden methods are not called! Having C++ background, this type of polymorphic calls make sense for me but I cannot figure out how Ada treats them?
Is the invocation Self.Print1;
from Prints
wrong?
Upvotes: 4
Views: 1305
Reputation: 31699
One way to think about this is to think about it at a low level, i.e. what code is generated by the compiler.
When the Prints
procedure is compiled, and the statement Self.Print1
is seen, the code generated by the compiler is a non-dispatching call. That means that the compiler figures out the address (or external symbol) for the Print1
method, and generates a call to it. It is not an indirect call, but rather a call to a fixed address, which will be the Print1
that appears in Parents
. The reason it's non-dispatching is that the type of Self
isn't a classwide type (it's just Parent
, not Parent'Class
).
When you declare the Child
type, it will inherit the Prints
procedure. That is, there is an implicitly declared procedure that looks like this:
procedure Prints (Self : in out Child); -- inherited procedure
But the compiler does not generate new code for this implicit procedure, if you don't override it. So when Prints
is called, even if it's called with a Child
parameter, the code will be the same as if it were a Parent
parameter. And the code, as explained in the previous paragraph, makes a fixed call, not a dispatching (indirect) call, to Print1
and Print2
, which will still be the ones declared in Parent
, because the code was generated when Parent
was compiled.
Back to the call in Prints
:
Self.Print1;
If the type of Self were Parent'Class
, or if you used a view conversion to convert it to Parent'Class
, as in Simon's answer (Parent'Class(Self)
), then the call would be dispatching, meaning it's basically an indirect call. The code would figure out the address of the correct procedure at run time, and make an indirect call to it.
The difference between Ada and C++ is that the Ada compiler uses the type of the object it's operating on to determine whether to make a dispatching (indirect) or non-dispatching (call). If the type is classwide, it's dispatching, otherwise it's fixed. C++, however, uses a property of a method, not a property of a type, to decide which kind of call to make; if the method is marked virtual
, then calls to it are dispatching, and if not, they're fixed. (At least I think that's the case; I'm not really a C++ expert.)
By the way, the same applies even if you don't use Object.Operation notation. If, instead of Self.Print1
, you said
Print1 (Self);
that would be a non-dispatching call; but
Print1 (Parent'Class (Self));
is a dispatching call.
Upvotes: 3
Reputation: 25501
In Ada, dispatching only happens when the object is of a class-wide type. The relevant manual section is ARM 3.9.2.
In Parents.Prints
, the controlling operand Self
is of type Parent
, and is “statically tagged”, so there is no dispatching.
One approach is to use “redispatching”, which looks like this:
procedure Prints(Self: in out Parent) is
begin
Put_Line("Parents.Prints: calling prints...");
Parent'Class (Self).Print1;
Parent'Class (Self).Print2;
end;
in which the view conversion Parent'Class (Self)
means that the object on which .Print1
is invoked is "dynamically tagged" and the call dispatches.
As you have it, Prints
is able to be overridden in derived types. This isn’t always (or even usually?) what you want. If not, it’d be appropriate to change its parameter to be class-wide:
procedure Prints(Self: in out Parent'Class);
(and in the body, of course!) and then everything works as you’d expected.
[A side note: I’ve now learned that the object.operation notation works with class-wide objects!]
Upvotes: 5