How to make an access to a single element of an array using indefinite types

Hello,

I’m sure there is a simple solution, but i cannot find it for now.
I am using some indefinite types for the sake of flexibility.
My goal is just to have an access (pointer) of one element of a matrix, but i get the error “object subtype must statically match designated subtype”

package lib is

   type unconstrained is array (Positive range <>) of aliased Natural;
   type unconstrainedptr is access all unconstrained;
   
   type arrayconstrained is array (1..5) of aliased unconstrained ( 1 .. 3);
   type arrayconstrainedptr is access all arrayconstrained;   
  
   content_array : aliased arrayconstrained := ( 1 .. 5 => (others => 0));    
end lib;

with lib; use lib;
procedure Main is   
   miarrayptr : lib.arrayconstrainedptr := content_array'Access; -- This is ok
   miptr : lib.unconstrainedptr := content_array(2)'access; -- This Fail !!
begin
   null;
end Main;

BTW, there are proper 2D arrays in Ada. That is a simple solution.
Complex solution. You constrained the unconstrained array when you declared arrayconstrained. You cannot take a pointer to an unconstrained array from a constrained array. The reason for that is that the compiler does not store the array bounds (AKA array dope) for constrained arrays. The bounds are statically known, 1…3 in your case, and the array contains only its elements. A pointer to an unconstrained array expects bounds which are nowhere to get. The compiler message about subtypes that must statically match hints you. A constrained array subtype does not match the unconstrained one. You can fix that by declaring a subtype:

   subtype unconstrained_13 is unconstrained ( 1..3);
   type unconstrained_13_ptr is access all unconstrained_13;

and using it like:

   type arrayconstrained is array (1..5) of aliased unconstrained_13;

Now it will work.

1 Like

I don’t have a compiler here to test atm, but you can try type converting your access since unconstraintedptr is a general access type.

