Penultimate array index retrieval

Suppose the following generic procedure to print the elements of an array indexed by a discreet type (note the slight logic added to prevent the printing of an extra , past the end of the last element):

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;

procedure Array_Printer is
      type Index is (<>);
      type Int_Array is array (Index range <>) of Integer;
   procedure Print(IA: Int_Array); 
   procedure Print(IA: Int_Array) is
      -- Penultimate: Index := Index'Pred(IA'Last); -- raises CONSTRAINT_ERROR for IA'Length = 1
      for I in IA'First .. Index'Pred(IA'Last) loop
         Put(IA(I), 0);Put(",");
      end loop;
      Put(IA(IA'Last), 0);
   end Print;
   type Int_Array is array(Positive range <>) of Integer;
   IA: Int_Array := (-3, -2, -1, 0, 1, 2, 3);
   IA2: Int_Array := (1 => 0);
   procedure Print_Int_Array is new Print(Index => Positive,
                                          Int_Array => Int_Array);
end Array_Printer;

When this procedure runs with an array of length > 1 (e.g. IA) it correctly prints the array ([-3,-2,-1,0,1,2,3]). However, when it is given an array of length = 1 (e.g. IA2) then, perhaps surprisingly, the penultimate index calculation in the for-loop doesn't raise a CONSTRAINT_ERROR (due to a predecessor not existing) and the expected result ([0]) gets printed.

When that calculation is done elsewhere however (e.g. in the declarative section of the procedure) then that exception is raised, indeed.

Is the compiler smart enough to figure-out there's only one element in the array and hence generates a null range for the for loop?

Gnatstudio seems to be invoking gprbuild with -cargs -g -O0

Any thoughts? - Thanks


  • The calculation of 'Pred doesn't fail for your instantiation with Positive because Positive'Pred gives a reesult of Positive'Base, or Integer. It is when you attempt to store this result in an object of subtype Positive that you have a problem. However, with other actuals for Index this calculation may fail (try Integer rather than Positive).

    The problem is thinking that you need to be able to calculate this value, since clearly there are situations where you can't. Rather one needs to solve the problem with the information that is definitely available. Naturally the procedure should work properly for arrays of any length. That leads to a solution such as

    generic -- Print
       type Index is (<>);
       type Int_List is array (Index range <>) of Integer;
    procedure Print (List : in Int_List);
    procedure Print (List : in Int_List) is
    begin -- Print
       Put (Item => '[');
       Put_All : for I in List'range loop
          Put (Item => List (I), Width => 0);
          if I < List'Last then
             Put (Item => ", ");
          end if;
       end loop Put_All;
       Put (Item => ']');
    end Print;

    Note that Ada.Text_IO has a Put for Character as well as for String. The difference between them is completely irrelevant, but I've always preferred to use a Character literal to a length-1 String literal whenever possible. This is probably due to my first Ada compiler being the Rolm/Data General compiler in 1984, which took about 10 minutes to compile the null program, so anything that might save the compiler some effort seemed worthwhile.

    The main uses of arrays are as maps, sequences, and mathematical matrices and vectors. Your use seems to be a sequence. In common use, sequences are discussed using numerical values starting with one for positions: the first element, the second element, the 23rd element, the last element. It therefore makes sense for arrays used as sequences to be indexed by a numeric subtype with a lower value of 1, and for sequences to have a lower bound of 1. Adding such restrictions makes code using sequences easier to read and easier to get right:

    generic -- Print
       type Index is range <> with Dynamic_Predicate => Index'First = 1;
       type Int_List is array (Index range <>) of Integer;
    procedure Print (List : in Int_List) with Pre => List'First = 1;