Julio Guerra
Julio Guerra

Reputation: 5661

Loss of information with generics' parameters

The declaration of generic parameters is not exhaustive enough to give type relationships (subtypes) and this information is simply lost... For example :

-- generic_p.ads
generic
   type Index_Range_Type is range <>;
   type Count_Range_Type is range <>;
procedure Generic_P (I : Index_Range_Type, C : Count_Range_Type);

-- generic_p.adb
procedure Generic_P (I : Index_Range_Type, C : Count_Range_Type) is
begin
   if I = C then -- oops : cannot compare different types...
     -- ...
   end if;
end Generic_P;

-- main.adb
procedure Main is
   type Index_Range_Type is 0 .. 512;
   subtype Count_Range_Type is Index_Range_Type range 1 .. Index_Range_Type'Last;

   procedure P is new Generic_P (Index_Range_Type, Count_Range_Type);

   I : Index_Range_Type := 33;
   C : Count_Range_Type := 42;
begin
   if I = C then -- Ok : Count_Range is a subset of Index_Range, they can be compared
      -- ...
   end if;
   P (I, C);
end Main;

Which gives the following error for the comparison in generic_p.adb : invalid operand types [...] left operand has type "Index_Range_Type" [...] right operand has "type Count_Range_Type". The subtyping is not visible in the generic procedure.

Is there any way to specify the relationship between generic parameters ?

Further Informations

I really need Count_Range_Type as a parameter of the procedure to be able to add another parameter which needs Count_Range_Type.

-- generic_p.ads
generic
   type Index_Range_Type is range <>;
   type Count_Range_Type is range <>;
   with procedure F (C : Count_Range_Type);
procedure Generic_P (I : Index_Range_Type, C : Count_Range_Type);

I can't directly use the type, I need P to be absolutely generic and independent.

Upvotes: 3

Views: 124

Answers (4)

Marc C
Marc C

Reputation: 8522

This addresses the "Further Informations" portion of the original question, whereby a generic needs to be instantiated with a procedure containing a parameter that's a subtype of the instantiating type.

Essentially, a generic package is employed to set up the subtype, and then a generic child package provides the desired procedure (which is instantiated with the generic formal procedure). Admittedly, this is a rather involved solution to the problem. So here we go:

The "parent" generic that creates the subtype:

generic
   type Index_Range_Type is range <>;
package Generic_Provider is
   pragma Assert(Index_Range_Type'First = 0);
   subtype Count_Range_Type is Index_Range_Type range 1 .. Index_Range_Type'last;

end Generic_Provider;

All this does is declare the subtype, it needs no body (and in fact a body would be illegal).

Here's the spec of our procedure provider, which utilizes a client-supplied formal procedure.

generic
   with procedure F(I : Index_Range_Type;
                    C : Count_Range_Type);

package Generic_Provider.Services is

   procedure P (I : Index_Range_Type; C : Count_Range_Type);

end Generic_Provider.Services;

Just for grins, its body, which verifies that the formal procedure can be invoked and that the subtype comparison is valid:

package body Generic_Provider.Services is

   procedure P (I : Index_Range_Type; C : Count_Range_Type) is
   begin
      if I = C then
         F(I, C);
      end if;
   end P;

end Generic_Provider.Services;

Finally, the instantiating main program:

with Generic_Provider.Services;

procedure Main is
   type Index_Range_Type is range 0 .. 512;
   package Type_Provider is new Generic_Provider (Index_Range_Type);
   subtype Count_Range_Type is Type_Provider.Count_Range_Type;

   procedure My_F (I : Index_Range_Type;
                   C : Count_Range_Type) is
   begin
      null;
   end My_F;

   package P_Provider is new Type_Provider.Services(My_F);

   I : Index_Range_Type := 33;
   C : Count_Range_Type := 42;
begin
   if I = C then
      null;
   end if;
   P_Provider.P (I, C);
end Main;

Upvotes: 3

flyx
flyx

Reputation: 39768

You can use a full type instead of a subtype:

-- generic_p.ads
generic
   type Index_Range_Type is range <>;
   type Count_Range_Type is new Index_Range_Type;
procedure Generic_P (I : Index_Range_Type; C : Count_Range_Type);

-- generic_p.adb
procedure Generic_P (I : Index_Range_Type; C : Count_Range_Type) is
begin
   -- Neccessary conversion
   if I = Index_Range_Type (C) then
     -- ...
   end if;
end Generic_P;

-- main.adb
procedure Main is
   type Index_Range_Type is 0 .. 512;
   type Count_Range_Type is new Index_Range_Type range 1 .. Index_Range_Type'Last;

   procedure P is new Generic_P (Index_Range_Type, Count_Range_Type);

   I : Index_Range_Type := 33;
   C : Count_Range_Type := 42;
begin
   if I = Index_Range_Type (C) then
      -- ...
   end if;
   P (I, C);
end Main;

It is also possible to just cast C to Index_Range_Type without specifying the relation between the types in the generic section. That way, you can still use your subtype, but any instance of the generic procedure where Count_Range_Type is not castable to Index_Range_Type will raise an exception.

I suggest reading Ada 95 Quality and Style Guide, 8.2.4 for more information about using subtypes with generics.

Upvotes: 2

Shark8
Shark8

Reputation: 4198

You could also put a with function "=" (Left : Index_Range_Type; Right : Count_Range_Type) Return boolean into the generic's header... that forces the user to provide an appropriate equals function.

That route is needed because as the generic header now stands, the compiler may NOT make the assumption he two types are related; that is not given from the information is the signature.

Upvotes: 3

Marc C
Marc C

Reputation: 8522

Off the top of my head, perhaps inverting who declares what might meet your need. Consider having the generic define the subtype:

generic
   type Index_Range_Type is range <>;
package Generic_Provider is

   pragma Assert(Index_Range_Type'First = 0);
   subtype Count_Range_Type is Index_Range_Type range 1 .. Index_Range_Type'last;

   procedure P (I : Index_Range_Type; C : Count_Range_Type);

end Generic_Provider;

In the body:

package body Generic_Provider is

   procedure P (I : Index_Range_Type; C : Count_Range_Type) is
   begin
      if I = C then -- No problem comparing now...
            -- ...
         null;
      end if;
   end P;
end Generic_Provider;

Then in your instantiating unit:

with Generic_Provider;

procedure Main is
   type Index_Range_Type is range 0 .. 512;
   package P_Provider is new Generic_Provider (Index_Range_Type);

   subtype Count_Range_Type is P_provider.Count_Range_Type;


   I : Index_Range_Type := 33;
   C : Count_Range_Type := 42;
begin
   if I = C then -- Ok : Count_Range is a subset of Index_Range, and all is good.
         -- ...
      null;
   end if;
   P_Provider.P (I, C);
end Main;

This is akin to the approach System.Address_To_Access_Conversions takes, whereby the generic is instantiated with a "foundational" type, and the generic then provides the "enhancements"--in that package it's an access type, in this it would be the subtype.

Upvotes: 3

Related Questions