"warning: coextension will not be deallocated when its associated owner is deallocated"

Can someone makes light of this ?

gcc -c -gnatX0 -gnatE main3.adb
main3.adb:11:45: warning: coextension will not be deallocated when its associated owner is deallocated [enabled by default]

procedure main3 is
    type Item (Degree : Natural := 0) is record
      Value: Float;
    end record;
    type ItemLink;
    type ItemAccess is  access all ItemLink;
    type ItemLink is record
      Next: ItemAccess;
      I: Item;
    end record;
    type Poly (Sentinel: access ItemLink := new ItemLink) is limited record
      MaxDegree: Natural := 0;
    end record;
begin null; end main3;

The AARM states again and again the lifetime connection between an object allocated in an anonymous access discriminant as part of initialization, and its enclosing object.
It mentions a few exceptions but none that would apply in that simplified exemple.

I don’t think GNAT fully implements coextension (at least it didn’t in the past). I don’t know if they are too hard to do or if there was another reason.

Ok. My two cents: they should implement the damn RM in full before coming up with a gazillion new features. Or better, how about waiting before adding something to the norm, until there exists a reference implementation…

As I said before, pragma Ada_2005 is your dear friend.
Nothing really relevant to software engineering was added to the language since. Moreover, most of the additions IMO damaged the language, especially when people starting to learn Ada keep on facing these before understanding the language core ideas:

  • Strong, nominal, manifested typing;
  • Modules for separate software development (= packages);
  • Strict separation of implementation and interface (= no executable code in declarations, no statements dressed as expressions, no functions returning limited objects);
  • Strictly imperative implementations (= no “functional” mess);
  • Clear semantics of implementations (= arrays instead of containers, no “functional” stuff, again, no inference beyond trivial etc);
  • Abstract data types and rich types algebra as the design vehicle (= if a programmer wanted functional/declarative/relational stuff and/or really high level abstractions, he, not the language and not the standard library, should be able to implement these in his project using core language feature of abstract data types).

As I said before, pragma Ada_2005 is your dear friend.

pragma Ada_2005 doesn’t do anything here, note that coextensions are part of Ada 2005.

Ok. My two cents: they should implement the damn RM in full before coming up with a gazillion new features. Or better, how about waiting before adding something to the norm, until there exists a reference implementation…

GNAT does prototype a bunch of things and has a bunch of extensions. The Ada spec is very extensive though and GNAT doesn’t seem to support everything or pass all the ACATS tests.

It was a general note. Coextensions are one of dead born features no programmer wanted and proposed to patch other language issues nobody wanted to fix. That is the problem with many recent additions being hacks. Coextension is hack of a hack. First you have a mix-in because MI is “bad” and some types are more tagged than others, then you fix an endless tail problems coming from that hack…

If features aren’t implemented anywhere, then put in an RFC with the ARG to remove them. The spec is very large already.

If you want to prohibit coextensions, you write:

pragma Restrictions(No_Coextensions);

I’m sure someone has a full list of restrictions they recommend.

First you have a mix-in because MI is “bad”

MI is bad because it leads to the diamond problem.

Ada’s Javaesque interfaces are bad because they lead to diamond problem, obviously abstract primitive operations are inherited in a diamond pattern just like members would.

Ada’s overloading is bad because they lead to diamond problem. You can easily bring similar objects into the same scope in a diamond pattern.

Ada’s renaming is bad because it leads to diamond problem. The different thing with renaming is that it is not idempotent. Renamed object under the same name coming from different contexts clashes with itself.

Generics are bad because they lead to diamond problem:

generic
package A is
end A;
generic
   with package BA is new A;
package B is
end B;
generic
   with package CA is new A;
package C is
end C;
generic
   with package DB is new B (<>);
   with package DC is new C (<>);
package D is
end D;

Packages are bad because of diamond problem:

with A;
package B is ..;

with A;
package C is ..;

with B, C;
package D is ...

and so on.

There is no language feature that does not have diamond problem. Or otherwise, there is no problem with diamonds at all. Diamonds are our friends… :sunglasses:

What?
No… that the dependency-graph would form a diamond is not “the diamond problem” — and this is precisely because the dependency is unambiguous — the diamond problem is the ambiguity when some object derives from classes/interfaces (which are themselves deriving from the same point, so as to ensure there is no way to disambiguate).

1 Like

It does not make any sense. There is no ambiguity. Compare same “problem” constructed with generics:

generic
package A is
end A;
generic
   with package BA is new A;
package B is
end B;
generic
   with package CA is new A;
package C is
end C;
generic
   with package DB is new B (<>);
   with package DC is new C (<>);
package D is
end D;

and

generic
package A is
end A;
generic
   with package BA is new A;
package B is
end B;
generic
   with package CA is new A;
package C is
end C;
generic
   with package DB is new B (<>);
   with package DC is new C (DB.BA);
package D is
end D;

It is important to understand that in all cases dependency can be

  • Idempotent
  • Additive

The first example above is additive the second is idempotent. So inheritance can be either. Ada’s choice is idempotent, as interfaces illustrate.
An additive choice has it place too, e.g.:

