Arrays
Array constraints
Array constraints are important in the declaration of an array because they define the total size of the array. In fact, arrays must always be constrained. In this section, we start our discussion with unconstrained array types, and then continue with constrained arrays and arrays types. Finally, we discuss the differences between unconstrained arrays and vectors.
In the Ada Reference Manual
Unconstrained array types
In the Introduction to Ada course, we've seen that we can declare array types whose bounds are not fixed: in that case, the bounds are provided when creating objects of those types. For example:
package Measurement_Defs is
type Measurements is
array (Positive range <>) of Float;
-- ^ Bounds are of type Positive,
-- but not known at this point.
end Measurement_Defs;
with Ada.Text_IO; use Ada.Text_IO;
with Measurement_Defs; use Measurement_Defs;
procedure Show_Measurements is
M : Measurements (1 .. 10);
-- ^ Providing bounds here!
begin
Put_Line ("First index: " & M'First'Image);
Put_Line ("Last index: " & M'Last'Image);
end Show_Measurements;
In this example, the Measurements array type from the
Measurement_Defs package is unconstrained. In the
Show_Measurements procedure, we declare a constrained object (M)
of this type.
Constrained arrays
The Introduction to Ada course highlights the fact that the bounds are fixed once an object is declared:
Although different instances of the same unconstrained array type can have different bounds, a specific instance has the same bounds throughout its lifetime. This allows Ada to implement unconstrained arrays efficiently; instances can be stored on the stack and do not require heap allocation as in languages like Java.
In the Show_Measurements procedure above, once we declare M, its
bounds are fixed for the whole lifetime of M. We cannot add another
component to this array. In other words, M will have 10 components for
its whole lifetime:
M : Measurements (1 .. 10);
-- ^^^^^^^
-- Bounds cannot be changed!
Constrained array types
Note that we could declare constrained array types. Let's rework the previous example:
package Measurement_Defs is
type Measurements is
array (1 .. 10) of Float;
-- ^ Bounds are of known and fixed.
end Measurement_Defs;
with Ada.Text_IO; use Ada.Text_IO;
with Measurement_Defs; use Measurement_Defs;
procedure Show_Measurements is
M : Measurements;
-- ^ We cannot change the
-- bounds here!
begin
Put_Line ("First index: " & M'First'Image);
Put_Line ("Last index: " & M'Last'Image);
end Show_Measurements;
In this case, the bounds of the Measurements type are fixed. Now, we
cannot specify the bounds (or change them) in the declaration of the M
array, as they have already been defined in the type declaration.
Unconstrained Arrays vs. Vectors
If you need, however, the flexibility of increasing the length of an array, you
could use the language-defined Vector type instead. This is how we could
rewrite the previous example using vectors:
with Ada.Containers; use Ada.Containers;
with Ada.Containers.Vectors;
package Measurement_Defs is
package Vectors is new Ada.Containers.Vectors
(Index_Type => Positive,
Element_Type => Float);
subtype Measurements is Vectors.Vector;
end Measurement_Defs;
with Ada.Text_IO; use Ada.Text_IO;
with Measurement_Defs; use Measurement_Defs;
procedure Show_Measurements is
use Measurement_Defs.Vectors;
M : Measurements := To_Vector (10);
-- ^ Creating 10-element
-- vector.
begin
Put_Line ("First index: "
& M.First_Index'Image);
Put_Line ("Last index: "
& M.Last_Index'Image);
Put_Line ("Adding element...");
M.Append (1.0);
Put_Line ("First index: "
& M.First_Index'Image);
Put_Line ("Last index: "
& M.Last_Index'Image);
end Show_Measurements;
In the declaration of M in this example, we're creating a 10-element
vector by calling To_Vector and specifying the element count. Later on,
with the call to Append, we're increasing the length of the M to
11 elements.
As you might expect, the flexibility of vectors comes with a price: every time we add an element that doesn't fit in the current capacity of the vector, the container has to reallocate memory in the background due to that new element. Therefore, arrays are more efficient, as the memory allocation only happens once for each object.
In the Ada Reference Manual
Multidimensional Arrays
So far, we've discussed unidimensional arrays, since they are very common in
Ada. However, Ada also supports multidimensional arrays using the same
facilities as for unidimensional arrays. For example, we can use the
First, Last, Range and Length attributes for each
dimension of a multidimensional array. This section presents more details on
this topic.
To create a multidimensional array, we simply separate the ranges of each
dimension with a comma. The following example presents the one-dimensional
array A1, the two-dimensional array A2 and the three-dimensional
array A3:
package Multidimensional_Arrays_Decl is
A1 : array (1 .. 10) of Float;
A2 : array (1 .. 5, 1 .. 10) of Float;
-- ^ first dimension
-- ^ second dimension
A3 : array (1 .. 2, 1 .. 5, 1 .. 10) of Float;
-- ^ first dimension
-- ^ second dimension
-- ^ third dimension
end Multidimensional_Arrays_Decl;
The two-dimensional array A2 has 5 components in the first dimension and
10 components in the second dimension. The three-dimensional array A3
has 2 components in the first dimension, 5 components in the second dimension,
and 10 components in the third dimension. Note that the ranges we've selected
for A1, A2 and A3 are completely arbitrary. You may select
ranges for each dimension that are the most appropriate in the context of your
application. Also, the number of dimensions is not limited to three, so you
could declare higher-dimensional arrays if needed.
We can use the Length attribute to retrieve the length of each
dimension. We use an integer value in parentheses to specify which dimension
we're referring to. For example, if we write A'Length (2), we're
referring to the length of the second dimension of a multidimensional array
A. Note that A'Length is equivalent to A'Length (1). The
same equivalence applies to other array-related attributes such as
First, Last and Range.
Let's use the Length attribute for the arrays we declared in the
Multidimensional_Arrays_Decl package:
with Ada.Text_IO; use Ada.Text_IO;
with Multidimensional_Arrays_Decl;
use Multidimensional_Arrays_Decl;
procedure Show_Multidimensional_Arrays is
begin
Put_Line ("A1'Length: "
& A1'Length'Image);
Put_Line ("A1'Length (1): "
& A1'Length (1)'Image);
Put_Line ("A2'Length (1): "
& A2'Length (1)'Image);
Put_Line ("A2'Length (2): "
& A2'Length (2)'Image);
Put_Line ("A3'Length (1): "
& A3'Length (1)'Image);
Put_Line ("A3'Length (2): "
& A3'Length (2)'Image);
Put_Line ("A3'Length (3): "
& A3'Length (3)'Image);
end Show_Multidimensional_Arrays;
As this simple example shows, we can easily retrieve the length of each
dimension. Also, as we've just mentioned, A1'Length is equal to
A1'Length (1).
Let's consider an application where we make hourly measurements for the first
12 hours of the day, on each day of the week. We can create a two-dimensional
array type called Measurements to store this data. Also, we can have
three procedures for this array:
Show_Indices, which presents the indices (days and hours) of the two-dimensional array;Show_Values, which presents the values stored in the array; andReset, which resets each value of the array.
This is the complete code for this application:
package Measurement_Defs is
type Days is
(Mon, Tue, Wed, Thu, Fri, Sat, Sun);
type Hours is range 0 .. 11;
subtype Measurement is Float;
type Measurements is
array (Days, Hours) of Measurement;
procedure Show_Indices (M : Measurements);
procedure Show_Values (M : Measurements);
procedure Reset (M : out Measurements);
end Measurement_Defs;
with Ada.Text_IO; use Ada.Text_IO;
package body Measurement_Defs is
procedure Show_Indices (M : Measurements) is
begin
Put_Line ("---- Indices ----");
for D in M'Range (1) loop
Put (D'Image & " ");
for H in M'First (2) ..
M'Last (2) - 1
loop
Put (H'Image & " ");
end loop;
Put_Line (M'Last (2)'Image);
end loop;
end Show_Indices;
procedure Show_Values (M : Measurements) is
package H_IO is
new Ada.Text_IO.Integer_IO (Hours);
package M_IO is
new Ada.Text_IO.Float_IO (Measurement);
procedure Set_IO_Defaults is
begin
H_IO.Default_Width := 5;
M_IO.Default_Fore := 1;
M_IO.Default_Aft := 2;
M_IO.Default_Exp := 0;
end Set_IO_Defaults;
begin
Set_IO_Defaults;
Put_Line ("---- Values ----");
Put (" ");
for H in M'Range (2) loop
H_IO.Put (H);
end loop;
New_Line;
for D in M'Range (1) loop
Put (D'Image & " ");
for H in M'Range (2) loop
M_IO.Put (M (D, H));
Put (" ");
end loop;
New_Line;
end loop;
end Show_Values;
procedure Reset (M : out Measurements) is
begin
M := (others => (others => 0.0));
end Reset;
end Measurement_Defs;
with Measurement_Defs; use Measurement_Defs;
procedure Show_Measurements is
M : Measurements;
begin
Reset (M);
Show_Indices (M);
Show_Values (M);
end Show_Measurements;
We recommend that you spend some time analyzing this example. Also, we'd like to highlight the following aspects:
We access a value from a multidimensional array by using commas to separate the index values within the parentheses. For example:
M (D, H)allows us to access the value on dayDand hourHfrom the multidimensional arrayM.To loop over the multidimensional array
M, we writefor D in M'Range (1) loopandfor H in M'Range (2) loopfor the first and second dimensions, respectively.To reset all values of the multidimensional array, we use an aggregate with this form:
(others => (others => 0.0)).
In the Ada Reference Manual
Unconstrained Multidimensional Arrays
Previously, we've discussed unconstrained arrays for the unidimensional case. It's possible to declare unconstrained multidimensional arrays as well. For example:
package Multidimensional_Arrays_Decl is
type F1 is array (Positive range <>) of Float;
type F2 is array (Positive range <>,
Positive range <>) of Float;
type F3 is array (Positive range <>,
Positive range <>,
Positive range <>) of Float;
end Multidimensional_Arrays_Decl;
Here, we're declaring the one-dimensional type F1, the two-dimensional
type F2 and the three-dimensional type F3.
As is the case with unidimensional arrays, we must specify the bounds when declaring objects of unconstrained multidimensional array types:
with Ada.Text_IO; use Ada.Text_IO;
with Multidimensional_Arrays_Decl;
use Multidimensional_Arrays_Decl;
procedure Show_Multidimensional_Arrays is
A1 : F1 (1 .. 2);
A2 : F2 (1 .. 4, 10 .. 20);
A3 : F3 (2 .. 3, 1 .. 5, 1 .. 2);
begin
Put_Line ("A1'Length (1): "
& A1'Length (1)'Image);
Put_Line ("A2'Length (1): "
& A2'Length (1)'Image);
Put_Line ("A2'Length (2): "
& A2'Length (2)'Image);
Put_Line ("A3'Length (1): "
& A3'Length (1)'Image);
Put_Line ("A3'Length (2): "
& A3'Length (2)'Image);
Put_Line ("A3'Length (3): "
& A3'Length (3)'Image);
end Show_Multidimensional_Arrays;
Arrays of arrays
It's important to distinguish between multidimensional arrays and arrays of
arrays. Both are supported in Ada, but they're very distinct from each other.
We can create an array of an array by first specifying a one-dimensional array
type T1, and then specifying another one-dimensional array type
T2 where each component of T2 is of T1 type:
package Array_Of_Arrays_Decl is
type T1 is
array (Positive range <>) of Float;
type T2 is
array (Positive range <>) of T1 (1 .. 10);
-- ^^^^^^^
-- bounds must be set!
end Array_Of_Arrays_Decl;
Note that, in the declaration of T2, we must set the bounds for the
T1 type. This is a major difference to multidimensional arrays, which
allow for unconstrained ranges in multiple dimensions.
We can rewrite the previous application for measurements using arrays of arrays. This is the adapted code:
package Measurement_Defs is
type Days is
(Mon, Tue, Wed, Thu, Fri, Sat, Sun);
type Hours is range 0 .. 11;
subtype Measurement is Float;
type Hourly_Measurements is
array (Hours) of Measurement;
type Measurements is
array (Days) of Hourly_Measurements;
procedure Show_Indices (M : Measurements);
procedure Show_Values (M : Measurements);
procedure Reset (M : out Measurements);
end Measurement_Defs;
with Ada.Text_IO; use Ada.Text_IO;
package body Measurement_Defs is
procedure Show_Indices (M : Measurements) is
begin
Put_Line ("---- Indices ----");
for D in M'Range loop
Put (D'Image & " ");
for H in M (D)'First ..
M (D)'Last - 1
loop
Put (H'Image & " ");
end loop;
Put_Line (M (D)'Last'Image);
end loop;
end Show_Indices;
procedure Show_Values (M : Measurements) is
package H_IO is
new Ada.Text_IO.Integer_IO (Hours);
package M_IO is
new Ada.Text_IO.Float_IO (Measurement);
procedure Set_IO_Defaults is
begin
H_IO.Default_Width := 5;
M_IO.Default_Fore := 1;
M_IO.Default_Aft := 2;
M_IO.Default_Exp := 0;
end Set_IO_Defaults;
begin
Set_IO_Defaults;
Put_Line ("---- Values ----");
Put (" ");
for H in M (M'First)'Range loop
H_IO.Put (H);
end loop;
New_Line;
for D in M'Range loop
Put (D'Image & " ");
for H in M (D)'Range loop
M_IO.Put (M (D) (H));
Put (" ");
end loop;
New_Line;
end loop;
end Show_Values;
procedure Reset (M : out Measurements) is
begin
M := (others => (others => 0.0));
end Reset;
end Measurement_Defs;
with Measurement_Defs; use Measurement_Defs;
procedure Show_Measurements is
M : Measurements;
begin
Reset (M);
Show_Indices (M);
Show_Values (M);
end Show_Measurements;
Again, we recommend that you spend some time analyzing this example and comparing it to the previous version that uses multidimensional arrays. Also, we'd like to highlight the following aspects:
We access a value from an array of arrays by specifying the index of each array separately. For example:
M (D) (H)allows us to access the value on dayDand hourHfrom the array of arraysM.To loop over an array of arrays
M, we writefor D in M'Range loopfor the first level ofMandfor H in M (D)'Range loopfor the second level ofM.Resetting all values of an array of arrays is very similar to how we do it for multidimensional arrays. In fact, we can still use an aggregate with this form:
(others => (others => 0.0)).
Derived array types and array subtypes
Derived array types
As expected, we can derive from array types by declaring a new type. Let's see
a couple of examples based on the Measurement_Defs package from previous
sections:
package Measurement_Defs is
type Measurements is
array (Positive range <>) of Float;
--
-- New array type:
--
type Measurements_Derived is
new Measurements;
--
-- New array type with
-- default component value:
--
type Measurements_Def30 is
new Measurements
with Default_Component_Value => 30.0;
--
-- New array type with constraints:
--
type Measurements_10 is
new Measurements (1 .. 10);
end Measurement_Defs;
In this example, we're deriving Measurements_Derived from the
Measurements type. In the case of the Measurements_Def30 type,
we're not only deriving from the Measurements type, but also setting
the default component value to 30.0.
Finally, in the case of the Measurements_10, we're deriving from the
Measurements type and
constraining the array type in the
range from 1 to 10.
Let's use these types in a test application:
with Measurement_Defs; use Measurement_Defs;
procedure Show_Measurements is
M1, M2 : Measurements (1 .. 10)
:= (others => 0.0);
MD : Measurements_Derived (1 .. 10);
MD2 : Measurements_Derived (1 .. 40);
MD10 : Measurements_10;
begin
M1 := M2;
-- ^^^^^^
-- Assignment of arrays of
-- same type.
MD := Measurements_Derived (M1);
-- ^^^^^^^^^^^^^^^^^^^^^^^^^
-- Conversion to derived type for
-- the assignment.
MD10 := Measurements_10 (M1);
-- ^^^^^^^^^^^^^^^^^^^^
-- Conversion to derived type for
-- the assignment.
MD10 := Measurements_10 (MD);
MD10 := Measurements_10 (MD2 (1 .. 10));
end Show_Measurements;
As illustrated by this example, we can assign objects of different array types, provided that we perform the appropriate type conversions and make sure that the bounds match.
Array subtypes
Naturally, we can also declare subtypes of array types. For example:
package Measurement_Defs is
type Measurements is
array (Positive range <>) of Float;
--
-- Simple subtype declaration:
--
subtype Measurements_Sub is Measurements;
--
-- Subtype with constraints:
--
subtype Measurements_10 is
Measurements (1 .. 10);
--
-- Subtype with dynamic predicate
-- (array can only have 20 components
-- at most):
--
subtype Measurements_Max_20 is Measurements
with Dynamic_Predicate =>
Measurements_Max_20'Length <= 20;
--
-- Subtype with constraints and
-- dynamic predicate (first element
-- must be 2.0).
--
subtype Measurements_First_Two is
Measurements (1 .. 10)
with Dynamic_Predicate =>
Measurements_First_Two (1) = 2.0;
end Measurement_Defs;
Here, we're declaring subtypes of the Measurements type. For example,
Measurements_Sub is a simple subtype of Measurements type. In
the case of the Measurements_10 subtype, we're constraining the type to
a range from 1 to 10.
For the Measurements_Max_20 subtype, we're specifying — via a
dynamic predicate — that arrays of this subtype can only have 20
components at most. Finally, for the Measurements_First_Two subtype,
we're constraining the type to a range from 1 to 10 and requiring that the
first component must have a value of 2.0.
Note that we cannot set the default component value for array subtypes — only type declarations are allowed to use that facility.
Let's use these subtypes in a test application:
with Measurement_Defs; use Measurement_Defs;
procedure Show_Measurements is
M1, M2 : Measurements (1 .. 10)
:= (others => 0.0);
MS : Measurements_Sub (1 .. 10);
MD10 : Measurements_10;
M_Max20 : Measurements_Max_20 (1 .. 40);
M_F2 : Measurements_First_Two;
begin
MS := M1;
MD10 := M1;
M_Max20 := (others => 0.0); -- ERROR!
MD10 (1) := 4.0;
M_F2 := MD10; -- ERROR!
end Show_Measurements;
As expected, assignments to objects with different subtypes — but with
the same parent type — work fine without conversion. The assignment to
M_Max_20 fails because of the predicate failure: the predicate requires
that the length be 20 at most, and it's 40 in this case. Also, the
assignment to M_F2 fails because the predicate requires that the first
element must be set to 2.0, and MD10 (1) has the value 4.0.