1

I'll start with the classic example of a generic procedure in Ada:

-------------------------
--  swaps.ads
-------------------------
package Swaps is
  generic
    type E is private;
  procedure Generic_Swap (Left, Right : in out E);
end Swaps;
-------------------------
--  swaps.adb
-------------------------
package body Swaps is
  procedure Generic_Swap (Left, Right : in out E) is
    Temporary : E;
  begin
    Temporary := Left;
    Left := Right;
    Right := Temporary;
  end Generic_Swap;
end Swaps;

Now suppose I want to implement a specialized String_Swap procedure for swapping strings, and provide it to all users of my package. I can add the following to the body declaration in swaps.adb:

procedure String_Swap is new Generic_Swap (String);

However, if I add nothing to the specification in swaps.ads, then no package can use this procedure. For example:

-------------------------
--  main.adb
-------------------------
with Swaps; use Swaps;
with Ada.Text_IO; use Ada.Text_IO;
procedure Main is
   First  : String := "world!";
   Second : String := "Hello, ";
begin
   String_Swap (First, Second); -- #Error: String_Swap is undefined#
   Put_Line (First);
   Put_Line (Second);
end Main;

I've tried to add the type of the procedure in to the specification:

procedure String_Swap (Left, Right : in out String);

but then Ada complains that this specification has a missing body and that the definition in swaps.adb conflicts with it.

Jacob Sparre Andersen
  • 6,733
  • 17
  • 22
John Gowers
  • 2,646
  • 2
  • 24
  • 37

4 Answers4

4

The way I would address this is to use a child package:

with Ada.Strings.Unbounded;

