Interface types and tagged types: abstract functions and ambiguous calls, oh my!

I thought I understood interface and tagged types pretty well, but apparently not!

learn.adacore.com has an example on interfaces that makes sense and compiles, etc. Once I get beyond that relatively trivial example, however, things fall apart.

I think this is as MWE as I can get:

    --  from the interface
    type A is abstract tagged null record;

    type B is new A with private;  --  private part has a String
    type C is new A with private;  --  private part has a vector of A's

    type I is interface;
    procedure AI (First : A'Class; Second : I) is abstract;

    type I2 is new I with null record;
    --  procedure AI (First : A'Class; Second : I2);
    procedure AI (First : B'Class; Second : I2);
    procedure AI (First : C'Class; Second : I2);

    -- from the implementation
    procedure AI (First : A'Class; Second : I2) is
    begin
        IO.Put_Line ("AI with A and I2");
    end AI;

    procedure AI (First : B'Class; Second : I2) is
    begin
        IO.Put_Line ("AI with B and I2");
    end AI;

    procedure AI (First : C'Class; Second : I2) is
    begin
        IO.Put_Line ("AI with C and I2");
    end AI;

A program with this fails to compile with this message:

my_interfaces.adb:6:10: error: ambiguous call to "Ai"
my_interfaces.adb:6:10: error: possible interpretation at my_interface.ads:16
my_interfaces.adb:6:10: error: possible interpretation (inherited) at my_interface.ads:13
my_interface.ads:13:10: error: type must be declared abstract or "Ai" overridden
my_interface.ads:13:10: error: "Ai" has been inherited from subprogram at line 11
  1. The error on line 13 goes away if I implement procedure AI (A'Class; I). I don’t understand why it wants that.
  2. The error on line 6 doesn’t go away no matter what I do, and I don’t understand what’s ambiguous. It’s referring to the abstract procedure (which it shouldn’t even attempt to use…?) and to the actual implementation.

Thanks for any help you can give!

Added later: I just closed something like 20 tabs that I had open last night, trying to make sense of this. So if it turns out to be something truly simple I’ll be :man_facepalming: myself for weeks.

1 Like

From your pasted example, you don’t have them in a package which prevents making operations primitive in some cases (being primitive is required for inheritance). The following example compiles on the jdoodle Ada compiler:

with Ada.Text_IO; use Ada.Text_IO;

procedure jdoodle is

    package IO renames Ada.Text_IO;

    package Inner is
        --  from the interface
        type A is abstract tagged null record;
    
        type B is new A with private;  --  private part has a String
        type C is new A with private;  --  private part has a vector of A's
    
        type I is interface;
        procedure AI (First : A'Class; Second : I) is abstract;
    
        type I2 is new I with null record;
        procedure AI (First : A'Class; Second : I2);
        procedure AI (First : B'Class; Second : I2);
        procedure AI (First : C'Class; Second : I2);
        
    private
    
        type B is new A with null record;
        
        type C is new A with null record;
        
    end Inner;
    
    package body Inner is

        -- from the implementation
        procedure AI (First : A'Class; Second : I2) is
        begin
            IO.Put_Line ("AI with A and I2");
        end AI;
    
        procedure AI (First : B'Class; Second : I2) is
        begin
            IO.Put_Line ("AI with B and I2");
        end AI;
    
        procedure AI (First : C'Class; Second : I2) is
        begin
            IO.Put_Line ("AI with C and I2");
        end AI;
        
    end Inner;
begin
    null;
end jdoodle;

Sure, I had something like that, and it compiles for me, too. Rereading the error and the code, I see that I didn’t supply enough information, or possibly wrong information. (Not-so-minimal example! :grin:) Let’s add a few things.

To the package interface, add

    function Get_T2 return I2;

    function Initialize return C;

To the package body, add

    function Initialize return C is
        Result : C;
    begin
        return Result;
    end Initialize;

    function Get_T2 return I2 is (T2);

To the main procedure body, add a variable Stuff: My_Interface.C := My_Interface.Initialize; and change null; to

Stuff.AI (My_Interface.Get_T2);

This is where I get the error. How about you?

(If you still don’t get it, I can post more of what I have, which is perhaps what I should have done to start with.)

I don’t have T2, is that meant to be I2 or something else?

If I guess that T2 is I2, then I have this example:

with Ada.Text_IO; use Ada.Text_IO;

procedure jdoodle is

    package IO renames Ada.Text_IO;

    package My_Interface is
        --  from the interface
        type A is abstract tagged null record;
    
        type B is new A with private;  --  private part has a String
        type C is new A with private;  --  private part has a vector of A's
    
        type I is interface;
        procedure AI (First : A'Class; Second : I) is abstract;
    
        type I2 is new I with null record;
        procedure AI (First : A'Class; Second : I2);
        procedure AI (First : B'Class; Second : I2);
        procedure AI (First : C'Class; Second : I2);
        
        function Get_T2 return I2;

        function Initialize return C;
        
    private
    
        type B is new A with null record;
        
        type C is new A with null record;
        
    end My_Interface;
    
    package body My_Interface is

        -- from the implementation
        procedure AI (First : A'Class; Second : I2) is
        begin
            IO.Put_Line ("AI with A and I2");
        end AI;
    
        procedure AI (First : B'Class; Second : I2) is
        begin
            IO.Put_Line ("AI with B and I2");
        end AI;
    
        procedure AI (First : C'Class; Second : I2) is
        begin
            IO.Put_Line ("AI with C and I2");
        end AI;
        
        function Initialize return C is
            Result : C;
        begin
            return Result;
        end Initialize;
    
        function Get_T2 return I2 is (I with null record);
        
    end My_Interface;
    
    Stuff: My_Interface.C := My_Interface.Initialize; 
    
begin
    Stuff.AI (My_Interface.Get_T2);
end jdoodle;

That gives the ambiguous error and I see why. You have two functions named AI which take both A’Class and C’Class. The problem is C’Class is always A’Class as well, so you will always be ambiguous (which of the 2 calls does the compiler pick?). There are two ways to fix this. Either name the functions different or put them in different packages and use Package_Name.AI(Stuff, My_Interface.Get_T2).

I tend to favor the different package approach since types A and C would normally be declared in separate packages (not always, but it’s most common in my experience).

Here is a reworked example using different packages that compiles (barring the T2 thing of course):

with Ada.Text_IO; use Ada.Text_IO;

procedure jdoodle is

    package IO renames Ada.Text_IO;

    package My_Interface is
        --  from the interface
        type A is abstract tagged null record;
    
        type B is new A with private;  --  private part has a String
        
        type I is interface;
        procedure AI (First : A'Class; Second : I) is abstract;
    
        type I2 is new I with null record;
        procedure AI (First : A'Class; Second : I2);
        procedure AI (First : B'Class; Second : I2);
        
        function Get_T2 return I2;

    private
    
        type B is new A with null record;
        
    end My_Interface;
    
    package My_C is
        use My_Interface;
        
        type C is new A with private;  --  private part has a vector of A's
        
        procedure AI (First : C'Class; Second : I2);
        function Initialize return C;
        
    private
        type C is new A with null record;
    end My_C;
    
    package body My_C is
        procedure AI (First : C'Class; Second : I2) is
        begin
            IO.Put_Line ("AI with C and I2");
        end AI;
        
        function Initialize return C is
            Result : C;
        begin
            return Result;
        end Initialize;
    end My_C;
    
    package body My_Interface is

        -- from the implementation
        procedure AI (First : A'Class; Second : I2) is
        begin
            IO.Put_Line ("AI with A and I2");
        end AI;
    
        procedure AI (First : B'Class; Second : I2) is
        begin
            IO.Put_Line ("AI with B and I2");
        end AI;
        
        function Get_T2 return I2 is (I with null record);
        
    end My_Interface;
    
    Stuff: My_C.C := My_C.Initialize; 
    
begin
    My_C.AI(Stuff, My_Interface.Get_T2);
end jdoodle;

Yes, T2 is an I2. Sorry, I thought I’d copied that.

I’m still not sure I understand the ambiguity. A is abstract. I understand that C'Class can also be A'Class, but given the choice between the two, I’d’a thunk the resolution would be… uh, “obvious”. :grin: But maybe that’s my impression from other OO languages.

I’ll give your approach a spin later and see what happens. Thanks for the patience.