Question about class-wide components in records

I do not understand why the following is not allowed:

type IncludesClass is record
   Int: Integer;
   Some: Sometype'Class
end record;

The class-wide type is at the end, so there should be no problem with the layout. It is an indefinite type, and we can instantiate it without initialization.
Record types with an unconstrained array require a discriminant, which can’t be given here.
But what’s the issue with declaring that type indefinite/limited ? The component would get the tag and the object’s size calculated when instanciating.

The issue is the compiler needs to know the size of the IncludesClass type itself at compile time before any instantiations are made. All of the derived types of Sometype’Class could have different sizes. Which one does it pick for the type itself? You are correct that it could potentially figure out the size of the objects themselves at compile time (in some cases, but not all, what if initialized by a function call?), but for a definite type Ada needs to guarantee knowing the size of the type at compile time, not just the individual variables.

Keep in mind, that Ada has to be able to allow you to make an array of definite types, so it needs to be able to allow you to make the following type:
type IncludesClass_Array is array(Positive range <>) of IncludesClass, which it cannot do without knowing a global size for the type itself.

You can use an Indefinite_Holder for this (which has a known size at compile time): The Generic Package Containers.Indefinite_Holders

You can use the Reference and Constant_Reference operations to get access to the underlying class object at runtime.

Sometype’Class is an indefinite type. It is same as:

type IncludeString is record
   Int : Integer;
   S   : String;
end record;

Just like with String Ii could be possible if tag as the bounds would have been considered a constraint, so that you could:

type T is tagged ...
type S is new T with ...

X : T'Class (S'Tag); -- Constrain X to S, but this is not Ada!

Then you could just do like with arrays:

type Includes_Class (What : Tag) is record
   Int : Integer;
   X   : T'Class (What); -- This is not Ada!
end record;

Of course Tag itself need to be constrained to the tags of all types derived from T, which is not possible either.

Without that, just like with String you need to resort to pointers explicit or implicit, e.g. Unbounded_String, Handle etc…

Slight correction:

type Base is array (positive range <>) of Integer;
type ARR (A: Positive) is record
    B : Base (1..A);
end record;

This won’t compile if you try to create an instance of Arr:

     1. procedure Indefinites is
     2.    type Base is array (Positive range <>) of Integer;
     3.    type Arr (A : Positive) is record
     4.       B : Base (1 .. A);
     5.    end record;
     6.    A : Arr;
               |
        >>> error: unconstrained subtype not allowed (need initialization)
        >>> error: provide initial value or explicit discriminant values
        >>> error: or give default discriminant values for type "Arr"

     7. begin
     8.    null;
     9. end Indefinites;

You could say A : Arr (10);.

You could try

   type Arr (A : Positive := 10) is record
      B : Base (1 .. A);
   end record;
   A : Arr;

which compiles fine, but try to run it …

$ ./indefinites 

raised STORAGE_ERROR : stack overflow

What?
This is because when you give a discriminant like this a default, the language allows you to change it … so the compiler reserves sufficient space … in this case, room for Positive’Last integers. You can work round this by using an appropriate subrange of the discriminant type.

The thing about class-wide types is that you can’t know what instances of the type may be declared elsewhere, possibly in several years time.

I think that there’s a proposed extension where you can define the maximum size a class instance is allowed to have? Sounds like a Bad Idea for any code outside a tightly-controlled environment.

 6.    A : Arr;

Hence I mentioned a variant and not mutable type. My point was, why is this ok:

type Base is array (positive range <>) of Integer;
type ARR (A: Positive) is record
    B : Base (1..A);
end record;
A: Arr := Some_constructor;

but not this:

type Base is tagged private;
type ARR is record
    B: Base'Class;
end record;
A: Arr := (others => some_constructor);

Yes size can’t be predicted because new classes could be added, but it’s no different from other indefinite or unconstrained type: just require initialization, a default component value, or a tag in discriminant, just like with arrays.

BTW, the following is legal

type Crazy (L1, L2 : Natural) is record
   S1 : String (1..L1);
   S2 : String (1..L2);
end record;

Not only the size can be variable but offsets to the record members too.

Variant part is more limited. There can be only one at the end. But you can nest them! I do not remember I ever used it. It must be a big headache for compiler vendors…

P.S. Problems with proper composition is why full MI is so much welcome.

P.P.S. Proper here means that the objects remain contiguous and can be allocated on the stack.

Quit doing this — it makes it hard to follow the thread.

Yeah, off the cuff, that feature sounds like it would fry the ability to write libraries nicely.

No, your question was deleted, but I can answer it easily:
Because you cannot have a value of an unconstrained type in a record. The construct Base (1..A) is constraining the subtype, in this case to the length of A — likewise, Base'Class is unconstrained, but not null access Base'Class is constrained, as is an instance of Indefinite_Holders.Holder.

1 Like

Sorry I just copied the post because I hadn’t finished writing it, before someone posted something else I was responding to… a mess. I won’t do it again.

The main difference is in how the record itself is declared:
type ARR (A: Positive) is record
vs
type ARR is record

The first says that size of objects of this record might be determinable at compile time or they might not be (so it’s fields can be indefinite as long as they reference the discriminant), but the second says that size of objects of that records are always determinable at compile time so it’s fields must always be definite.

Also again, you cannot make an array of the first type (unless you make a constrained subtype/derived type first) while the language expects that you can make an array of the second type.

I definitely would like a way to do the second more easily as well, so don’t take my comments as I disagree with what you are looking for.

The first and second are the same; what was the second form supposed to be?

sorry fixed it. Just a copy / paste error. You can see the full declarations in their post above. I was just snipping the tops.

With this proposed change to the language: ada-spark-rfcs/considered/rfc-class-size.md at eb716e2d7764455f67770cb63338e50b97719df8 · AdaCore/ada-spark-rfcs · GitHub

You will be able to have class wide fields in a record because the size of all types in the hierarchy will be bounded.

It’ll be fun creating the error messages that will help us users to understand exactly what we’ve done wrong.

That must be better than just leaving things up to the implementers, ofc

1 Like

On different targets! The stuff is highly non-portable.

Absolutely. Yet another ugly hack.

IMO, the only right way to do it is making the type tag a constraint.

Are you talking about the RFC? I don’t get what you mean.

Sorry.

The RFC (showing evidence of deep thought) lays out many ways in which things could go wrong while implementing what seemed at first glance to be a straightforward requirement. E.g.

If the Size'Class aspect is specified for a type T, then every specific descendant of T [redundant: (including T)]

  • shall have a Size that does not exceed the specified value; and
  • shall be undiscriminated; and
  • shall have no composite subcomponent whose subtype is subject to a dynamic constraint; and
  • shall have no interface progenitors; and
  • shall not have a tagged partial view other than a private extension; and
  • shall not have a statically deeper accessibility level than that of T.

So someone at Adacore will need to implement those checks, and think of a way of pointing the user towards the error they’ve actually made if a check fails.

Also, we have at least one new concept (“tag-constrained”; is “mutably tagged” defined?) which I can guess at but don’t find clear.