Question about vector initialization

Yesterday, I was trying to do something with an array of vectors and stumbled a bit. So I’d like to ask some clarification questions about initialization…

With arrays (as with most other types), a variable is uninitialized until it’s explicitly set to a value.

My_List : array (1 .. 10) of Boolean;   -- uninitialized
My_List := [1 => True, other => False];

With vectors it’s slightly different. The reference manual says:

If an object of type Vector is not otherwise initialized, it is initialized to the same value as Empty_Vector.

So if I have:

package Boolean_Vectors is Ada.Containers.Vectors (
  Index_Type   => Positive,
  Element_Type => Boolean
);

subtype Boolean_Vector is Boolean_Vectors.Vector;

I can actually do:

My_Vector : Boolean_Vector; -- Implicitly initialized here?
My_Vector.Append (True);    -- Or here?
My_Vector.Append (False);

Although I’m not sure when exactly initialization happens (and why it was designed to happen implicitly).

Now, Empty_Vector is a constant, so I cannot use it if I want to initialize an empty vector explicitly.
So, what do I do when I want to have an array of vectors?

My_Vector_List : array (1 .. 10) of Boolean_Vector;
My_Vector_List := [others => ??]; -- How do I put an empty vector here
                                  -- that can grow later?

Or do I have to do it like this?

My_Vector_1, My_Vector_2 : Boolean_Vector;

My_Vector_List (1) := My_Vector_1;
My_Vector_List (2) := My_Vector_2;
...

Yes… I think. My uncertainty is that I’m not sure what you mean by “initialized”. It is initialized as a vector with length 0.

Given that you asked about arrays, might this be your confusion?

  • array (1 .. 10) of Boolean has capacity 10, and that can never change.

  • Boolean_Vector may start with capacity 0, 10, 100, … it’s up to whoever implements the library (assuming I read the ARM correctly), but either way, it can change, and more importantly, it will change when you try to append one more element than it can hold. GNAT at least initializes a vector to hold zero elements; in my copy, line 742 of a-convec.ads has

    Elements : Elements_Access := null;
    

    But! once you do add something, there’s an array beneath the hood:

    type Elements_Array is array (Index_Type range <>) of aliased Element_Type;
    

Does that answer the question? (and for the real Ada experts: that is correct, yes?)

You can! For example (just tested):

Winning_Path : Location_Vectors.Vector := Location_Vectors.Empty_Vector;

The key is to make sure you use Empty_Vector from the correct package.

What would make it impossible to use Empty_Vector would be if it were private to the package, but it’s not.

2 Likes

There are several cases in Ada where variables are implicitly or automatically initialized when declared, or to be precise, when an object of the type is created without an explicit initialization:

  1. Objects of an access type are initialized to null.
  2. A record declaration can give implicit initial (default) values to discriminants and other components.
  3. An object of a Controlled type is initialized by its Initialize operation.
  4. Any scalar type can be provided with a Default_Value aspect that defines the, well, default initial value for any object of that type.

Methods 1 and 2 existed already in the first version of Ada (Ada 83). Methods 3 and 4 were added in later updates to Ada.

All the container types like Vector are visibly declared as tagged types, and no doubt implemented with Controlled types (to ensure that they are finalized automatically). Thus they probably use method 3, or method 2, to initialize a Vector object to be empty.

When a variable of an array type is declared, all the elements of the array are created. If the array variable is not given an explicit initial value, and if some default initialization applies to the element type, that default initialization is applied to each element. So if you declare an array of Vectors, without an explicit initial value, all the Vectors in that array are automatically initialized in the same way as when you declare a single Vector variable, that is, to be empty.

In your My_Vector example, declaring the My_Vector variable as a Vector creates a Vector object, and as there is no explicit initial value given (with “:=”) that Vector object is by default/implicitly/automatically initialized to be empty, in the declaration itself (and not in the first Append operation).

3 Likes

Note that the keyword is others.

Initialization is part of the elaboration of an object declaration.

Empty_Vector is a constant, so I cannot use it if I want to initialize an empty vector explicitly

I’m not sure why you would think this. Consider

C : constant Integer := 23;
V : Integer := C;

This certainly works, as does

V := C;

so why would you think a Vector would be any different?

My_Vector_List := (others => Boolean_Vectors.Empty_Vector);

is similarly OK; the (presumably non-empty) vectors of My_Vector_List are replaced by copies of Empty_Vector.

As Holsti has pointed out, there are many ways to get default initialization, and well designed abstractions arrange to be default initialized to some useful value, as with Vector. Having used Ada 83 for over a decade before Ada 95 came out, I tend to favor the default initialization of record components, and they can be quite powerful; by initializing with a call to a carefully crafted function, they can pretty much do anything an Initialize procedure can do, though writing the latter may be more straightforward than writing the former.

2 Likes

With -gnatX you can initialise it there:

