Can I instantiate a generic within the same unit in Ada?

158 Views Asked by At

It doesn't seem like this is possible, but I haven't seen a definitive answer. What I want to do is define a generic subprogram with some generic formal parameters and instantiate it in the same package, like the following simplified and untested example:

generic
    Proc_Address : access System.Address;
    type Param_1_Type (<>) is private;
procedure Procedure_IP(Param_1 : Param_1_Type);

Instance_1_Address : System.Address := ...
procedure Instance_1 is new Procedure_IP(Instance_1_Address, type_1);
Instance_2_Address : System.Address := ...
procedure Instance_2 is new Procedure_IP(Instance_2_Address, type_2);
--etc

But this kind of thing keeps resulting in those "access before elaboration" errors. I can't seem to find any pragmas that will affect elaboration of a single subprogram; seems it has to be the whole package. Attempts to move the generic subprogram into a separate package have proven more troublesome than I had hoped because the specific stuff I want the function to do is closely related to other things happening in the same package.

Is there any way to resolve the elaboration order issue, so that I can keep the declaration and instantiations in the same package?

Option B is to rework this in a different way, like maybe passing a function pointer to a non-generic function or something, but doing it as a generic seems to be the cleanest way to go about it, especially since I'll need to refer to each of the intended instances a lot.

4

There are 4 best solutions below

0
Jere On BEST ANSWER

So the package or subprogram body must be complete before you can instantiate a generic. That said, you can use a package hierarchy to address the issue of needing to share data between the main package and the procedure if in a different file. Ada provides "private" child packages that can be shared among its siblings. You essentially do a hierarchy like this:

                                     Top_Level
                                          |
          ----------------------------------------------------------------
          |                               |                              |
Top_Level.Main_Package        Top_Level.Generic_Procedure     Top_Level.Private_Package

Then both Main_Package and Generic_Procedure have access to things in Private_Package and you can instantiate Generic_Procedure in Main_Package. Example Code to follow is below. Notice how both Main_Package and Generic_Procedure can access Thing in Private_Package and since the package is private, only they can use it.

top_level.ads

package Top_Level with Pure is end Top_Level;

top_level-main_package.ads

with Top_Level.Generic_Procedure;
with System;
private with Top_Level.Private_Package;

package Top_Level.Main_Package is

    subtype type_1 is Integer;
    subtype type_2 is String;
    
    Instance_1_Address : System.Address := System.Null_Address;
    procedure Instance_1 is new Generic_Procedure(Instance_1_Address, type_1);
    Instance_2_Address : System.Address := System.Null_Address;
    procedure Instance_2 is new Generic_Procedure(Instance_1_Address, type_2);

    function Get_Thing return Integer;

private

    function Get_Thing return Integer is (Private_Package.Thing);

end Top_Level.Main_Package;

top_level-generic_procedure.ads

with System;

generic
    Proc_Address : System.Address;
    type Param_1_Type (<>) is private;
procedure Top_Level.Generic_Procedure(Param_1 : Param_1_Type);

top_level-generic_procedure.adb

with Top_Level.Private_Package;
with Ada.Text_IO;

procedure Top_Level.Generic_Procedure
    (Param_1 : Param_1_Type) 
is
begin
    Ada.Text_IO.Put_Line(Private_Package.Thing'Image);
end Top_Level.Generic_Procedure;

top_level-private_package.ads

private package Top_Level.Private_Package is

    Thing : constant Integer := 3;
    
end Top_Level.Private_Package;

Full compiled example on godbolt: https://godbolt.org/z/7qaaPc4Ex

0
Zerte On

Did you consider something like this? (It compiles and spares you an access)

with System;

procedure Test is

  generic
      type Param_1_Type (<>) is private;
      Default_Address : System.Address;
  package Package_IP is
      Proc_Address : System.Address := Default_Address;
      procedure Procedure_IP (Param_1 : Param_1_Type);
  end;
  package body Package_IP is
      procedure Procedure_IP (Param_1 : Param_1_Type) is null;
  end;

  package Instance_1 is new Package_IP (Integer, System.Null_Address);

  package Instance_2 is new Package_IP (Float, System.Null_Address);

begin
  null;
end;
1
Mark L On

I think there is something wrong in your code:

generic
    Proc_Address : access System.Address;
    type Param_1_Type (<>) is private;
procedure Procedure_IP(Param_1 : Param_1_Type);

Why is the type of generic parameter Proc_Address an access to System.Address?

Try with:

generic
    Proc_Address : System.Address;
    type Param_1_Type (<>) is private;
procedure Procedure_IP(Param_1 : Param_1_Type);
1
Shark8 On

But this kind of thing keeps resulting in those "access before elaboration" errors.

Ok, so [IIUC] the problem is that the generic-instantiation is being attempted before the generic's body has been seen. Assuming you're in a declare-block or equivalent, the following should work.

generic
    Proc_Address : access System.Address;
    type Param_1_Type (<>) is private;
procedure Procedure_IP(Param_1 : Param_1_Type);

procedure Procedure_IP(Param_1 : Param_1_Type) is
Begin
   Null; -- Do Stuff here.
End Procedure_IP;

Instance_1_Address : System.Address := ...
procedure Instance_1 is new Procedure_IP(Instance_1_Address, type_1);
Instance_2_Address : System.Address := ...
procedure Instance_2 is new Procedure_IP(Instance_1_Address, type_2);
--etc