user2853686
user2853686

Reputation: 43

Calling an overridden method from the base class in Ada

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

Answers (2)

ajb
ajb

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

Simon Wright
Simon Wright

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

Related Questions