package Swaps.Instances is
   procedure Swap is new Generic_Swap (Element => Character);
   procedure Swap is new Generic_Swap (Element => Ada.Strings.Unbounded.Unbounded_String;
   ...
end Swaps.Instances;

Note that it's possible to write a generic that will handle indefinite types:

generic
   type Element (<>) is private;

and change the body of Generic_Swap to

procedure Generic_Swap (Left, Right : in out Element) is
   Temp : constant Element := Left;
begin -- Generic_Swap
   Left := Right;
   Right := Temp;
end Generic_Swap;

but to use it the actual objects must either be unconstrained or have the same subtype.

Jeffrey R. Carter
  • 3,033
  • 9
  • 10
3

The only thing that a user of Swaps can see is the spec. Since there is nothing about String_Swap in the spec, no amount of fiddling in the package body will make any difference.

If you wanted to "implement a specialized String_Swap procedure for swapping strings", you would have to include it in the spec:

package Swaps is
   generic
      type E is private;
   procedure Generic_Swap(Left, Right : in out E);
   procedure String_Swap is new Generic_Swap(String);
end Swaps;

This turns out to be a bad example: when compiled with -gnatl, we get

 1. package Swaps is
 2.    generic
 3.       type E is private;
 4.    procedure Generic_Swap(Left, Right : in out E);
 5.    procedure String_Swap is new Generic_Swap(String);
                                                 |
    >>> actual for "E" must be a definite subtype

 6. end Swaps;

This is because the type String is indefinite, that is, a particular String has a particular length, and can only be assigned to another String (or slice of a String) of the same length; so even if your procedure Main was written out without using the generic, it would fail with a constraint error at runtime. Check out Ada.Strings.Unbounded at ARM A.4.5.

So, try with a definite type:

package Swaps is
   generic
      type E is private;
   procedure Generic_Swap(Left, Right : in out E);
   procedure Character_Swap is new Generic_Swap(Character);
end Swaps;

Unfortunately,

 1. package Swaps is
 2.    generic
 3.       type E is private;
 4.    procedure Generic_Swap(Left, Right : in out E);
 5.    procedure Character_Swap is new Generic_Swap(Character);
       |
    >>> warning: cannot instantiate "Generic_Swap" before body seen
    >>> warning: Program_Error will be raised at run time

 6. end Swaps;

The solution has to be to instantiate separately: perhaps at library level,

with Swaps;
procedure Character_Swap is new Swaps.Generic_Swap(Character);

It’s going to be a lot easier to leave it up to your users to instantiate the generic as they wish.

Simon Wright
  • 25,108
  • 2
  • 35
  • 62
  • So is your conclusion that it's not possible to define `Generic_Swap(Character)` within the package and expose it to all users of the package? – John Gowers Feb 23 '18 at 22:59
  • 2
    You could create a package of instantiations, with the generic package as a child package. The generic package can then be hidden from users, or they can "with/use" it if they need to make additional instantiations. –  Feb 24 '18 at 12:42
3

You can't use your generic for type String because it is an unconstrained type. But lets use Ada.Strings.Unbounded.Unbounded_String instead.

What you have to do is:

  1. Publish a specification of a suitable procedure in the package specification.
  2. Implement the public procedure with a call to the internally instantiated generic procedure.
with Ada.Strings.Unbounded;

package Swaps is
   generic
      type Element_Type is private;
   procedure Generic_Swap (Left, Right : in out Element_Type);

   procedure Swap (Left, Right : in out Ada.Strings.Unbounded.Unbounded_String);
end Swaps;
package body Swaps is

   procedure Generic_Swap (Left, Right : in out Element_Type) is
      Temporary : Element_Type;
   begin
      Temporary := Left;
      Left      := Right;
      Right     := Temporary;
   end Generic_Swap;

   procedure Swap_Unbounded_Strings is
     new Generic_Swap (Element_Type => Ada.Strings.Unbounded.Unbounded_String);

   procedure Swap (Left, Right : in out Ada.Strings.Unbounded.Unbounded_String) is
   begin
      Swap_Unbounded_Strings (Left  => Left,
                              Right => Right);
   end Swap;
end Swaps;

But in general, I prefer to keep instantiations of generics completely separate from the specifications and implementations of those generics.

Jacob Sparre Andersen
  • 6,733
  • 17
  • 22
  • 1
    Note that, instead of a procedure that calls the instantiation, you can also use a renaming-as-body to implement Swap: `procedure Swap (Left, Right : in out Ada.Strings.Unbounded_String) renames Swap_Unbounded_Strings;` – Jeffrey R. Carter Feb 25 '18 at 08:51
  • 1
    Looks like I left out an `Unbounded`: should be `Ada.Strings.Unbounded.Unbounded_String;`. – Jeffrey R. Carter Feb 25 '18 at 09:12
1

I've tried to add the type of the procedure in to the specification:

procedure String_Swap (Left, Right : in out String);

but then Ada complains that this specification has a missing body and that the definition in swaps.adb conflicts with it.

This is good idea, but instantiation can't complete a procedure declaration (and compiler says that). What you need is use a procedure renaming:

   procedure String_Swap_Inst is new Generic_Swap (String);

   procedure String_Swap (Left, Right : in out String)
     renames String_Swap_Inst;

Also, to be able to use String with your generic, you need change it a little to allow unconstrained types:

package Swaps is
   generic
      type E (<>) is private;
   procedure Generic_Swap (Left, Right : in out E);

   procedure String_Swap (Left, Right : in out String);
end Swaps;

package body Swaps is
  procedure Generic_Swap (Left, Right : in out E) is
    Temporary : E := Left;
  begin
    Left := Right;
    Right := Temporary;
   end Generic_Swap;

   procedure String_Swap_Inst is new Generic_Swap (String);

   procedure String_Swap (Left, Right : in out String)
     renames String_Swap_Inst;
end Swaps;

And of course you can swap this way only string of equal length, otherwise you will get a Constraint_Error just like in an assignment:

X : String (1 .. 2) := "123";  --  Constraint_Error!!!
Maxim Reznik
  • 1,216
  • 9
  • 12