with Ada.Containers.Vectors;

procedure Vec is
   package Boolean_Vectors is new Ada.Containers.Vectors (
     Index_Type   => Positive,
     Element_Type => Boolean);

   subtype Boolean_Vector is Boolean_Vectors.Vector;

   My_Vector1 : Boolean_Vector;
   My_Vector2 : Boolean_Vector := [true, false];
begin
   null;
end Vec;

When compiled with gnatG (or -gnatD) gives:

procedure vec is
   subtype vec__T2194b is natural range 0 .. 5;
   C2193b : vec__T2194b := 0;
   M2192b : constant system__secondary_stack__mark_id :=
     $system__secondary_stack__ss_mark;
   procedure vec___finalizer;
   freeze vec___finalizer []
begin

   <snip>

   package boolean_vectors is new ada.ada__containers.
     ada__containers__vectors (index_type => positive, element_type =>
     boolean, vec__boolean_vectors__Oeq => vec__boolean_vectors__Oeq);
   subtype vec__boolean_vector is boolean_vectors.
     vec__boolean_vectors__vector;
   my_vector1 : vec__boolean_vector;
   vec__boolean_vectors__vectorIP (my_vector1, P536b => true);
   C2193b := 2;
   S920b : ada__containers__count_type := 2;
   type vec__A923b is access all vec__boolean_vectors__vector;
   freeze vec__A923b []
   type vec__A927b is access all vec__boolean_vectors__vector;
   freeze vec__A927b []
   T928b : vec__A927b := null;
   C2193b := 3;
   R922b : constant vec__A923b := vec__boolean_vectors__empty (2)
     'reference;
   T928b := vec__A927b!(R922b);
   C919b : vec__boolean_vector := R922b.all;
   C2193b := 4;
   C919b._tag := boolean_vectors__vectorP;
   vec__boolean_vectors__adjust__2 (C919b);
   B929b : declare
      A926b : constant boolean := $ada__exceptions__triggered_by_abort;
      R925b : boolean := false;
   begin
      system__soft_links__abort_defer.all;
      B930b : begin
         T928b := null;
         vec__boolean_vectors__finalize__2 (R922b.all);
      exception
         when others =>
            R925b := true;
      end B930b;
      system__soft_links__abort_undefer.all;
      if R925b and then not A926b then
         [program_error "finalize raised exception"]
      end if;
   end B929b;
   vec__boolean_vectors__append__3 (C919b, true);
   vec__boolean_vectors__append__3 (C919b, false);
   my_vector2 : vec__boolean_vector := C919b;
   C2193b := 5;


   my_vector2._tag := boolean_vectors__vectorP;
   vec__boolean_vectors__adjust__2 (my_vector2);
   freeze boolean_vectors__implementation []
   freeze boolean_vectors []
   null;
   return;
at end
   vec___finalizer;
end vec;

You can trace back the initialisation of my_Vector2 from C919b.

Thanks, everyone, for shedding light on this!

Makes perfect sense now. Actually, I’m not sure how I got stuck; I tried something like the following, which indeed works exactly like you say if I do it properly. :grimacing:

procedure Vector_Init is

   type Color is (Yellow, Orange, Red, Green, Blue, Violet);

   package Color_Vectors is new Ada.Containers.Vectors (
      Index_Type   => Positive,
      Element_Type => Color
   );

   Colors : array (1 .. 3) of Color_Vectors.Vector;

begin

   -- Colors := [others => Color_Vectors.Empty_Vector];
   -- Works the same with and without explicit initialization.

   Colors (1).Append (Yellow);
   Colors (1).Append (Blue);
   Colors (2).Append (Green);

   Ada.Text_IO.Put_Line (Colors'Image);
   -- [ [YELLOW, BLUE], [GREEN], [] ]

end Vector_Init;

I know now what tripped me up: If I assign one of the array elements to a variable and modify it, it does not mutate the original element.

Colors := [others => Color_Vectors.Empty_Vector];

Colors (1).Append (Yellow);
Colors (1).Append (Blue);
Colors (2).Append (Green);

Ada.Text_IO.Put_Line (Colors'Image);
-- [[YELLOW, BLUE], [GREEN], [], ...]

C := Colors (3);
C.Append (Violet); -- modifies C but does not mutate Colors (3)
   
Ada.Text_IO.Put_Line (C'Image);
-- [VIOLET]
Ada.Text_IO.Put_Line (Colors'Image);
-- [[YELLOW, BLUE], [GREEN], []]

So Colors (3) is copied when assigned to C?

Yes, types in Ada are not by reference.

You can use something like

declare
    C : constant Color_Vectors.Reference_Type := Colors.Reference (3);
begin
   C.Append (Violet);
end;
1 Like

You’re assigning the value of a variable to an array element. Now, if it was a vector of access-to-Color and you were assigning an access-to-variable, that would be a different matter!