type Doubly_Linked is abstract tagged record
   Previous : access Doubly_Linked'Class;
   Next     : access Doubly_Linked'Class;
end record;

Now if we wanted some item to participate in two lists, then without any precious stones we would like it additive (not Ada):

type Mesh_Element is
   new Doubly_Linked as IO_Queue -- Some renaming need to happen
   and Doubly_Linked as Device_List with private;

In any case there is no “problem.” People keep on repeating nonsense invented by somebody decades ago.

Dmitry, what I wanted with coextension was a way for a discriminant to be any type, discrete or not, definite/constrained or not, and since that entails allocation, a way for the object to be finalized when the object goes out of scope. I assume you would use a controlled type ? If so, I would be fine with an aspect doing just that serving as syntactic sugar.
For MI, a problem I do get somehow, concretely how would you tell which bodies to use for a given inherited subprogram ? Let alone for ten of them at once ?

Discriminant is an immutable component which can be used to constrain other components. So it would be logical to have discriminant of any definite type. Yet an indefinite type is out of question.

Yes, that is canonical way. You have an access type component which is deallocated in Finalize and cloned in Adjust. Note that this has a bunch of problems starting with serialization. Suppose we wanted to marshal the object?

I prefer diet Cola. :grinning: Aspects are pure evil.

Where is a problem? In an idempotent model of inheritance it is just one body accessible through different paths.

with Text_IO; use Text_IO;
with Ada.Text_IO; use Ada.Text_IO;
procedure Hello is
begin
   Put_Line ("Hello World!");
end Hello;

Oh, my dear, how do you tell which Put_Line to use?.. :upside_down_face:

Oh, my dear, how do you tell which Put_Line to use?.. :upside_down_face:

You don’t need to, afaik the call is either resolved statically when profiles differ, or overloaded. But alternative bodies have subtype conformant profiles. You need a way for the dispatching table to know what to pick dynamically, have some priority between ancestors perhaps, but that seems awfully unmanageable to me. Either that or we’re back to static, variant programming !
Please be a dear and tell how you solve this:

Randy occasionally mentioned coextensions on c.l.a - but never as “this is a cool way of solving x.y.z problem”; and the only mention in the AARM05 I can find is in 3.10.2(14.4). As I recall this is the RM section that you aren’t supposed to be able to understand, so I’ve never tried. A bit like walking on quicksand.

2 Likes

What is here to solve? Sorry I do not understand what confuses you. Here is the chain of thought:

First observe that Ada;s OO model is such that when types are statically known everything is resolved. When you derive S from T you get Foo defined on T overloaded with S substituting T in the controlling arguments/result. This is fully static no conflicts, ambiguities etc. OK?

If S inherits Foo by multiple paths (MI) Foo is coalesced into a single Foo. This is just like in the case of Put_Line I presented. Again, no conflicts.

And finally, dispatching table changes nothing in the picture. You just put the body you would resolve statically in the slot corresponding to the type you had in that static case. (With multiple dispatch [Ada has in a restricted from of a diagonal] the type becomes a tuple of types).

See?

Ada’s OO model is pretty consistent and has the key feature that dispatch does not fail at run time. You always have the slot in the dispatching table filled with a body.

I hate feeling stupid. You want full MI without puny interfaces, right ? In the following, what is the output ? Heck, what is the formal parameter of the Foo I call, A or B ?

with Ada.text_io;
use ada.text_io;
procedure main2 is
   package Essai is
      type AA is tagged null record;
      procedure Foo (A: AA);
      type BB is tagged null record;
      procedure Foo (B: BB);
      type CC is new AA and BB with null record; --hypothetical
   end Essai;
   package body Essai is
      procedure Foo (A: AA) is
      begin
         put_line (" comes from A ! ");
      end Foo;
      procedure Foo (B: BB) is
      begin
         put_line (" comes from B ! ");
      end Foo;
   end Essai;
   E: CC;
begin
   E.Foo;
end main2;

This is not diamond and you already can do it in Ada:

   package PA is
      type AA is tagged null record;
      procedure Foo (A : AA);
   end PA;
   package body PA is
      procedure Foo (A: AA) is
      begin
         Put_Line ("A's Foo");
      end Foo;
   end PA;

   package PB is
      type BB is interface;
      procedure Foo (B: BB) is abstract;
   end PB;

   package body PB is
   end PB;

   package PC is
      use PA, PB;
      type CC is new AA and BB with null record; -- Real
   end PC;

This is legal Ada. Now if you could provide a not-null (see below) implementation of Foo for BB, then

      type CC is new AA and BB with null record;

would require you to override and E.Foo would call the override.

P.S. Ada needed no interfaces. Ada 95 already had abstract data types. Interface is nothing but an abstract tagged type with no implementations.

P.P.S. Implementations were added anyway as you can use “is null” on an interface. If you changed PB to

   package PB is
      type BB is interface;
      procedure Foo (B: BB) is null;
   end PB;

then

   type CC is new AA and BB with null record;

would still be legal, which is obviously inconsistent, but nobody cares.