Try:
miptr : lib.unconstrainedptr := lib.unconstrainedptr(content_array(2)'access);

I’d try it myself, but I don’t have a way at this location, so I don’t know if it is a legal conversion or not.

No, it is illegal for the same reason. See ARM 34/2:

…Explicit conversion is allowed between general access types with matching designated subtypes;…

Ada is a well-designed language. It is difficult to shot yourself in the foot. You should try really hard. :slightly_smiling_face:

thank you dmitry, going with 2D array it’s the best workaround i’m testing

Hello, @dmitry-kazakov (and all forum members). I continue studying for better understanding how to make complex variables structures using unconstrained types related to the discussion of this topic.

I’ve made this simplified example:


My goal is to have a specification package that describes this data structure.
I have an implementation that I think it could be upgraded because it has two mayor faults (IMHO):

  • I’m not 100% sure that all the data structuree will be declared in totally contiguos memory.
  • It needs a body with an initialization procedure and also dynamic memory with “new” operator;

Here is my implementation:

   --What we know at compiling time: 
   type fruitstypes is (apples, lemons);
   type conf_single is record
      number_of_fruits : Positive;
      number_of_seeds  : Positive;
   end record;   
   type conf_all is array (fruitstypes) of conf_single;
   conf : constant conf_all :=
     (apples => (number_of_fruits => 3, number_of_seeds => 4),
      lemons => (number_of_fruits => 2, number_of_seeds => 3));
   ---- End of what we know at compiling time -----------------
   type unconstrained_array_of_seeds is array (Positive range <>) of Natural;   
   type unconstrained_seeds(Tam :Positive) is record
      seeds : aliased unconstrained_array_of_seeds (1 .. Tam);
   end record;
   type unconstrained_seedsPtr is access all unconstrained_seeds;   
   type unconstrained_array_fruits is array (Positive range <>) of unconstrained_seedsPtr;
   type unconstrained_array_fuitsPtr is access all unconstrained_array_fruits;  
   type baskefruittype is array (fruitstypes) of unconstrained_array_fuitsPtr;
   basketfruit : aliased baskefruittype;
   procedure P_Init; 
----- body part ---
   procedure P_Init is
   begin
      for I in basketfruit'Range loop
         basketfruit(I) := new unconstrained_array_fruits (1 .. conf(I).number_of_fruits);
         for J in basketfruit(I)'Range loop
            basketfruit(I)(J) := new unconstrained_seeds (conf(I).number_of_seeds);
         end loop;         
      end loop;      
   end P_Init;

Thas is working, but I need to guaranty contiguos memory (for better cacheable performance) and try to avoid the body package with a initalization stage that uses dynamic memory (and it doesn’t contribute too much, just interpreting a constant configuration).

Anybody knows if it’s possible to allocate all the data structure in the specification package without using body package and assuring contiguos memory?

You would use vectors for this. As long as you use either Ada.Containers.Vectors or Ada.Containers.Bounded_Vectors, GNAT will do it using a contiguous block of memory. Indefinite vectors will not, but it looks like you only need the definite versions.

Note that I couldn’t find anywhere in the language spec that guarantees contiguous memory usage (it looks like the language spec leaves it up to the compiler vendor), but you can see GNAT’s implementation. If you need a more language level guarantee for contiguity, then you may have to roll your own. But since you are talking about performance level stuff, you are probably already outside the bounds of language defined portability for almost every language.

thank you for answer @jere I saw that Containers are implemented with generic packages.
In the example i use just two fruits, but it could be hundreds of elements.
It means i need to instantiate hundred of packages. It’s also worse becouse with every package is needed to add a subtype constraining the type.

Modification of adding fruits in my example is just adding or dropping enumerate elements. Using Vectors could be harder isn’it?


   subtype seeds_of_Fruit32 is unconstrained_seeds(5);
   package Fruit_Vectors32 is new ada.containers.Bounded_Vectors 
     (Index_Type => Natural,
      Element_Type => seeds_of_Fruit32);
---
---
---- 100 fruits later....
   subtype seeds_of_Fruit132 is unconstrained_seeds(7);
   package Fruit_Vectors132 is new ada.containers.Bounded_Vectors 
     (Index_Type => Natural,
      Element_Type => seeds_of_Fruit132);

This looks very untyped. Are apple and lemon seeds are of same type? Surely not, so they cannot be Unconstrained_Seeds. Same is with fruits, there cannot be Unconstrained_Array_Fruits.

If you use indefinite elements in arrays you need pointers. If you use pointers you need some pool. If you use a standard pool the representation is non-contiguous. So to ensure contiguity you need a special pool and a strategy to allocate elements. For example a stack pool on top of some array of storage elements would guarantee you contiguity but also you would lose some memory because the overall size is unknown in advance.

Initialization does not require body if standard array aggregate can be used.

Otherwise taking your picture literally:

   type Seed is new Integer;
   type Array_Of_Seeds is array (Positive range <>) of Seed;
   subtype Apple_Seeds is Array_Of_Seeds (1..4);
   subtype Lemon_Seeds is Array_Of_Seeds (1..3);
   type Array_Of_Apples is array (Positive range <>) of Apple_Seeds;
   type Array_Of_Lemons is array (Positive range <>) of Lemon_Seeds;
   type Basket (Apples_Count, Lemons_Count : Natural) is record
      Apples : Array_Of_Apples (1..Apples_Count);
      Lemons : Array_Of_Lemons (1..Lemons_Count);
   end record;
   X : Basket :=
       (  Apples_Count => 3,
          Lemons_Count => 2,
          Apples => ((1,2,3,4), (4,5,6,7), (8,9,10,11)),
          Lemons => ((12,13,14), (15,16,17))
       );

You cannot have array of fruits because in Ada arrays cannot have discriminants. So you can pass a constraint down to array elements.

You can use 2D array to make it as untyped as possible:

   type Seed_Count  is new Integer;
   type Fruit_Count is new Integer;
   type Fruit_Seeds is
      array (Fruit_Count range <>, Seed_Count range <>) of Seed;
   type Basket1 (Apples_Count, Lemons_Count : Fruit_Count) is record
      Apples : Fruit_Seeds (1..Apples_Count, 1..4);
      Lemons : Fruit_Seeds (1..Lemons_Count, 1..3);
   end record;
   Y : Basket1 :=
       (  Apples_Count => 3,
          Lemons_Count => 2,
          Apples => ((1,2,3,4), (4,5,6,7), (8,9,10,11)),
          Lemons => ((12,13,14), (15,16,17))
       );

I mean if you look at your code:

type fruitstypes is (apples, lemons);
type unconstrained_array_of_seeds is array (Positive range <>) of Natural;   
type unconstrained_seeds(Tam :Positive) is record
	seeds : aliased unconstrained_array_of_seeds (1 .. Tam);
end record;
type unconstrained_seedsPtr is access all unconstrained_seeds;   
type unconstrained_array_fruits is array (Positive range <>) of unconstrained_seedsPtr;
type unconstrained_array_fuitsPtr is access all unconstrained_array_fruits;  
type baskefruittype is array (fruitstypes) of unconstrained_array_fuitsPtr;
basketfruit : aliased baskefruittype;

With vectors it just becomes:

type fruitstypes is (apples, lemons);
	
	package Seed_Vectors is new Ada.Containers.Vectors(Positive, Natural);
	type unconstrained_seeds is new Seed_Vectors.Vector with null record;
	
	type Fruit(Seed_Count : Ada.Containers.Count_Type := 3) is record
		Seeds : Unconstrained_Seeds := To_Vector(Seed_Count);
	end record;
	
	package Fruit_Vectors is new Ada.Containers.Vectors(Positive, Fruit);
	type unconstrained_array_fruits is new Fruit_Vectors.Vector with null record;
	
	type baskefruittype is array (fruitstypes) of unconstrained_array_fruits;
	
	basketfruit : baskefruittype;

And for specific fruits you do:

    subtype Apple is Fruit(4);
    subtype Lemon is Fruit(3);

Entire compilable example:

With Ada.Text_IO; Use Ada.Text_IO;  
with Ada.Containers.Vectors;

procedure Test is

	--type fruitstypes is (apples, lemons);
	--type unconstrained_array_of_seeds is array (Positive range <>) of Natural;   
	--type unconstrained_seeds(Tam :Positive) is record
	--	seeds : aliased unconstrained_array_of_seeds (1 .. Tam);
	--end record;
	--type unconstrained_seedsPtr is access all unconstrained_seeds;   
	--type unconstrained_array_fruits is array (Positive range <>) of unconstrained_seedsPtr;
	--type unconstrained_array_fuitsPtr is access all unconstrained_array_fruits;  
	--type baskefruittype is array (fruitstypes) of unconstrained_array_fuitsPtr;
	--basketfruit : aliased baskefruittype;
	
	type fruitstypes is (apples, lemons);
	
	package Seed_Vectors is new Ada.Containers.Vectors(Positive, Natural);
	type unconstrained_seeds is new Seed_Vectors.Vector with null record;
	
	type Fruit(Seed_Count : Ada.Containers.Count_Type := 3) is record
		Seeds : Unconstrained_Seeds := To_Vector(Seed_Count);
	end record;
	
	subtype Apple is Fruit(4);
	subtype Lemon is Fruit(3);
	
	package Fruit_Vectors is new Ada.Containers.Vectors(Positive, Fruit);
	type unconstrained_array_fruits is new Fruit_Vectors.Vector with null record;
	
	type baskefruittype is array (fruitstypes) of unconstrained_array_fruits;
	
	basketfruit : baskefruittype;
	
	A : Apple;
	L : Lemon;
	
begin
	for Count in 1 .. 3 loop
		basketfruit(Apples).Append(A);
	end loop;
	
	for Count in 1 .. 2 loop
		basketfruit(Lemons).Append(L);
	end loop;
end Test;

thank you all @jere, @dmitry-kazakov , I will study and experiment with all information and help you give me.