comp.lang.ada
 help / color / mirror / Atom feed
* abstract sub programs overriding
@ 2004-03-02 19:01 Evangelista Sami
  2004-03-03  1:43 ` Stephen Leake
                   ` (2 more replies)
  0 siblings, 3 replies; 67+ messages in thread
From: Evangelista Sami @ 2004-03-02 19:01 UTC (permalink / raw)


hello all

i have a class hierarchy with 3 levels and an abstract procedure :

-----------------------------
type t1 is abstract tagged record ... end record;
procedure proc(my_t : in t1) is abstract;

type t2 is abstract new t1 with record ... end record;
procedure proc(my_t : in t2);

type t3 is new t2 with record ... end record;
type access_t3 is access all t3;
function new_t3 return access_t3;
-----------------------------

function new_t3 only create a t3 object and return it. Now i have this
procedure :

-----------------------------
procedure generate is
   access_t3 : t3 := new_t3;
begin
   proc(access_t3.all);
end;
-----------------------------

how is it that the "proc(access_t3.all);" call
raise "CONSTRAINT_ERROR : generator.adb:93 access check failed"

i thought it would be correct since i have overriden proc for type t2
and t3 inherits from t2. what's wrong?
what would be the solution?

thanks



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-02 19:01 abstract sub programs overriding Evangelista Sami
@ 2004-03-03  1:43 ` Stephen Leake
  2004-03-05 15:02   ` Evangelista Sami
  2004-03-03 12:00 ` Marius Amado Alves
  2004-03-13  7:51 ` Simon Wright
  2 siblings, 1 reply; 67+ messages in thread
From: Stephen Leake @ 2004-03-03  1:43 UTC (permalink / raw)
  To: comp.lang.ada

evangeli@cnam.fr (Evangelista Sami) writes:

> hello all
> 
> i have a class hierarchy with 3 levels and an abstract procedure :

Please post compilable code, then I'll look at it in detail.

> <snip>

> how is it that the "proc(access_t3.all);" call
> raise "CONSTRAINT_ERROR : generator.adb:93 access check failed"

what is the value of access_t3 at this point? Probably null. Use the
debugger.

-- 
-- Stephe




^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-02 19:01 abstract sub programs overriding Evangelista Sami
  2004-03-03  1:43 ` Stephen Leake
@ 2004-03-03 12:00 ` Marius Amado Alves
  2004-03-13  7:51 ` Simon Wright
  2 siblings, 0 replies; 67+ messages in thread
From: Marius Amado Alves @ 2004-03-03 12:00 UTC (permalink / raw)
  To: comp.lang.ada

> type t3 is new t2 with record ... end record;
> type access_t3 is access all t3;
> function new_t3 return access_t3;
> ...
> procedure generate is
>    access_t3 : t3 := new_t3;
> begin
>    proc(access_t3.all);
> end;

You are dereferencing a non pointer. You are confusing names. Try:

procedure Generate is
  Ptr : Access_T3 := New_T3;
begin
  Proc (Ptr.all);
end;



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-03  1:43 ` Stephen Leake
@ 2004-03-05 15:02   ` Evangelista Sami
  2004-03-05 16:15     ` Marius Amado Alves
                       ` (2 more replies)
  0 siblings, 3 replies; 67+ messages in thread
From: Evangelista Sami @ 2004-03-05 15:02 UTC (permalink / raw)


here is the code :

----------------------------------------------------
package Generator is

   type Element_Record is abstract tagged private;
   type Element is access all Element_Record'Class;

   procedure Generate
     (El : in Element_Record) is abstract;

private

   type Element_Record is abstract tagged record
      null;
   end record;

end Generator;
----------------------------------------------------
package Generator.Declarations is

   type Decl_Record is abstract new Element_Record with private;

   type Type_Decl_Record is abstract new Decl_Record with private;

   type Discrete_Type_Decl_Record is abstract
     new Type_Decl_Record with private;

   type Enumerate_Type_Decl_Record is
     new Discrete_Type_Decl_Record with private;
   type Enumerate_Type_Decl is access all Enumerate_Type_Decl_Record;

   function New_Enumerate_Type_Decl return Element;

private

   type Decl_Record is abstract new Element_Record with record
      null;
   end record;

   type Type_Decl_Record is abstract new Decl_Record with record
      null;
   end record;

   procedure Generate
     (El : in Type_Decl_Record);

   type Discrete_Type_Decl_Record is abstract new Type_Decl_Record with record
      null;
   end record;

   type Enumerate_Type_Decl_Record is new Discrete_Type_Decl_Record with record
      null;
   end record;

end Generator.Declarations;
----------------------------------------------------
package body Generator.Declarations is

   procedure Generate
     (El : in Type_Decl_Record) is
   begin
      null;
   end;

   function New_Enumerate_Type_Decl return Element is
      Result : Enumerate_Type_Decl := new Enumerate_Type_Decl_Record;
   begin
      return Element(Result);
   end;

end Generator.Declarations;
----------------------------------------------------
with Generator; use Generator;
with Generator.Declarations; use Generator.Declarations;

procedure Main is
   El : Element := New_Enumerate_Type_Decl;
begin
   Generate(El.all);
end;
----------------------------------------------------


in fact i have 5 levels :
element -> decl -> type_decl -> discrete_type_decl -> enumerate_type_decl

it crashes on "Generate(El.all);" :
raised CONSTRAINT_ERROR : main.adb:7 access check failed



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-05 15:02   ` Evangelista Sami
@ 2004-03-05 16:15     ` Marius Amado Alves
  2004-03-08 18:54       ` Adam Beneschan
  2004-03-05 16:26     ` Marius Amado Alves
  2004-03-06 23:20     ` Dan Eilers
  2 siblings, 1 reply; 67+ messages in thread
From: Marius Amado Alves @ 2004-03-05 16:15 UTC (permalink / raw)
  To: comp.lang.ada

On Friday 05 March 2004 15:02, Evangelista Sami wrote:
> ...
> it crashes on "Generate(El.all);" :
> raised CONSTRAINT_ERROR : main.adb:7 access check failed

I'm surprised it compiles at all given that Generate is a private operation 
(and therefore not available outside its declarative region).



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-05 15:02   ` Evangelista Sami
  2004-03-05 16:15     ` Marius Amado Alves
@ 2004-03-05 16:26     ` Marius Amado Alves
  2004-03-06  9:31       ` Simon Wright
  2004-03-06 23:20     ` Dan Eilers
  2 siblings, 1 reply; 67+ messages in thread
From: Marius Amado Alves @ 2004-03-05 16:26 UTC (permalink / raw)
  To: comp.lang.ada

(slight correction)

I mean the *actual* Generate that you are trying to call is not available. I 
guess the compiler/system tries to execute the abstract Generate which is 
the only one available and naturally fails. Try moving the actual Generate 
declaration to a public part.

(actual = having a body)



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-05 16:26     ` Marius Amado Alves
@ 2004-03-06  9:31       ` Simon Wright
  2004-03-06 15:18         ` Evangelista Sami
  0 siblings, 1 reply; 67+ messages in thread
From: Simon Wright @ 2004-03-06  9:31 UTC (permalink / raw)


Marius Amado Alves <amado.alves@netcabo.pt> writes:

> I mean the *actual* Generate that you are trying to call is not
> available. I guess the compiler/system tries to execute the abstract
> Generate which is the only one available and naturally fails. Try
> moving the actual Generate declaration to a public part.

Assuming GNAT --

The program certainly runs without exceptions if you do this.

However I'm not sure that it isn't a GNAT bug; after all, any concrete
Element_Record must have a concrete Generate.

If you run the original program (with some data components in the
records, so you can track what's what) the value returned looks just
fine.

-- 
Simon Wright                               100% Ada, no bugs.
                                                     ^^^^^^^ !



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-06  9:31       ` Simon Wright
@ 2004-03-06 15:18         ` Evangelista Sami
  2004-03-06 19:09           ` Marius Amado Alves
  0 siblings, 1 reply; 67+ messages in thread
From: Evangelista Sami @ 2004-03-06 15:18 UTC (permalink / raw)


Simon Wright <simon@pushface.org> wrote in message news:<x7v7jxyuxs5.fsf@smaug.pushface.org>...
> Marius Amado Alves <amado.alves@netcabo.pt> writes:
> 
> > I mean the *actual* Generate that you are trying to call is not
> > available. I guess the compiler/system tries to execute the abstract
> > Generate which is the only one available and naturally fails. Try
> > moving the actual Generate declaration to a public part.
> 
> Assuming GNAT --

it is gnat 3.15p

> The program certainly runs without exceptions if you do this.
> 
> However I'm not sure that it isn't a GNAT bug; after all, any concrete
> Element_Record must have a concrete Generate.

if i have overriden generate at a higher lever even if it is for an
asbtract type, this should not raise any problem. am i wrong?
 
> If you run the original program (with some data components in the
> records, so you can track what's what) the value returned looks just
> fine.



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-06 15:18         ` Evangelista Sami
@ 2004-03-06 19:09           ` Marius Amado Alves
  2004-03-07 12:35             ` Simon Wright
  0 siblings, 1 reply; 67+ messages in thread
From: Marius Amado Alves @ 2004-03-06 19:09 UTC (permalink / raw)
  To: comp.lang.ada

> if i have overriden generate at a higher lever even if it is for an
> asbtract type, this should not raise any problem. am i wrong?

Sorry, I don't understand the question.

It's very simple, really: you can only call concrete, and of course 
available, operations.

In your program you had an abstract (not concrete) Generate (for a 'root' 
type), and a concrete, but not available Generate (for a derived type). The 
concrete Generate was not available (at the point of the call) because it was 
declared in the private part of a separate unit. So the first was available 
but was not concrete, the second was not available, GNAT went for the only 
available one, and naturally failed, mumbling whatever GNAT mumbles when it 
tries to call an abstract operation.



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-05 15:02   ` Evangelista Sami
  2004-03-05 16:15     ` Marius Amado Alves
  2004-03-05 16:26     ` Marius Amado Alves
@ 2004-03-06 23:20     ` Dan Eilers
  2 siblings, 0 replies; 67+ messages in thread
From: Dan Eilers @ 2004-03-06 23:20 UTC (permalink / raw)


evangeli@cnam.fr (Evangelista Sami) wrote in message news:<5f59677c.0403050702.5387352b@posting.google.com>...
> here is the code :
>...
> it crashes on "Generate(El.all);" :
> raised CONSTRAINT_ERROR : main.adb:7 access check failed

Here is a slightly modified version.
It still crashes, but commenting out "type unused" makes
it work fine.

procedure Main is
  package Generator is

     type Element_Record is abstract tagged null record;
     type Element is access all Element_Record'Class;

     procedure Generate (El : Element_Record) is abstract;

     type Decl_Record is abstract
        new Element_Record with null record;

     type Type_Decl_Record is new Decl_Record with null record;

     type Discrete_Type_Decl_Record is
        new Type_Decl_Record with private;
     type Discrete_Type_Decl is access all Discrete_Type_Decl_Record;

     procedure Generate (El : Type_Decl_Record);

     type unused is new Discrete_Type_Decl_Record with private;

  private

     type Discrete_Type_Decl_Record is
        new Type_Decl_Record with null record;

     type unused is new Discrete_Type_Decl_Record with null record;

  end Generator;

  package body Generator is
     procedure Generate (El : Type_Decl_Record) is
     begin
        null;
     end;
  end Generator;

  use generator;
  x: Discrete_Type_Decl := new Discrete_Type_Decl_Record;
begin
   Generate(Element(x).all);
end;



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-06 19:09           ` Marius Amado Alves
@ 2004-03-07 12:35             ` Simon Wright
  2004-03-07 13:39               ` Marius Amado Alves
  2004-03-08 19:08               ` Adam Beneschan
  0 siblings, 2 replies; 67+ messages in thread
From: Simon Wright @ 2004-03-07 12:35 UTC (permalink / raw)


Marius Amado Alves <amado.alves@netcabo.pt> writes:

> It's very simple, really: you can only call concrete, and of course 
> available, operations.
> 
> In your program you had an abstract (not concrete) Generate (for a
> 'root' type), and a concrete, but not available Generate (for a
> derived type). The concrete Generate was not available (at the point
> of the call) because it was declared in the private part of a
> separate unit. So the first was available but was not concrete, the
> second was not available, GNAT went for the only available one, and
> naturally failed, mumbling whatever GNAT mumbles when it tries to
> call an abstract operation.

I'm sure this explanation isn't right. The pointer concerned is to a
classwide type, and the contract says that any actual Element_Record
has a concrete (callable) Generate operation.

It would be easy to construct a program where Main had access to an
Element (the classwide pointer) but not to Generator.Declarations
(where the concrete type is defined).

I agree that putting it in the private part is perhaps misleading, but
that doesn't mean it's wrong -- we need a lawyer!

I have seen GNAT bugs where a type was privately controlled ..

For info, the OP's program executes just fine under Object Ada 7.2.2.

-- 
Simon Wright                               100% Ada, no bugs.



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-07 12:35             ` Simon Wright
@ 2004-03-07 13:39               ` Marius Amado Alves
  2004-03-08 19:08               ` Adam Beneschan
  1 sibling, 0 replies; 67+ messages in thread
From: Marius Amado Alves @ 2004-03-07 13:39 UTC (permalink / raw)
  To: comp.lang.ada

On Sunday 07 March 2004 12:35, Simon Wright wrote:
> Marius Amado Alves <amado.alves@netcabo.pt> writes:
> > It's very simple, really: you can only call concrete, and of course
> > available, operations.
> ...
> I'm sure this explanation isn't right....
>
> I agree that putting it in the private part is perhaps misleading, but
> that doesn't mean it's wrong -- we need a lawyer!

Yes. The subtleties of Ada OOP. I stand corrected. I offered a
solution using a subset where the simple rule above is true.
I had the impression that this was sufficient for the original 
poster. Sami, sorry if I misunderstood. I'll be silent on full 
power Ada OOP, as clearly I don't master it. (Consistently, I 
never use it. And I don't feel I'm loosing much. Too convoluted. 
But that's another story.)



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-05 16:15     ` Marius Amado Alves
@ 2004-03-08 18:54       ` Adam Beneschan
  2004-03-08 23:42         ` Marius Amado Alves
  0 siblings, 1 reply; 67+ messages in thread
From: Adam Beneschan @ 2004-03-08 18:54 UTC (permalink / raw)


Marius Amado Alves <amado.alves@netcabo.pt> wrote in message news:<mailman.67.1078503118.327.comp.lang.ada@ada-france.org>...
> On Friday 05 March 2004 15:02, Evangelista Sami wrote:
> > ...
> > it crashes on "Generate(El.all);" :
> > raised CONSTRAINT_ERROR : main.adb:7 access check failed
> 
> I'm surprised it compiles at all given that Generate is a private operation 
> (and therefore not available outside its declarative region).

The "Generate" that the call refers to is declared in the public part
of Generator, and is thus visible.  Although this procedure is
abstract, the call is dispatching (because El.all's type is
class-wide), and thus legal.  It is perfectly fine for dispatching
calls to dispatch to subprograms that are not visible and couldn't be
called directly.  See 3.9.2(20), 7.3.1(6).

                                    -- Adam



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-07 12:35             ` Simon Wright
  2004-03-07 13:39               ` Marius Amado Alves
@ 2004-03-08 19:08               ` Adam Beneschan
  2004-03-08 20:03                 ` Hyman Rosen
  2004-03-10 15:51                 ` Evangelista Sami
  1 sibling, 2 replies; 67+ messages in thread
From: Adam Beneschan @ 2004-03-08 19:08 UTC (permalink / raw)


Simon Wright <simon@pushface.org> wrote in message news:<x7vu11096nc.fsf@smaug.pushface.org>...
> Marius Amado Alves <amado.alves@netcabo.pt> writes:
> 
> > It's very simple, really: you can only call concrete, and of course 
> > available, operations.
> > 
> > In your program you had an abstract (not concrete) Generate (for a
> > 'root' type), and a concrete, but not available Generate (for a
> > derived type). The concrete Generate was not available (at the point
> > of the call) because it was declared in the private part of a
> > separate unit. So the first was available but was not concrete, the
> > second was not available, GNAT went for the only available one, and
> > naturally failed, mumbling whatever GNAT mumbles when it tries to
> > call an abstract operation.
> 
> I'm sure this explanation isn't right. The pointer concerned is to a
> classwide type, and the contract says that any actual Element_Record
> has a concrete (callable) Generate operation.
> 
> It would be easy to construct a program where Main had access to an
> Element (the classwide pointer) but not to Generator.Declarations
> (where the concrete type is defined).
> 
> I agree that putting it in the private part is perhaps misleading, but
> that doesn't mean it's wrong -- we need a lawyer!

Putting it in the private part doesn't matter, since a dispatching
call can still reach it (3.9.2(20), 7.6.1(3)).  In this case, the
abstract Generate that Type_Decl_Record inherits is overridden with a
nonabstract version (in the private part of Generator.Declarations);
this nonabstract version is then inherited by
Discrete_Type_Decl_Record, and later by Enumerate_Type_Decl_Record. 
This last inherited subprogram is the one that should be called when a
dispatching call is made to Generate on an object of type
Enumerate_Type_Decl_Record.

In fact, I think the whole discussion about abstract vs. concrete
subprograms is barking up the wrong tree.  As far as I know, there
should never be a runtime error for an attempt to call an abstract
subprogram.  The language rules ensure that this can never happen,
i.e. that whenever you call a subprogram (either directly or via
dispatching), that subprogram will be concrete (as opposed to C++,
which I believe does not guarantee this).  This is accomplished by
disallowing objects of an abstract type and direct calls to an
abstract subprogram, and, most importantly, by disallowing primitive
abstract subprograms of a nonabstract tagged type, and by requiring
that abstract subprograms be overridden when a derived type is
nonabstract.

I would expect the "access check" error to refer to an accessibility
level problem (although I can't speak to what other errors might cause
GNAT to say "access check").  Since all the access types in this code
are library level (none is declared inside a subprogram), it does seem
quite weird that this error would be showing up.

                                  -- Adam



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-08 19:08               ` Adam Beneschan
@ 2004-03-08 20:03                 ` Hyman Rosen
  2004-03-09  8:51                   ` Dmitry A. Kazakov
  2004-03-10 15:51                 ` Evangelista Sami
  1 sibling, 1 reply; 67+ messages in thread
From: Hyman Rosen @ 2004-03-08 20:03 UTC (permalink / raw)


Adam Beneschan wrote:
 > whenever you call a subprogram (either directly or via dispatching),
 > that subprogram will be concrete (as opposed to C++, which I believe
 > does not guarantee this).

This can happen in C++ only during object constructors or destructors
which Ada does not have (at least not in the same sense). C++ does not
allow objects of abstract type to be created, but during the execution
of a constructor or destructor, the type of the object is the type of
the class whose *tor is running, and that may be an abstract class. It
is undefined behavior to make a dispatching call which resolves to an
abstract method. Most implementations make such a call execute a stub
function which prints an error message and aborts the program.



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-08 18:54       ` Adam Beneschan
@ 2004-03-08 23:42         ` Marius Amado Alves
  0 siblings, 0 replies; 67+ messages in thread
From: Marius Amado Alves @ 2004-03-08 23:42 UTC (permalink / raw)
  To: comp.lang.ada

> > I'm surprised it compiles at all given that Generate is a private
> > operation (and therefore not available outside its declarative region).
>
> It is perfectly fine for dispatching
> calls to dispatch to subprograms that are not visible and couldn't be
> called directly.  See 3.9.2(20), 7.3.1(6).

Yes. Even with a "note" for us laymen 3.9.2(21).
I stand corrected--again. (I can see clearly now how and why I erred.
A "different rule" for tagged types. Not sure I like it.)



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-08 20:03                 ` Hyman Rosen
@ 2004-03-09  8:51                   ` Dmitry A. Kazakov
  2004-03-09 13:34                     ` Hyman Rosen
  0 siblings, 1 reply; 67+ messages in thread
From: Dmitry A. Kazakov @ 2004-03-09  8:51 UTC (permalink / raw)


On Mon, 08 Mar 2004 15:03:20 -0500, Hyman Rosen <hyrosen@mail.com>
wrote:

>Adam Beneschan wrote:
> > whenever you call a subprogram (either directly or via dispatching),
> > that subprogram will be concrete (as opposed to C++, which I believe
> > does not guarantee this).
>
>This can happen in C++ only during object constructors or destructors
>which Ada does not have (at least not in the same sense). C++ does not
>allow objects of abstract type to be created, but during the execution
>of a constructor or destructor, the type of the object is the type of
>the class whose *tor is running, and that may be an abstract class.

Yes, this is because C++ OO model is inconsistent. Nothing may change
the object type, otherwise artifacts will inevitable show this or that
way.

> It
>is undefined behavior to make a dispatching call which resolves to an
>abstract method. Most implementations make such a call execute a stub
>function which prints an error message and aborts the program.

--
Regards,
Dmitry Kazakov
www.dmitry-kazakov.de



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-09  8:51                   ` Dmitry A. Kazakov
@ 2004-03-09 13:34                     ` Hyman Rosen
  2004-03-09 14:49                       ` Dmitry A. Kazakov
  0 siblings, 1 reply; 67+ messages in thread
From: Hyman Rosen @ 2004-03-09 13:34 UTC (permalink / raw)


Dmitry A. Kazakov wrote:
> Yes, this is because C++ OO model is inconsistent.

This is literally true, if by consistent you mean "objects have the same
type from the start of construction to the end of destruction".

> Nothing may change the object type

Obviously this is not a fixed law of nature, but merely a choice. C++ has
chosen otherwise.

> otherwise artifacts will inevitable show this or that way.

The artifacts that show up in the C++ model is that abstract methods may
be invoked by dispatching. The artifact that is avoided by the C++ model
is that methods are never invoked on unconstructed objects. It's a perfectly
reasonable tradeoff, since detecting dispatch to abstract methods is trivially
easy. It's the job of constructors to make an object consistent. Other methods
should never have to worry that they're being called with inconsistent state.



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-09 13:34                     ` Hyman Rosen
@ 2004-03-09 14:49                       ` Dmitry A. Kazakov
  2004-03-09 15:14                         ` Hyman Rosen
  0 siblings, 1 reply; 67+ messages in thread
From: Dmitry A. Kazakov @ 2004-03-09 14:49 UTC (permalink / raw)


On Tue, 09 Mar 2004 08:34:03 -0500, Hyman Rosen <hyrosen@mail.com>
wrote:

>Dmitry A. Kazakov wrote:
>> Yes, this is because C++ OO model is inconsistent.
>
>This is literally true, if by consistent you mean "objects have the same
>type from the start of construction to the end of destruction".
>
>> Nothing may change the object type
>
>Obviously this is not a fixed law of nature,

Well, it is a philosophical question... (:-))

>but merely a choice. C++ has chosen otherwise.

>> otherwise artifacts will inevitable show this or that way.
>
>The artifacts that show up in the C++ model is that abstract methods may
>be invoked by dispatching. The artifact that is avoided by the C++ model
>is that methods are never invoked on unconstructed objects.

The problem is that to construct an object (A) /= to construct the
dispatching table of its methods (B). C++ choice is very close to: A
implies B. Obviously it is a bad choice from many points of view. One
of them is safety, as the case shows.

>It's a perfectly
>reasonable tradeoff, since detecting dispatch to abstract methods is trivially
>easy. It's the job of constructors to make an object consistent.

True, but in practice it is difficult to view object construction /
destruction as monolitic or error free. I think that probably two
stage construction is needed. The first one for type specific
construction, the second for class-wide one (at least to have
dispatching in constructors)

>Other methods
>should never have to worry that they're being called with inconsistent state.

--
Regards,
Dmitry Kazakov
www.dmitry-kazakov.de
--
Regards,
Dmitry Kazakov
www.dmitry-kazakov.de



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-09 14:49                       ` Dmitry A. Kazakov
@ 2004-03-09 15:14                         ` Hyman Rosen
  2004-03-09 15:56                           ` Dmitry A. Kazakov
  0 siblings, 1 reply; 67+ messages in thread
From: Hyman Rosen @ 2004-03-09 15:14 UTC (permalink / raw)


Dmitry A. Kazakov wrote:
> The problem is that to construct an object (A) /= to construct the
> dispatching table of its methods (B).

If a constructor is able to call member functions, some choice must
be made as to where dispatching calls will go.

> C++ choice is very close to: A implies B.

Yes.

> Obviously it is a bad choice from many points of view.

It's obviously a good choice from my point of view.

> One of them is safety, as the case shows.

As I said, if calls to member functions are to be permitted,
as they are in C++, Java, and Ada, it must be decided what
happens in the case of dispatching. The C++ approach is the
safest, because when a method is actually reached, it sees
only properly initialized data for its type. If code does
happen to attempt to dispatch to an abstract method, the
result is formally undefined, but in practice implementations
dispatch to a stub function which aborts the program, and thus
the error is detected at the point where it happens.

> True, but in practice it is difficult to view object construction /
> destruction as monolitic or error free.

In C++ construction is neither monolithic nor required to be error-free,
in the sense that any constructor may throw an exception to abort the
creation of an object.


> I think that probably two stage construction is needed.
 > The first one for type specific construction, the second for class-wide
 > one (at least to have dispatching in constructors)

Constructors can always pass arguments up the hierarchy in C++,
so ultimate type information is available if needed. If you want
to require the ultimate class to provide this data with no chance
of forgetting, simply inherit from a virtual base class without a
default constructor.

struct RepositoryOfFinalTypeInformation
{
     int magic_type_code;
     RepositoryOfFinalTypeInformation(int code) : magic_type_code(code) { }
};

struct A : virtual RepositoryOfFinalTypeInformation
{
     A() : RepositoryOfFinalTypeInformation(1)
     {
         if (magic_type_code == 1) /* ultimately A */;
         if (magic_type_code == 2) /* ultimately B */;
         if (magic_type_code == 3) /* ultimately C */;
     }
};

struct B : A
{
     B() : RepositoryOfFinalTypeInformation(2) { }
};

struct C : B { }; /* error - will not compile */
struct C : B
{
     C() : RepositoryOfFinalTypeInformation(3) { } /* Aah. That's better. */
};



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-09 15:14                         ` Hyman Rosen
@ 2004-03-09 15:56                           ` Dmitry A. Kazakov
  2004-03-09 16:32                             ` Hyman Rosen
  0 siblings, 1 reply; 67+ messages in thread
From: Dmitry A. Kazakov @ 2004-03-09 15:56 UTC (permalink / raw)


On Tue, 09 Mar 2004 10:14:45 -0500, Hyman Rosen <hyrosen@mail.com>
wrote:

>Dmitry A. Kazakov wrote:
>> The problem is that to construct an object (A) /= to construct the
>> dispatching table of its methods (B).
>
>If a constructor is able to call member functions, some choice must
>be made as to where dispatching calls will go.

No. This is necessary only if the object is class-wide, i.e. when this
is treated as a class-wide pointer. Make it specific and the problem
will disappear.

>> C++ choice is very close to: A implies B.
>
>Yes.
>
>> Obviously it is a bad choice from many points of view.
>
>It's obviously a good choice from my point of view.
>
>> One of them is safety, as the case shows.
>
>As I said, if calls to member functions are to be permitted,
>as they are in C++, Java, and Ada, it must be decided what
>happens in the case of dispatching.

Which need not to be the case. See above.

>> True, but in practice it is difficult to view object construction /
>> destruction as monolitic or error free.
>
>In C++ construction is neither monolithic nor required to be error-free,
>in the sense that any constructor may throw an exception to abort the
>creation of an object.

... with calling an abstract method as a consequence?

>> I think that probably two stage construction is needed.
> > The first one for type specific construction, the second for class-wide
> > one (at least to have dispatching in constructors)
>
>Constructors can always pass arguments up the hierarchy in C++,
>so ultimate type information is available if needed. If you want
>to require the ultimate class to provide this data with no chance
>of forgetting, simply inherit from a virtual base class without a
>default constructor.

Dispatching on class-wides, which specific objects are not fully
constructed, is bogus. Nothing can heal it, even dispatching table
forgery cannot.

--
Regards,
Dmitry Kazakov
www.dmitry-kazakov.de



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-09 15:56                           ` Dmitry A. Kazakov
@ 2004-03-09 16:32                             ` Hyman Rosen
  2004-03-10  9:32                               ` Dmitry A. Kazakov
  0 siblings, 1 reply; 67+ messages in thread
From: Hyman Rosen @ 2004-03-09 16:32 UTC (permalink / raw)


Dmitry A. Kazakov wrote:
> No. This is necessary only if the object is class-wide, i.e. when this
> is treated as a class-wide pointer. Make it specific and the problem
> will disappear.

This is a vacuous statement. Yes, if you don't dispatch at all, then
you don't have to decide where dispatching goes. If you want to make
your own language where this is the case, go ahead, but C++, Java, and
Ada all allow (re)dispatching.


>>In C++ construction is neither monolithic nor required to be error-free,
>>in the sense that any constructor may throw an exception to abort the
>>creation of an object.
> 
> ... with calling an abstract method as a consequence?

No. In Java construction is not monolithic or exception-free either,
but there dispatching always uses the most derived type. Calling an
abstract method through dispatching from a *tor is a consequence of
C++ preventing methods from running on unconstructed objects. As I
keep saying, since such dispatching is easily detected, it's not a
problem in practice. Erroneous code is caught at the point of error.

> Dispatching on class-wides, which specific objects are not fully
> constructed, is bogus. Nothing can heal it, even dispatching table
> forgery cannot.

And yet, unless you program in a language which prevents such
dispatching from occurring, some decision must be made. None of
C++, Java, nor Ada agree that such dispatching should be prohibited,
so clearly your point of view is far from common.




^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-09 16:32                             ` Hyman Rosen
@ 2004-03-10  9:32                               ` Dmitry A. Kazakov
  2004-03-10 13:08                                 ` Hyman Rosen
  0 siblings, 1 reply; 67+ messages in thread
From: Dmitry A. Kazakov @ 2004-03-10  9:32 UTC (permalink / raw)


On Tue, 09 Mar 2004 11:32:40 -0500, Hyman Rosen <hyrosen@mail.com>
wrote:

>Dmitry A. Kazakov wrote:
>> No. This is necessary only if the object is class-wide, i.e. when this
>> is treated as a class-wide pointer. Make it specific and the problem
>> will disappear.
>
>This is a vacuous statement. Yes, if you don't dispatch at all, then
>you don't have to decide where dispatching goes.

Absolutely

>If you want to make your own language where this is the case, go ahead,

I will stay with Ada for a while (:-))

>but C++, Java, and Ada all allow (re)dispatching.

To allow /= to force. To re-dispatch in Ada you have do it expilictly
by converting the object of a specific type to a class-wide. This is
way different (though also problematic) from inconsistent attempts in
C++ to view the same thing as both specific and class-wide. This
cannot be reconciled.

>>>In C++ construction is neither monolithic nor required to be error-free,
>>>in the sense that any constructor may throw an exception to abort the
>>>creation of an object.
>> 
>> ... with calling an abstract method as a consequence?
>
>No. In Java construction is not monolithic or exception-free either,
>but there dispatching always uses the most derived type. Calling an
>abstract method through dispatching from a *tor is a consequence of
>C++ preventing methods from running on unconstructed objects. As I
>keep saying, since such dispatching is easily detected, it's not a
>problem in practice. Erroneous code is caught at the point of error.
>
>> Dispatching on class-wides, which specific objects are not fully
>> constructed, is bogus. Nothing can heal it, even dispatching table
>> forgery cannot.
>
>And yet, unless you program in a language which prevents such
>dispatching from occurring, some decision must be made. None of
>C++, Java, nor Ada agree that such dispatching should be prohibited,
>so clearly your point of view is far from common.

Huh. So the common point of view is that dispatching on unconstructed
objects is good?

Note also that in Ada it cannot happen. Initialize is not a "first
stage" constructor. It is called when all components of the object
have been constructed by "pre-constructors". For example:

type X is new Ada.Finalization.Controlled with null record;
procedure Initialize (Object : in out X);

type Y is new X with record
   New_Field : Integer := 10;
end record;
procedure Initialize (Object : in out Y);

When either of Initialize is called on an instance of Y, New_Field is
already "pre-constructed" and so contains 10. This solves a lot of
problems. And it is much close to what I wished to see in a good
programming language than C++ offers. As I said, IMO two-stage
construction is probably the only way.

--
Regards,
Dmitry Kazakov
www.dmitry-kazakov.de



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-10  9:32                               ` Dmitry A. Kazakov
@ 2004-03-10 13:08                                 ` Hyman Rosen
  2004-03-10 14:58                                   ` Robert I. Eachus
  2004-03-11 10:09                                   ` Dmitry A. Kazakov
  0 siblings, 2 replies; 67+ messages in thread
From: Hyman Rosen @ 2004-03-10 13:08 UTC (permalink / raw)


Dmitry A. Kazakov wrote:
> To allow /= to force. To re-dispatch in Ada you have do it expilictly
> by converting the object of a specific type to a class-wide. This is
> way different (though also problematic) from inconsistent attempts in
> C++ to view the same thing as both specific and class-wide. This
> cannot be reconciled.

In C++ you can also choose whether to dispatch or not, but the default
is to dispatch. If you want to call a function of a particular class,
you just specify that (eg., Mumble::Foo()) and you get it. Remember,
Ada compilers still use a vtable pointer inside each object, not the
way you would have it, a two-part object/vtable fat pointer.

> Huh. So the common point of view is that dispatching on unconstructed
> objects is good?

As I said, not in C++. But much fuss has been made of claiming that
C++'s way of dispatching in *tors is "confusing" and there are also
people who want constructors to be able to easily condition their
behavior based on the complete type of the object. So Java, and Ada
too, I suppose, do a two-step initialization. The bits of the object
are perhaps set to something marginally non-random (in Java it's all
bits zero; you describe what Ada does below) and then methods are on
their own to worry about whether things are ready to be used.

> Note also that in Ada it cannot happen.

Really? What if you have a hierarchical class where each base has its
own Initialize? Is each version responsible for calling the version of
its base? What is to keep a base version from converting to classwide
type and calling a dispatching method which will try to use an unset
member of the derived class (eg., a file not yet opened)?



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-10 13:08                                 ` Hyman Rosen
@ 2004-03-10 14:58                                   ` Robert I. Eachus
  2004-03-10 16:00                                     ` Hyman Rosen
  2004-03-11 10:09                                   ` Dmitry A. Kazakov
  1 sibling, 1 reply; 67+ messages in thread
From: Robert I. Eachus @ 2004-03-10 14:58 UTC (permalink / raw)


Hyman Rosen wrote:

> In C++ you can also choose whether to dispatch or not, but the default
> is to dispatch. If you want to call a function of a particular class,
> you just specify that (eg., Mumble::Foo()) and you get it. Remember,
> Ada compilers still use a vtable pointer inside each object, not the
> way you would have it, a two-part object/vtable fat pointer.

No, Ada semantics is that each object of a tagged type contains 
(surprise!) a tag.  It is common for this tag to be a pointer to the 
dispatch table.  There are provisions for converting the tag to a 
particular value for saving in files, or for distributed systems. (see 
RM E.2.3)

> Really? What if you have a hierarchical class where each base has its
> own Initialize? Is each version responsible for calling the version of
> its base? What is to keep a base version from converting to classwide
> type and calling a dispatching method which will try to use an unset
> member of the derived class (eg., a file not yet opened)?

The semantics of the language, of course.  You can, if you want create 
an Initialize procedure that reads an uninitialized component, but that 
is no different from any other procedure, and most compilers will warn 
you about it.  The usual is, as Dmitry pointed out, to provide default 
values for such components so that the problem never arises.

To answer your specific question, whether the file associated with an 
object is opened as a side effect of an initial value, or by Initialize 
procedure is up to the programmer.  But assuming that the type has an 
object of type File_Type from Ada.Text_IO, Ada.Sequential_IO, or 
Ada.Direct_IO, if the file is not opened before reading from it, 
Use_Error will be raised.  This means that the compiler chosen 
representation for File_Type must either be an access type, or a record 
type with at least one subcomponent with a default value.

-- 
                                           Robert I. Eachus

"The only thing necessary for the triumph of evil is for good men to do 
nothing." --Edmund Burke




^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-08 19:08               ` Adam Beneschan
  2004-03-08 20:03                 ` Hyman Rosen
@ 2004-03-10 15:51                 ` Evangelista Sami
  2004-03-11  1:38                   ` Dan Eilers
  1 sibling, 1 reply; 67+ messages in thread
From: Evangelista Sami @ 2004-03-10 15:51 UTC (permalink / raw)


adam@irvine.com (Adam Beneschan) wrote in message news:<b4682ab7.0403081108.7cf7436e@posting.google.com>...
> Simon Wright <simon@pushface.org> wrote in message news:<x7vu11096nc.fsf@smaug.pushface.org>...
> > Marius Amado Alves <amado.alves@netcabo.pt> writes:
> > 
> > > It's very simple, really: you can only call concrete, and of course 
> > > available, operations.
> > > 
> > > In your program you had an abstract (not concrete) Generate (for a
> > > 'root' type), and a concrete, but not available Generate (for a
> > > derived type). The concrete Generate was not available (at the point
> > > of the call) because it was declared in the private part of a
> > > separate unit. So the first was available but was not concrete, the
> > > second was not available, GNAT went for the only available one, and
> > > naturally failed, mumbling whatever GNAT mumbles when it tries to
> > > call an abstract operation.
> > 
> > I'm sure this explanation isn't right. The pointer concerned is to a
> > classwide type, and the contract says that any actual Element_Record
> > has a concrete (callable) Generate operation.
> > 
> > It would be easy to construct a program where Main had access to an
> > Element (the classwide pointer) but not to Generator.Declarations
> > (where the concrete type is defined).
> > 
> > I agree that putting it in the private part is perhaps misleading, but
> > that doesn't mean it's wrong -- we need a lawyer!
> 
> Putting it in the private part doesn't matter, since a dispatching
> call can still reach it (3.9.2(20), 7.6.1(3)).  In this case, the
> abstract Generate that Type_Decl_Record inherits is overridden with a
> nonabstract version (in the private part of Generator.Declarations);
> this nonabstract version is then inherited by
> Discrete_Type_Decl_Record, and later by Enumerate_Type_Decl_Record. 
> This last inherited subprogram is the one that should be called when a
> dispatching call is made to Generate on an object of type
> Enumerate_Type_Decl_Record.
> 
> In fact, I think the whole discussion about abstract vs. concrete
> subprograms is barking up the wrong tree.  As far as I know, there
> should never be a runtime error for an attempt to call an abstract
> subprogram.  The language rules ensure that this can never happen,
> i.e. that whenever you call a subprogram (either directly or via
> dispatching), that subprogram will be concrete (as opposed to C++,
> which I believe does not guarantee this).  This is accomplished by
> disallowing objects of an abstract type and direct calls to an
> abstract subprogram, and, most importantly, by disallowing primitive
> abstract subprograms of a nonabstract tagged type, and by requiring
> that abstract subprograms be overridden when a derived type is
> nonabstract.
> 
> I would expect the "access check" error to refer to an accessibility
> level problem (although I can't speak to what other errors might cause
> GNAT to say "access check").  Since all the access types in this code
> are library level (none is declared inside a subprogram), it does seem
> quite weird that this error would be showing up.
> 
>                                   -- Adam





so the conclusion is that it seems to be a gnat bug?
could you give me an advice to change this?



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-10 14:58                                   ` Robert I. Eachus
@ 2004-03-10 16:00                                     ` Hyman Rosen
  2004-03-10 18:07                                       ` Robert I. Eachus
  0 siblings, 1 reply; 67+ messages in thread
From: Hyman Rosen @ 2004-03-10 16:00 UTC (permalink / raw)


Robert I. Eachus wrote:
> No, Ada semantics is that each object of a tagged type contains a tag.

Yeah, I actually know that. It's the same in C++. The standard doesn't
require any particular implementation. I think you could even do DK's
fat pointers if you wanted. But it's so conventional to have a vtable
pointer in the object that I just used that as shorthand.

> To answer your specific question

I don't think you're answering to the issue at hand. The first question
is, if you have a class hierarchy where each class has an Initialize
defined for itself, and you create an object of the most derived class,
does Ada arrange for all of the Initialize procedures to be called, or
does each Initialize need to explicitly invoke its parent? The second
question, or observation, is that however it's called, a parent Initialize
can view-convert its object to classwide type and call a dispatching
procedure on it. This means that a derived class procedure might be
called from a parent Initialize before its own Initialize has prepared
the class. I'm not saying this is common, but DK was saying that this
couldn't happen in Ada, and I'm saying that it can. It can't happen in
C++, but in excahnge, C++ can dispatch to an abstract procedure, which
is formally undefined behavior, but which implementations generally turn
into an immediate abort, so that the problematic call can be found.



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-10 16:00                                     ` Hyman Rosen
@ 2004-03-10 18:07                                       ` Robert I. Eachus
  2004-03-10 20:04                                         ` Hyman Rosen
  0 siblings, 1 reply; 67+ messages in thread
From: Robert I. Eachus @ 2004-03-10 18:07 UTC (permalink / raw)


Hyman Rosen wrote:

> I don't think you're answering to the issue at hand. The first question
> is, if you have a class hierarchy where each class has an Initialize
> defined for itself, and you create an object of the most derived class,
> does Ada arrange for all of the Initialize procedures to be called, or
> does each Initialize need to explicitly invoke its parent?

The usual in Ada is to define any tagged type that has a private part 
such that it always gets initialized correctly, independent of whether a 
child class is created, with or without an explicit Initialize.  It is 
also normal to insure that even if the Initialize procedure is called 
twice, there is no problem.

Doing this correctly for Finalization is a bit more complex, because of 
the possibility of an exception during the creation, Initialize call, or 
  Finalize for a child type.

> The second
> question, or observation, is that however it's called, a parent Initialize
> can view-convert its object to classwide type and call a dispatching
> procedure on it. This means that a derived class procedure might be
> called from a parent Initialize before its own Initialize has prepared
> the class. I'm not saying this is common, but DK was saying that this
> couldn't happen in Ada, and I'm saying that it can.

DK is right, in fact I have trouble figuring out which problem you are 
trying to create here.  A view conversion can convert to a parent type, 
but to down convert to a type, the object has to be of that type (or a 
descendant of it).  So what you are imagining sounds like an explicit 
call to Parent.Initialize(Parent_Type(Child_Object)), where 
Parent.Initialize then makes a dispatching call to some other operation 
of the child type.  First, let me say, DON'T DO THAT.  It will be very 
difficult to design real examples where you don't raise an exception. 
Now, as it turns out the only way this could cause a problem is if 
Child.Initialize had a nested call to Parent.Initialize before the 
Initialization of the Child specific fields was completed.  Again, you 
could do that, but you should not for other reasons.  It is much more 
common, however, for such a call to be done for only the parent part of 
the object, for example when creating a child object using an aggregate.
There is no problem in that case, because the (implicit) call to the 
parent Initialize is for an object of the parent type. Also, as I said 
above, and as Dmitry said, there is a two-stage initialization process, 
and it is normal in Ada to insure that all fields where it matters are 
intialized to some default value in the first stage of initialization.

So if you really want to, you could probably design some example program 
where you violate all three of these principles, and it causes a 
problem.  But it is much easier to use Unchecked_Conversion to get to 
the same place.

-- 
                                           Robert I. Eachus

"The only thing necessary for the triumph of evil is for good men to do 
nothing." --Edmund Burke




^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-10 18:07                                       ` Robert I. Eachus
@ 2004-03-10 20:04                                         ` Hyman Rosen
  2004-03-11  2:43                                           ` Robert I. Eachus
  0 siblings, 1 reply; 67+ messages in thread
From: Hyman Rosen @ 2004-03-10 20:04 UTC (permalink / raw)


Robert I. Eachus wrote:
> DK is right DON'T DO THAT So if you really want to

So he's not right, because it's not impossible, just very unusual.

Anyway, let's see if I understand. You are saying that in Ada, it
is unusual to call Parent.Initialize(Parent_Type(Child_Object)) from
Child's Initialize. That seems odd to me; how does the child know that
the parent can do without this call? If it can be so blithely ignored,
what good is having it in the first place?

You also say that "It is much more common, however, for such a call to
be done for only the parent part of the object, for example when creating
a child object using an aggregate." Isn't it the case that when you do that,
Child's Initialize won't be called at all?

You also say that as a matter of policy, Initialize should not make
a dispatching calls because it is likely to be incorrect. That's what
C++ avoids by preventing dispatching from going to parts of the final
object which are not yet constructed.



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-10 15:51                 ` Evangelista Sami
@ 2004-03-11  1:38                   ` Dan Eilers
  0 siblings, 0 replies; 67+ messages in thread
From: Dan Eilers @ 2004-03-11  1:38 UTC (permalink / raw)


evangeli@cnam.fr (Evangelista Sami) wrote in message news:<5f59677c.0403100751.4e908ad9@posting.google.com>...

> so the conclusion is that it seems to be a gnat bug?
> could you give me an advice to change this?
\x14
Yes, this seems to be a gnat bug.
The problem is as you described earlier:

>--in fact i have 5 levels :
>--element -> decl -> type_decl -> discrete_type_decl -> enumerate_type_decl

In my earlier post, I showed that the mere existence of the 5th level
causes the problem, even if it is totally unused.

However, if 5 levels are needed, it may be possible to avoid the  
problem by adding explicit type conversions.  For example, in main,
change
   Generate(El.all);
to
   Generate(Type_Decl_Record(El.all));

	-- Dan Eilers



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-10 20:04                                         ` Hyman Rosen
@ 2004-03-11  2:43                                           ` Robert I. Eachus
  2004-03-11 13:55                                             ` Hyman Rosen
  0 siblings, 1 reply; 67+ messages in thread
From: Robert I. Eachus @ 2004-03-11  2:43 UTC (permalink / raw)


Hyman Rosen wrote:

> So he's not right, because it's not impossible, just very unusual.

No, he is right.  As a langauge lawyer I know how to "get around the 
rules."  But counting something that you can do in a child package of 
Ada.Controlled or Ada.Limited_Controlled as a property of the language 
is silly.  There are other ways to work around the rule, but they are 
similarly unlikely.  So things that can be done with a user defined type 
derived from some other user defined type are interesting, but in the 
end at least one Intialize procedure will be called for all user defined 
controlled objects.

> Anyway, let's see if I understand. You are saying that in Ada, it
> is unusual to call Parent.Initialize(Parent_Type(Child_Object)) from
> Child's Initialize. That seems odd to me; how does the child know that
> the parent can do without this call? If it can be so blithely ignored,
> what good is having it in the first place?

No, as I said, there are two common idioms, the first is to call the 
parent Initialize AFTER doing any initialization work needed on the 
fields that occur only in the child.  (The second is discussed below.)

> You also say that "It is much more common, however, for such a call to
> be done for only the parent part of the object, for example when creating
> a child object using an aggregate." Isn't it the case that when you do 
> that, Child's Initialize won't be called at all?

Yes, the parent object will be Initialized, and for a Controlled (not 
Limited_Controlled) aggregate assignment, the object created in this way 
will be Adjusted as a whole.  But I think you missed my main point, that 
the Initialize procedure for the child type may use an 
extension_aggregate to create the object, then make any necessary 
adjustments, either explicitly or implicitly through the Adjust call 
made for regular Controlled types.  Obviously you don't want the child's 
Initialize procedure to be called on extension_aggregates, because an 
extension_aggregate may necessary inside the Initialize procedure of the 
child type.

Sort of a meta comment here, and it goes back to what I said above.  All 
objects of types descended from Controlled or Limited_Controlled have 
fields that are not visible to an ordinary user.  This means that for 
most users what they can do in a child package of Ada.Controlled is of 
limited interest.  This is really what makes the only interesting cases:

procedure Initalize(Object: in out Child) is
begin
   Do_Something(Object);
   Initialize(Parent(Object));
end Initialize;

or:

procedure Initialize(Object: in out Child) is
begin
   Object := (Parent_Type with ...);
   Do_Something_Else(Object);
end Initialize;

Now notice that it is trivially obvious that in Do_Something or 
Do_Something_Else, Object is not fully initialized.  But we are inside 
the Initialize procedure, so of course it hasn't completed yet.  As a 
programmer it is not hard to choose whichever idiom is appropriate for 
the type you are defining.  But it is also the case that for any 
controlled Object users cannot bypass the first stage of initialization, 
where Object is created and all components are initialized to their 
default values. (Or in the extension aggregate case, given valid 
non-default values.)

So yes, there are areas where user written code can access an object 
that has not yet been completely initialized.  But in practice, as a 
user you will find that once you get rid of any bugs (and exceptions) 
inside Intialize, Adjust, and Finalize, that code written by others, 
including the authors of types derived from your types, cannot mess up 
the invariants of your type.

> You also say that as a matter of policy, Initialize should not make
> a dispatching calls because it is likely to be incorrect. That's what
> C++ avoids by preventing dispatching from going to parts of the final
> object which are not yet constructed.

No, I did not say that.  I said that attempts to down convert an object 
to a child type will fail. Technically, it is not possible to do a VALUE 
conversion from a parent type to a (tagged) child type.  You will find 
that in some cases a view conversion is legal, but an attempt to assign 
to that view will often raise Constraint_Error (or Program_Error).

I am not going to try and describe here when conversions are illegal, 
when they will raise Program_Error, and when they will raise 
Constraint_Error.  All that is spelled out in RM 4.6, and it is hard to 
say it in fewer words. (RM 4.6 is cryptic enough!  You might want to use 
the AARM instead: http://www.adaic.org/standards/95aarm/html/AA-4-6.html)

So what I was saying was that it is possible to put an view conversion 
to a child type inside an Initialize procedure, but if you try to write 
the code, you will find yourself dodging lots of land mines.  For 
example, the body of the package which defines the parent type is going 
to have to with the package that defines the child type.  But if you are 
not very careful, there will be no valid elaboration order for the 
corresponding package bodies.  There are lots of land mines that have 
nothing to do with the issues you are trying to raise here that can come 
up if you try what you indicated.

It is like discussing the speed at which you can drive southbound down 
the northbound side of a divided highway.  You may be interested in 
whether or not the banking of the curves works correctly.  However, 
there are going to be lots of impediments to doing the actual experiment 
that have nothing to do with curves, banking, or automobile suspensions. 
  (They would have something to do with license suspensions though. ;-)

-- 
                                           Robert I. Eachus

"The only thing necessary for the triumph of evil is for good men to do 
nothing." --Edmund Burke




^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-10 13:08                                 ` Hyman Rosen
  2004-03-10 14:58                                   ` Robert I. Eachus
@ 2004-03-11 10:09                                   ` Dmitry A. Kazakov
  2004-03-11 14:10                                     ` Hyman Rosen
  1 sibling, 1 reply; 67+ messages in thread
From: Dmitry A. Kazakov @ 2004-03-11 10:09 UTC (permalink / raw)


On Wed, 10 Mar 2004 08:08:56 -0500, Hyman Rosen <hyrosen@mail.com>
wrote:

>Dmitry A. Kazakov wrote:
>> To allow /= to force. To re-dispatch in Ada you have do it expilictly
>> by converting the object of a specific type to a class-wide. This is
>> way different (though also problematic) from inconsistent attempts in
>> C++ to view the same thing as both specific and class-wide. This
>> cannot be reconciled.
>
>In C++ you can also choose whether to dispatch or not, but the default
>is to dispatch. If you want to call a function of a particular class,
>you just specify that (eg., Mumble::Foo()) and you get it.

To dispatch vs. not has to be defined by solely the object type. This
is the only right way, IMO, and Ada conforms to it.

>Remember,
>Ada compilers still use a vtable pointer inside each object, not the
>way you would have it, a two-part object/vtable fat pointer.

That tags are embedded in Ada is a consequence of a desire to have
re-dispatch and more generally to have objects with identity. Though
as practice shown it was not needed, because the Rosen trick does the
same and has the advantage of explicit indication that identity is
indeed relevant. However the Ada concept of specific and class-wide
types as different types is one of rare real advances in that swamp
called OO. And yes, the concept could be used for non-tagged types
having the best of both worlds.

>> Huh. So the common point of view is that dispatching on unconstructed
>> objects is good?
>
>As I said, not in C++. But much fuss has been made of claiming that
>C++'s way of dispatching in *tors is "confusing" and there are also
>people who want constructors to be able to easily condition their
>behavior based on the complete type of the object. So Java, and Ada
>too, I suppose, do a two-step initialization. The bits of the object
>are perhaps set to something marginally non-random (in Java it's all
>bits zero; you describe what Ada does below) and then methods are on
>their own to worry about whether things are ready to be used.
>
>> Note also that in Ada it cannot happen.
>
>Really? What if you have a hierarchical class where each base has its
>own Initialize? Is each version responsible for calling the version of
>its base?

Yes, but Initialize is not a constructor in C++ sense. When Ada will
have [first stage] constructors that responsibility will become one of
the compiler.

>What is to keep a base version from converting to classwide
>type and calling a dispatching method which will try to use an unset
>member of the derived class (eg., a file not yet opened)?

That will be difficult to achieve with controlled types because they
have *reverse* order of calls to Initialize, but I understand the
question.

When objects are constructed in two stages, the first stage makes some
basic initialization in a way that a base knows nothing about
children. This initialization *cannot* be overriden by children, only
extended. C++ tries to be conform with that, but it has an unsolvable
problems of its messing with specific vs. class-wide. At this stage no
dispatch is needed and no surprises to expect. This exists in Ada, but
it is not user-definable, yet. The second stage construction can be
made class-wide, so that a base could dispatch from its constructor to
child methods. Note that this construction should be made in a reverse
order - children first, exactly as Initialize in Ada works. That makes
dispatch in base's Initialize safe. To summarize:

1. The first stage constructs a *specific* instance of a Derived_Type,
descendant of Type, out of a raw memory chunk. It creates an object of
the type Derived_Type, but that might be not a valid instance of
Type'Class. This makes

2. the second stage by constructing Type'Class out of Derived_Type.

Returning to your example, if Type is an abstract type, and
Derived_Type has to have File, accessed by Type via some dispatching
method, then File (a handle to) has to be initialized and opened at
the stage one, and the class-wide constructor of Type may safely use
it at the stage two.

IMO Ada model is very close to that.

--
Regards,
Dmitry Kazakov
www.dmitry-kazakov.de



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-11  2:43                                           ` Robert I. Eachus
@ 2004-03-11 13:55                                             ` Hyman Rosen
  2004-03-12 23:02                                               ` Robert I. Eachus
  2004-03-16  6:00                                               ` Randy Brukardt
  0 siblings, 2 replies; 67+ messages in thread
From: Hyman Rosen @ 2004-03-11 13:55 UTC (permalink / raw)


Robert I. Eachus wrote:
> No, he is right.

We'll get to that below.

> No, as I said, there are two common idioms, the first is to call the 
> parent Initialize AFTER doing any initialization work needed on the 
> fields that occur only in the child.
> 
> procedure Initalize(Object: in out Child) is
> begin
>   Do_Something(Object);
>   Initialize(Parent(Object));
> end Initialize;

Hmm. The understanding in C++ is that in a constructor, all of the base
constructors have completed, so all facilities of the base classes are
available to be used in the derived class constructors. I'm not saying
that this first idiom you describe is wrong for Ada, but it does seem to
force derived classes into having very intimate knowledge of the needs
of their bases. What if initializing the child fields requires making
calls to base methods?

> procedure Initialize(Object: in out Child) is
> begin
>   Object := (Parent_Type with ...);
>   Do_Something_Else(Object);
> end Initialize;

Nice. In C++ it is highly discouraged to implement constructors via
assignment (because it is fragile and error-prone). If I'm not mistaken,
this code will cause Finalize to be called on Object before the assignment
takes place, which means that it's possible for Finalize to be called on
an object before any Initialize for it has completed. I really have to stop
thinking of Initialize/Adjust/Finalize as being equivalent to C++'s
constructor/assignment/destructor. It may just be that I haven't gotten my
mind around it properly, but it seems to me that Ada OO is significantly
harder to understand and explain than Java's or C++'s, even (or especially)
for simple cases.

> So yes, there are areas where user written code can access an object 
> that has not yet been completely initialized.  But in practice, as a 
> user you will find that once you get rid of any bugs (and exceptions) 
> inside Intialize, Adjust, and Finalize, that code written by others, 
> including the authors of types derived from your types, cannot mess up 
> the invariants of your type.

I agree. But that's why I said that DK was wrong. Our discussion has been
about code that runs during object construction and destruction. That is,
the process of getting rid of the bugs and exceptions that you mention.

> No, I did not say that.  I said that attempts to down convert an object 
> to a child type will fail.

That's not what I was talking about. I was saying that in a parent
Initialize, the object could be view-converted to its classwide type,
and then a dispatching call to could be made on the converted object
which would wind up calling an overridden method of the child's type.
Think of the template-method design pattern for example. (This would
be in your idiom one code, but with the parent's Initialize called
*before* the Do_Something(Object), because Do_Something requires that
its base classes be completely set up.) So overridden methods could
wind up being called on incompletely prepared objects. This can't
happen in C++. There, the dispatching would only go to methods of the
class whose constructor or destructor is running. If such a method is
abstract, the implementation generally arranges for the program to
abort.



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-11 10:09                                   ` Dmitry A. Kazakov
@ 2004-03-11 14:10                                     ` Hyman Rosen
  2004-03-11 14:59                                       ` Dmitry A. Kazakov
  0 siblings, 1 reply; 67+ messages in thread
From: Hyman Rosen @ 2004-03-11 14:10 UTC (permalink / raw)


Dmitry A. Kazakov wrote:
> To dispatch vs. not has to be defined by solely the object type. This
> is the only right way, IMO, and Ada conforms to it.

"Has to be"? "The only right way"? That's simply your opinion.

> Returning to your example, if Type is an abstract type, and
> Derived_Type has to have File, accessed by Type via some dispatching
> method, then File (a handle to) has to be initialized and opened at
> the stage one, and the class-wide constructor of Type may safely use
> it at the stage two.

Right, but what if you didn't realize that, and didn't code this
correctly? Presumably with File, you would get an exception, but
there could be other types which would cause subtle errors that
could go undetected until much later in the program. The question
isn't how to do it right, but what happens when you do it wrong.
C++ doesn't let you dispatch to the child in such a case, so the
problem is noticed right away. If the parent actually needs some
part of the child, that has to be coded explicitly, and that
visibility makes an error much less likely.



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-11 14:10                                     ` Hyman Rosen
@ 2004-03-11 14:59                                       ` Dmitry A. Kazakov
  2004-03-11 15:40                                         ` Hyman Rosen
  0 siblings, 1 reply; 67+ messages in thread
From: Dmitry A. Kazakov @ 2004-03-11 14:59 UTC (permalink / raw)


On Thu, 11 Mar 2004 09:10:36 -0500, Hyman Rosen <hyrosen@mail.com>
wrote:

>Dmitry A. Kazakov wrote:
>> To dispatch vs. not has to be defined by solely the object type. This
>> is the only right way, IMO, and Ada conforms to it.
>
>"Has to be"? "The only right way"? That's simply your opinion.

No, this is just a contract model.

>> Returning to your example, if Type is an abstract type, and
>> Derived_Type has to have File, accessed by Type via some dispatching
>> method, then File (a handle to) has to be initialized and opened at
>> the stage one, and the class-wide constructor of Type may safely use
>> it at the stage two.
>
>Right, but what if you didn't realize that, and didn't code this
>correctly?

These are different things - to have a bad design or to have a good
one, but poorly coded one.

>Presumably with File, you would get an exception, but
>there could be other types which would cause subtle errors that
>could go undetected until much later in the program. The question
>isn't how to do it right, but what happens when you do it wrong.

You cannot do it right if the language does not support object
construction properly.

>C++ doesn't let you dispatch to the child in such a case, so the
>problem is noticed right away.

Noticed but not solved.

>If the parent actually needs some
>part of the child, that has to be coded explicitly, and that
>visibility makes an error much less likely.

I tried to show that the source of the problem is that to construct T
/= to construct T'Class. It is not obvious, especially if you are
using C++ pretending that T=T'Class. So a desire to dispatch from a
constructor is not a whim, it is the necessty to construct a
*class-wide* object.

--
Regards,
Dmitry Kazakov
www.dmitry-kazakov.de



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-11 14:59                                       ` Dmitry A. Kazakov
@ 2004-03-11 15:40                                         ` Hyman Rosen
  2004-03-11 16:28                                           ` Dmitry A. Kazakov
  0 siblings, 1 reply; 67+ messages in thread
From: Hyman Rosen @ 2004-03-11 15:40 UTC (permalink / raw)


Dmitry A. Kazakov wrote:
> No, this is just a contract model.

Between what parties?

> These are different things - to have a bad design or to have a good
> one, but poorly coded one.

You want your language to help you catch the poor coding. That's why
Ada catches bounds errors, for example.

> You cannot do it right if the language does not support object
> construction properly.

The concept of two-phase initialization is a backward step in OO.
The paradigm of C++'s *tor mechanism is that other methods don't
ever need object validity checks, because constructors make the
object kosher.

> I tried to show that the source of the problem is that to construct T
> /= to construct T'Class.

This makes no sense. A general object, in the OO world, may have each
derived class require services from its base class, even during object
construction. Bases may need to invoke services from derived classes,
such as in the template method design pattern. When the latter is
invoked from the former, problems arise. I have no idea what this has
to do with being classwide or not.



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-11 15:40                                         ` Hyman Rosen
@ 2004-03-11 16:28                                           ` Dmitry A. Kazakov
  2004-03-11 17:26                                             ` Hyman Rosen
  0 siblings, 1 reply; 67+ messages in thread
From: Dmitry A. Kazakov @ 2004-03-11 16:28 UTC (permalink / raw)


On Thu, 11 Mar 2004 10:40:02 -0500, Hyman Rosen <hyrosen@mail.com>
wrote:

>Dmitry A. Kazakov wrote:
>> No, this is just a contract model.
>
>Between what parties?

Interface developer and its user

>> These are different things - to have a bad design or to have a good
>> one, but poorly coded one.
>
>You want your language to help you catch the poor coding. That's why
>Ada catches bounds errors, for example.

Exactly

>> You cannot do it right if the language does not support object
>> construction properly.
>
>The concept of two-phase initialization is a backward step in OO.

If that would be true, no C++ library would have Init() in each and
other class.

>The paradigm of C++'s *tor mechanism is that other methods don't
>ever need object validity checks, because constructors make the
>object kosher.

But they cannot do it, when dispaching methods need to be called as a
part of base specific object construction.

>> I tried to show that the source of the problem is that to construct T
>> /= to construct T'Class.
>
>This makes no sense.

Any primitive operation (method) can be either dispatching or
class-wide. So a constructor / destructor. This a programmer's choice,
not one of the language, if one wishes to make some parts of
constructor overridable (=dispatching), some not (=specific), and some
common for all descendants (=class-wide). 

> A general object, in the OO world, may have each
>derived class require services from its base class, even during object
>construction. Bases may need to invoke services from derived classes,
>such as in the template method design pattern. When the latter is
>invoked from the former, problems arise. I have no idea what this has
>to do with being classwide or not.

It quite obvious: "To invoke a service from a derived type" is a more
complicated way to say "to dispatch". But dispatching is possible on
class-wide objects only.

--
Regards,
Dmitry Kazakov
www.dmitry-kazakov.de



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-11 16:28                                           ` Dmitry A. Kazakov
@ 2004-03-11 17:26                                             ` Hyman Rosen
  2004-03-12  8:53                                               ` Dmitry A. Kazakov
  2004-03-12 18:07                                               ` Robert I. Eachus
  0 siblings, 2 replies; 67+ messages in thread
From: Hyman Rosen @ 2004-03-11 17:26 UTC (permalink / raw)


Dmitry A. Kazakov wrote:
> If that would be true, no C++ library would have Init() in each and
> other class.

As you might already suspect, there is no paucity of poorly designed
C++ code, OO and otherwise. There is also a good deal of C++ that was
written for exception handling disabled, and used Init methods as a
way of detecting failure of construction.

> But they cannot do it, when dispaching methods need to be called as a
> part of base specific object construction.

Correct. This makes the chicken-and-egg problem visible to the programmer,
who must then code a workaround. This is C++ helping, not hindering.

> It quite obvious: "To invoke a service from a derived type" is a more
> complicated way to say "to dispatch". But dispatching is possible on
> class-wide objects only.

But the distinction between classwide objects and non-classwide objects
exists only in your universe. In Ada you can always view-convert an object
into its classwide type.



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-11 17:26                                             ` Hyman Rosen
@ 2004-03-12  8:53                                               ` Dmitry A. Kazakov
  2004-03-12 13:09                                                 ` Hyman Rosen
  2004-03-12 18:07                                               ` Robert I. Eachus
  1 sibling, 1 reply; 67+ messages in thread
From: Dmitry A. Kazakov @ 2004-03-12  8:53 UTC (permalink / raw)


On Thu, 11 Mar 2004 12:26:28 -0500, Hyman Rosen <hyrosen@mail.com>
wrote:

>Dmitry A. Kazakov wrote:
>> If that would be true, no C++ library would have Init() in each and
>> other class.
>
>As you might already suspect, there is no paucity of poorly designed
>C++ code, OO and otherwise. There is also a good deal of C++ that was
>written for exception handling disabled, and used Init methods as a
>way of detecting failure of construction.
>
>> But they cannot do it, when dispaching methods need to be called as a
>> part of base specific object construction.
>
>Correct. This makes the chicken-and-egg problem visible to the programmer,
>who must then code a workaround. This is C++ helping, not hindering.
>
>> It quite obvious: "To invoke a service from a derived type" is a more
>> complicated way to say "to dispatch". But dispatching is possible on
>> class-wide objects only.
>
>But the distinction between classwide objects and non-classwide objects
>exists only in your universe.

As a matter of fact, in Ada T and T'Class are different types. I tried
to explain you that the problem with constructors in C++ is rooted in
equalizing T and T'Class. That in your universe there is no such
distinction does not mend the problem.

>In Ada you can always view-convert an object
>into its classwide type.

Yes, this is why a two-stage construction is required for them. The
point is very simple - each type needs a constructor. So when T is
view-convertible to T'Class, then two constructors have to be applied:
one of T, one of T'Class. In the constructor of T you cannot dispatch,
in the constructor of T'Class you can do it safely.

Observe that should Ada have classes for all types and user-defined
constructors too, then for types having different representation of T
and T'Class, the objects would be constructed only once by the type
specific constructor, exactly how C++ does it. The class-wide
constructor would be called only during conversion to T'Class, which
would become a true conversion producing a new object.

--
Regards,
Dmitry Kazakov
www.dmitry-kazakov.de



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-12  8:53                                               ` Dmitry A. Kazakov
@ 2004-03-12 13:09                                                 ` Hyman Rosen
  2004-03-12 14:00                                                   ` Dmitry A. Kazakov
  0 siblings, 1 reply; 67+ messages in thread
From: Hyman Rosen @ 2004-03-12 13:09 UTC (permalink / raw)


Dmitry A. Kazakov wrote:
> As a matter of fact, in Ada T and T'Class are different types. I tried
> to explain you that the problem with constructors in C++ is rooted in
> equalizing T and T'Class. That in your universe there is no such
> distinction does not mend the problem.
> 
> ... this is why a two-stage construction is required for them. The
> point is very simple - each type needs a constructor. So when T is
> view-convertible to T'Class, then two constructors have to be applied:
> one of T, one of T'Class. In the constructor of T you cannot dispatch,
> in the constructor of T'Class you can do it safely.

But this is not Ada, but AdaDK. In Ada, T'Class objects don't have
their own constructors, or initializers, or anything. All T'Class
objects are just a view of a (derived from) T object. Am I wrong?
A declared T'Class object has to be initialized by a T actual,
perhaps one returned by a T'Class function, where the return value
must then be a T actual. A declared T'Class parameter is just a
view of some T object.

I think that once again you are making blanket statements which
confuse, or conflate, aspects of Ada as they are and aspects of
Ada as you wish they would be.



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-12 13:09                                                 ` Hyman Rosen
@ 2004-03-12 14:00                                                   ` Dmitry A. Kazakov
  2004-03-12 14:56                                                     ` Hyman Rosen
  0 siblings, 1 reply; 67+ messages in thread
From: Dmitry A. Kazakov @ 2004-03-12 14:00 UTC (permalink / raw)


On Fri, 12 Mar 2004 08:09:51 -0500, Hyman Rosen <hyrosen@mail.com>
wrote:

>Dmitry A. Kazakov wrote:
>> As a matter of fact, in Ada T and T'Class are different types. I tried
>> to explain you that the problem with constructors in C++ is rooted in
>> equalizing T and T'Class. That in your universe there is no such
>> distinction does not mend the problem.
>> 
>> ... this is why a two-stage construction is required for them. The
>> point is very simple - each type needs a constructor. So when T is
>> view-convertible to T'Class, then two constructors have to be applied:
>> one of T, one of T'Class. In the constructor of T you cannot dispatch,
>> in the constructor of T'Class you can do it safely.
>
>But this is not Ada, but AdaDK. In Ada, T'Class objects don't have
>their own constructors, or initializers, or anything. All T'Class
>objects are just a view of a (derived from) T object. Am I wrong?
>A declared T'Class object has to be initialized by a T actual,
>perhaps one returned by a T'Class function, where the return value
>must then be a T actual. A declared T'Class parameter is just a
>view of some T object.
>
>I think that once again you are making blanket statements which
>confuse, or conflate, aspects of Ada as they are and aspects of
>Ada as you wish they would be.

Any comparative consideration of object construction should be based
on this or that theory, which according to you always confuse and
conflate whatsoever aspects of a language X.

OK, let's add to the law of Moses:

C++ does everything right and anything else is not Ada.

--
Regards,
Dmitry Kazakov
www.dmitry-kazakov.de



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-12 14:00                                                   ` Dmitry A. Kazakov
@ 2004-03-12 14:56                                                     ` Hyman Rosen
  2004-03-12 18:19                                                       ` Dmitry A. Kazakov
  0 siblings, 1 reply; 67+ messages in thread
From: Hyman Rosen @ 2004-03-12 14:56 UTC (permalink / raw)


Dmitry A. Kazakov wrote:
> C++ does everything right and anything else is not Ada.

This has nothing to do with programming language wars,
but perhaps with English language wars. When you state
categorically "So when T is view-convertible to T'Class,
then two constructors have to be applied: one of T, one
of T'Class. In the constructor of T you cannot dispatch,
in the constructor of T'Class you can do it safely." it
certainly sounds like you are saying that this is what
Ada does. But this is not what Ada does, it is what you
would like Ada to do. Responding to such statements is
difficult when it's not clear which case you are talking
about.



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-11 17:26                                             ` Hyman Rosen
  2004-03-12  8:53                                               ` Dmitry A. Kazakov
@ 2004-03-12 18:07                                               ` Robert I. Eachus
  1 sibling, 0 replies; 67+ messages in thread
From: Robert I. Eachus @ 2004-03-12 18:07 UTC (permalink / raw)


Hyman Rosen wrote:

> But the distinction between classwide objects and non-classwide objects
> exists only in your universe. In Ada you can always view-convert an object
> into its classwide type.

In Ada every tagged object is a member of a single, non-abstract, 
specific type.  You can have a view of an object which is class-wide, 
and you can have parameter or variable names of objects which take their 
type from the initial value.  But every object has one type for its 
entire life no matter how many assignments and/or view conversions are done.

Yes, again, you can play unsafe programming games that change an 
object's tag, and thus its type.  But such code is as implementation 
specific and unsafe as the name implies.  There are similar, but safer, 
tricks you can play using types with discriminated types.  But now you 
are using mechanisms which are designed to be implementation 
independent, and which for the most part are.  Some representation 
clause for such types may not be accepted by all compilers, but that is 
a portability issue not a safety issue.

Let me move from the abstract to a specific case.  Let's say I have a 
persistent data type, where the necessary data is read from a database, 
and if it is changed, it is written back.  Normally such a class will be 
written so that any types derived from the type will be initialized 
correctly, and in fact the author of an extension doesn't even need to 
be aware of the process used to do the initialization.

But what if there is a type extension which requires more data to be 
persistent.  The alternatives are for the new type to use a separate 
database, or to extend the entry in the existing database for objects of 
the new type.  These two approaches will require different code for the 
child's type Initialize procedure, and in one sense that is why there 
are several different common idioms.  If you use a separate database, 
then the parent initialize can be called last.  If the approach used is 
to have a combined database, the approach will usually be to never call 
the parent's version of Initialize.  However, there are some cases where 
you can call Initialize for the parent, then do additional reads for the 
child type.

What happens if the call to the parent Initialize fails?  If there is no 
local handler, an exception will propagate out of Initialize, and the 
scope containing the object will be exited without the object ever 
becoming fully initialized.  Sorry about that, but there really is no 
choice.  Again to get specific, assume that the disk that contains the 
database is off-line, or the DBMS is not running, or the user didn't 
provide the right database password on the command line or whatever.

If your design for Initialize is reasonable it will print a message that 
gives the user a hint about what is wrong, and then exit the program (or 
propagate the exception outward).  The problem occurs if you put a 
handler for the exception inside Initialize, and although you try to 
correct the problem, you fail--but don't re-raise the exception.

This is the failure case of interest apparently to Hyman Rosen, but 
which I fail to see as a language design issue.  You can just as easily 
initialize the object with garbage or put junk in an access value.  They 
are all implementation bugs that may only occur in the mind of the 
programmer.

Why do I say that?  Because without good specification, what the program 
is expected to do in that case is something that only exists in the mind 
of the programmer, and can only be checked by a mind reader, not a 
compiler.  If you do have a good specification, then you can use a tool 
like SPARK to map the specifications to the code, and mismatches become 
machine checkable.

You don't even have to use something as heavyweight as SPARK, if there 
is a question you can write checks using 'Valid at the end of your 
Initialize procedure.  In many cases, the compiler can determine at 
compile time that particular components of the object are always 
initialized, and optimize the calls away.  However, remember that we are 
not talking ordinary cases here.  This is only really appropriate in an 
error handler that otherwise would not do a re-raise.


-- 
                                           Robert I. Eachus

"The only thing necessary for the triumph of evil is for good men to do 
nothing." --Edmund Burke




^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-12 14:56                                                     ` Hyman Rosen
@ 2004-03-12 18:19                                                       ` Dmitry A. Kazakov
  2004-03-12 18:34                                                         ` Hyman Rosen
  0 siblings, 1 reply; 67+ messages in thread
From: Dmitry A. Kazakov @ 2004-03-12 18:19 UTC (permalink / raw)


Hyman Rosen wrote:

> Dmitry A. Kazakov wrote:
>> C++ does everything right and anything else is not Ada.
> 
> This has nothing to do with programming language wars,
> but perhaps with English language wars. When you state
> categorically "So when T is view-convertible to T'Class,
> then two constructors have to be applied: one of T, one
> of T'Class. In the constructor of T you cannot dispatch,
> in the constructor of T'Class you can do it safely." it
> certainly sounds like you are saying that this is what
> Ada does. But this is not what Ada does, it is what you
> would like Ada to do.

No it is how Ada does it. Here is an example:

package Foo is
   type A is new Ada.Finalization.Controlled with null record;
   procedure Initialize (Object : in out A);
   type In_B is new Ada.Finalization.Controlled with null record;
   procedure Initialize (Object : in out In_B);
   type B is new A with record
      Field : In_B;
   end record;
end Foo;

with Ada.Text_IO;  use Ada.Text_IO;
package body Foo is
   procedure Initialize (Object : in out A) is
   begin
      Put_Line ("Init A");
   end Initialize ;
   procedure Initialize (Object : in out In_B) is
   begin
      Put_Line ("Init field of B");
   end Initialize;
end Foo;

Initializing of an object of B will print:

Init field of B
Init A

showing that Initialize for A is called *after* initialization of Field of
B. It means that A's Initialize may safely use any field of B.

-- 
Regards,
Dmitry A. Kazakov
www.dmitry-kazakov.de



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-12 18:19                                                       ` Dmitry A. Kazakov
@ 2004-03-12 18:34                                                         ` Hyman Rosen
  2004-03-12 20:05                                                           ` Georg Bauhaus
  2004-03-13 10:12                                                           ` Dmitry A. Kazakov
  0 siblings, 2 replies; 67+ messages in thread
From: Hyman Rosen @ 2004-03-12 18:34 UTC (permalink / raw)


Dmitry A. Kazakov wrote:
> No it is how Ada does it. Here is an example:

WHere does your example involve constructing a T vs. constructing a T'Class?

> Initializing of an object of B will print:
> Init field of B
> Init A
> showing that Initialize for A is called *after* initialization of Field of
> B. It means that A's Initialize may safely use any field of B.

But it also means that initializing a field of B cannot safely use A!



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-12 18:34                                                         ` Hyman Rosen
@ 2004-03-12 20:05                                                           ` Georg Bauhaus
  2004-03-13 10:12                                                           ` Dmitry A. Kazakov
  1 sibling, 0 replies; 67+ messages in thread
From: Georg Bauhaus @ 2004-03-12 20:05 UTC (permalink / raw)


Hyman Rosen <hyrosen@mail.com> wrote:
: 
: But it also means that initializing a field of B cannot safely use A!

So it seems. Here is an example producing
Init field of B
hook has val -1073743060
Init B

when the main subprogram has a declaration of a variable of type
B but not of type A. When a variable of type A is added before
the B variable, the B variables initial hook val becomes 0.
If an A variable is declared after a B variable, the Natural
hook val stays at -1073743060. (GCC 3.1 Mac OS X)

with Ada.Finalization;  use Ada.Finalization;
package Foo is

   type Hook is tagged record  -- not part of hierarchy
      val: Natural;  -- should not have negative values
   end record;

   procedure hook_op(ref: access Hook);

   type A is new Controlled with record
      a_part: aliased Hook;
   end record;
   procedure Initialize(Object: in out A);

   type In_B is new Controlled with null record;
   procedure Initialize(object: in out In_B);

   type B is new A with record
      field: In_B;
   end record;
   procedure Initialize(Object: in out B);

end foo;


with Ada.Text_IO;  use Ada.Text_IO;
package body Foo is

   procedure hook_op(ref: access Hook) is
   begin
      put_line("hook has val " & Natural'image(ref.val));
   end hook_op;

   procedure Initialize (Object : in out A) is
   begin
      Put_Line ("Init A");
   end Initialize ;

   procedure Initialize (Object : in out B) is
   begin
      hook_op(Object.a_part'access);
      Put_Line ("Init B");
   end Initialize ;

   procedure Initialize (Object : in out In_B) is
   begin
      Put_Line ("Init field of B");
   end Initialize;
end Foo;





^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-11 13:55                                             ` Hyman Rosen
@ 2004-03-12 23:02                                               ` Robert I. Eachus
  2004-03-14 21:33                                                 ` Hyman Rosen
  2004-03-16  6:00                                               ` Randy Brukardt
  1 sibling, 1 reply; 67+ messages in thread
From: Robert I. Eachus @ 2004-03-12 23:02 UTC (permalink / raw)


Hyman Rosen wrote:

>> procedure Initalize(Object: in out Child) is
>> begin
>>   Do_Something(Object);
>>   Initialize(Parent(Object));
>> end Initialize; 
> 
> Hmm. The understanding in C++ is that in a constructor, all of the base
> constructors have completed, so all facilities of the base classes are
> available to be used in the derived class constructors. I'm not saying
> that this first idiom you describe is wrong for Ada, but it does seem to
> force derived classes into having very intimate knowledge of the needs
> of their bases. What if initializing the child fields requires making
> calls to base methods?

Actually this version is used when the extension doesn't need to know 
anything about the elements (and operations) of the parent, if you need 
to access the parent fields you need to use the second option:
> 
>> procedure Initialize(Object: in out Child) is
>> begin
>>   Object := (Parent_Type with ...);
>>   Do_Something_Else(Object);
>> end Initialize;
> 
> Nice. In C++ it is highly discouraged to implement constructors via
> assignment (because it is fragile and error-prone). If I'm not mistaken,
> this code will cause Finalize to be called on Object before the assignment
> takes place, which means that it's possible for Finalize to be called on
> an object before any Initialize for it has completed.

No.  Finalize MAY be called on the Parent_Type part of the value being 
generated (but after the copy into the real Object).  If the Parent_Type 
is Limited_Controlled, then the value should be built in place, and no 
finalization will occur.  The other case where you will get a Finalize 
before Object has been completely Initialized, if some part of one of 
the Initialize calls raises an unhandled exception.

> I really have to stop
> thinking of Initialize/Adjust/Finalize as being equivalent to C++'s
> constructor/assignment/destructor. It may just be that I haven't gotten my
> mind around it properly, but it seems to me that Ada OO is significantly
> harder to understand and explain than Java's or C++'s, even (or especially)
> for simple cases.

Very much agree.  The Ada model is much more complex, since there was a 
lot of effort put into allowing nesting of controlled objects.  For the 
normal user, though, the real trick is that you may have no user defined 
Initialize procedure.  You give the components of the controlled object 
default initial values and you are done.

A better way to explain it is to point out that during development, the 
focus was on finalization.  The initialization in Ada 83 was sufficient 
for most users.  So for 99% of tagged types, there are no user defined 
Initialize procedures--and it works just fine.  Adjust is typically only 
used to provide deep copy semantics.

>> So yes, there are areas where user written code can access an object 
>> that has not yet been completely initialized.  But in practice, as a 
>> user you will find that once you get rid of any bugs (and exceptions) 
>> inside Intialize, Adjust, and Finalize, that code written by others, 
>> including the authors of types derived from your types, cannot mess up 
>> the invariants of your type.
> 
> I agree. But that's why I said that DK was wrong. Our discussion has been
> about code that runs during object construction and destruction. That is,
> the process of getting rid of the bugs and exceptions that you mention.

And what DK and I have been saying is that there is no problem, because 
of the "two stage" initialization of Ada tagged types.  Compilers can 
shortcut the process of building an object, if the compiler can prove 
that it makes no difference.  But other reading such an object in the 
debugger, the only way to get an uninitialized or abnormal subcomponent 
in a tagged object is to do something in that corrupts the subcomponent, 
then handle the exception and throw it away.  (And as I keep saying, if 
you throw the execption away without fixing the problem, you are 
definitely in "here be dragons" territory.  But that is true no matter 
where you do that.)

> That's not what I was talking about. I was saying that in a parent
> Initialize, the object could be view-converted to its classwide type,
> and then a dispatching call to could be made on the converted object
> which would wind up calling an overridden method of the child's type.

How many times do I have to say it.  You can try to DO that, but writing 
working code that does that is very hard--not impossible.  But if and 
when you get that code to compile, surprise! the object is initialized 
correctly.

The problem, or more appropriately what prevents the problem, is the 
order of elaboration rules in Ada.  To get the problem case, you need to 
create an object of the child type before the body of the parent package 
is elaborated.  So you need not only for the body of the package that 
declares the parent type to reference the child type, you need to create 
an object of the parent type before the main program is elaborated. 
There will be cases where, if you try to do this, there would be a 
possible elaboration order which the Ada rules don't allow.  (Mix the 
elaboration of procedures from the two package bodies.)  You can use 
nested calls and additional procedures in a third unit to make it all 
work.  But as I said then it all works.

So "down casting" to a child type in the body of the package which 
declares the parent is not illegal as such.  But if they are controlled, 
and you have different explicit Intialize procedures for the two types, 
and you create objects before the main program executes, it is VERY hard 
to get the resulting code to link.

> Think of the template-method design pattern for example. (This would
> be in your idiom one code, but with the parent's Initialize called
> *before* the Do_Something(Object), because Do_Something requires that
> its base classes be completely set up.) So overridden methods could
> wind up being called on incompletely prepared objects. This can't
> happen in C++. There, the dispatching would only go to methods of the
> class whose constructor or destructor is running. If such a method is
> abstract, the implementation generally arranges for the program to
> abort.

Again, it is very easy and natural in most cases.  Even if you have two 
explicit Initialize procedures, which in Ada is rare, the only problem 
is if, as above, Initialize for the parent requires the body of the 
child type to be elaborated, and there are objects of the type created 
before the main program starts executing.  Doing all of this is very bad 
form.  Doing one or two of the three is not a problem.  But it is 
usually referencing the body of the child package from the body of the 
parent package that Ada programmers avoid.  In Ada, elaboration order 
issues are an emergent property of a program as a whole.  There are a 
few rules though that you can follow to avoid ever having elaboration 
order (or compile order problems) this is one of them.  If for some 
reason you have to break it, do so.  But it will usually be painful to 
get right.

-- 

                                          Robert I. Eachus

"The only thing necessary for the triumph of evil is for good men to do 
nothing." --Edmund Burke




^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-02 19:01 abstract sub programs overriding Evangelista Sami
  2004-03-03  1:43 ` Stephen Leake
  2004-03-03 12:00 ` Marius Amado Alves
@ 2004-03-13  7:51 ` Simon Wright
  2 siblings, 0 replies; 67+ messages in thread
From: Simon Wright @ 2004-03-13  7:51 UTC (permalink / raw)


evangeli@cnam.fr (Evangelista Sami) writes:

> -----------------------------
> type t1 is abstract tagged record ... end record;
> procedure proc(my_t : in t1) is abstract;
> 
> type t2 is abstract new t1 with record ... end record;
> procedure proc(my_t : in t2);
> 
> type t3 is new t2 with record ... end record;
> type access_t3 is access all t3;
> function new_t3 return access_t3;
> -----------------------------
> 
> function new_t3 only create a t3 object and return it. Now i have this
> procedure :
> 
> -----------------------------
> procedure generate is
>    access_t3 : t3 := new_t3;
> begin
>    proc(access_t3.all);
> end;
> -----------------------------
> 
> how is it that the "proc(access_t3.all);" call
> raise "CONSTRAINT_ERROR : generator.adb:93 access check failed"
> 
> i thought it would be correct since i have overriden proc for type t2
> and t3 inherits from t2. what's wrong?
> what would be the solution?

I have reported this problem to ACT, who have accepted it as a bug and
fixed it in the development sources. I don't know when it will be
available in a public release (I guess that now that means "in GCC").

-- 
Simon Wright                               100% Ada, no bugs.



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-12 18:34                                                         ` Hyman Rosen
  2004-03-12 20:05                                                           ` Georg Bauhaus
@ 2004-03-13 10:12                                                           ` Dmitry A. Kazakov
  1 sibling, 0 replies; 67+ messages in thread
From: Dmitry A. Kazakov @ 2004-03-13 10:12 UTC (permalink / raw)


Hyman Rosen wrote:

> Dmitry A. Kazakov wrote:
>> No it is how Ada does it. Here is an example:
> 
> WHere does your example involve constructing a T vs. constructing a
> T'Class?

Come on, that was a theory explaining and predicting the phenomenon = "how
it works in Ada" and "why it cannot in C++".

To prevent silly accusations, I don't claim that Ada designers followed that
theory, I only say that Ada design is conform with the theory.

>> Initializing of an object of B will print:
>> Init field of B
>> Init A
>> showing that Initialize for A is called *after* initialization of Field
>> of B. It means that A's Initialize may safely use any field of B.
> 
> But it also means that initializing a field of B cannot safely use A!

Right, because normally a *component* of B shall not know A. It is a
component of B, not of A (sic!). At most it may know B or B'Class and so
through these, A. This would mean a circular type dependecy recommended to
avoid in any software design tutorial. If you have such dependency (for
whatever reason (*), then you cannot complete initialization of the
component in its constructor. BTW, you have switched from inheritance (B is
derived from A) to aggreagation (In_B is a component of B). [I used In_B
only to print a message showing that controlled types are constructed in
two phases.] A design where one type uses another, being a field of its
derivant is an awful mixture of aggregation and inheritance in one flagon.
It is an instance of the aliasing problem, which is in general unsolvable,
as we all know.

----------
* Typical example is a task component that refers the enclosing object
through a discriminant. Here the multiple-stage initialization shows its
power. The task is activated after all calls to Initialize, so the
following is safe!

type A;
task type Runner (This : access A'Class);
type A is new Ada.Finalization.Limited_Controlled with record
   Do_It : Runner (A'Unchecked_Access);
end record;
procedure Initialize (Object : in out A);
   -- called before Object.Do_It starts

[Yet, I'd prefer MI and tagged task types, but that's another story]

-- 
Regards,
Dmitry A. Kazakov
www.dmitry-kazakov.de



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-12 23:02                                               ` Robert I. Eachus
@ 2004-03-14 21:33                                                 ` Hyman Rosen
  2004-03-15  5:59                                                   ` Robert I. Eachus
  0 siblings, 1 reply; 67+ messages in thread
From: Hyman Rosen @ 2004-03-14 21:33 UTC (permalink / raw)


Robert I. Eachus wrote:
> Hyman Rosen wrote:
>>> procedure Initialize(Object: in out Child) is
>>> begin
>>>   Object := (Parent_Type with ...);
>>>   Do_Something_Else(Object);
>>> end Initialize;
>>
>> If I'm not mistaken, this code will cause Finalize to be called on  Object
 >> before the assignment takes place, which means that it's possible for Finalize
 >> to be called on an object before any Initialize for it has completed.
> 
> No.

Why not? If Object is of a Controlled type, and this code is performing
an assignment to it, doesn't that cause Finalize to be called on the
object before the assignment takes place?

> And what DK and I have been saying is that there is no problem, because 
> of the "two stage" initialization of Ada tagged types.

OK, I'm going to leave this be for now because any examples I have
tried to devise seem terribly contrived. If I can come up with a
reasonable scenario, I'll post it.

> So "down casting" to a child type in the body of the package which 
> declares the parent is not illegal as such.

Note that I have never suggested downcasting to a child type.
I have just suggested view converting to the parent's classwide type,
and then calling a dispatching routine on this classwide type.
I certainly don't know enough about Ada's elaboration order rules to
know whether they would prevent the scenario I'm thinking of.
I accept that the whole thing is unlikely.



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-14 21:33                                                 ` Hyman Rosen
@ 2004-03-15  5:59                                                   ` Robert I. Eachus
  2004-03-15 14:39                                                     ` Hyman Rosen
  0 siblings, 1 reply; 67+ messages in thread
From: Robert I. Eachus @ 2004-03-15  5:59 UTC (permalink / raw)


Hyman Rosen wrote:

> Why not? If Object is of a Controlled type, and this code is performing
> an assignment to it, doesn't that cause Finalize to be called on the
> object before the assignment takes place?

If you look at what I was talking about, what would happen is that 
Finalize for the parent type would be called--after it had been 
Initialized.  The Initialize for the child type won't have completed 
(you are still inside it), but that is not a problem since Finalize for 
the child type won't be called.

It all gets pretty complex if you have to "follow the bouncing ball" for 
some reason.  But in practice you never have to worry about it.  Unless 
of course, you are a language lawyer playing games. ;-)

>> So "down casting" to a child type in the body of the package which 
>> declares the parent is not illegal as such.
> 
> 
> Note that I have never suggested downcasting to a child type.
> I have just suggested view converting to the parent's classwide type,
> and then calling a dispatching routine on this classwide type.

Ah, if you do that there is no issue to think about.  The subprogram 
that will be called is the same one that would have been called if you 
hadn't made the call classwide.  All that you have insured is that the 
dispatching will occur dynamically.

However if the Initialize for the parent type is called by the 
Initialize for the child type, and you do this, it is the child's 
subprogram that will be called.  (The dispatching will look at the 
actual tag, not at the designated type.)  And as I said, don't do that. 
  It can be made to work, but in general it is not what you want, and 
the extra work at compile time (or during debugging) to resolve ABE 
(access before elaboration) problems just isn't worth it.

In general the only time you want/need to make an explicit classwide 
call inside another dispatching operation is when you are doing multiple 
dispatching.  And that is another can of worms.  (I have done it.  In 
fact, for most PL/I compilers the any-to-any default routines are 
written that way.  The problem is completely language independent.  For 
example, the Multics PL/I compiler had 17 different types which had to 
be handled by the any-to-any operators.  In theory there are 4913 cases 
to be dealt with.  In practice there were only about 150 special cases, 
but that was at least 100 too many.)

> I certainly don't know enough about Ada's elaboration order rules to
> know whether they would prevent the scenario I'm thinking of.
> I accept that the whole thing is unlikely.

Oh, they'll get ya.  The point I keep trying to make is that only 
language lawyers and compiler vendors should worry about those cases. 
And even then they don't get paid well enough. ;-)

When I am not writing compiler or ACATS tests, I never have to worry 
about order of elaboration.  But I have also written code that sails 
very close to the edge. I can assure you that the frustrating thing 
about elaboration order problems is that they are an emergent property. 
    The unit the compiler fails to compile, or the routine that raises 
Program_Error at run-time, is almost always not the one where the 
problem is.


-- 
                                           Robert I. Eachus

"The only thing necessary for the triumph of evil is for good men to do 
nothing." --Edmund Burke




^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-15  5:59                                                   ` Robert I. Eachus
@ 2004-03-15 14:39                                                     ` Hyman Rosen
  2004-03-16 16:16                                                       ` Robert I. Eachus
  0 siblings, 1 reply; 67+ messages in thread
From: Hyman Rosen @ 2004-03-15 14:39 UTC (permalink / raw)


Robert I. Eachus wrote:
> If you look at what I was talking about

I think I still don't understand. If you say 'a := b;' and a has a
Controlled type, isn't Finalize called on a before its bits are
clobbered? So in your Initilaize example, when you say
'Object := (Parent_Type with ...);', isn't Finalize called on Object
before its bits are replaced with the extension aggregate? And since
we're in the first Initialize of Object, doesn't that mean that Finalize
will be called on it before its first Initialize is complete?

> However if the Initialize for the parent type is called by the 
> Initialize for the child type, and you do this, it is the child's 
> subprogram that will be called.  (The dispatching will look at the 
> actual tag, not at the designated type.)  And as I said, don't do that. 
>  It can be made to work, but in general it is not what you want, and the 
> extra work at compile time (or during debugging) to resolve ABE (access 
> before elaboration) problems just isn't worth it.

Yes, exactly! This is what I have been getting at all along. And what I've
also been saying is that C++ enforces the "don't do that" by dispatching to
the parent type instead of to the child type in constructors. It does that
by dynamically altering the tag (in Ada terms) of the object during the
construction and destruction process. (C++ is in a more complex situation
than Ada here becuase of multiple and virtual inheritance cases.) But in
doing so, it rubs DK the wrong way, because his philosophy requires objects
to never change their type dynamically.

> Oh, they'll get ya.  The point I keep trying to make is that only 
> language lawyers and compiler vendors should worry about those cases.

Sure, but who else hangs out on c.l.a.? :-)



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-11 13:55                                             ` Hyman Rosen
  2004-03-12 23:02                                               ` Robert I. Eachus
@ 2004-03-16  6:00                                               ` Randy Brukardt
  1 sibling, 0 replies; 67+ messages in thread
From: Randy Brukardt @ 2004-03-16  6:00 UTC (permalink / raw)


"Hyman Rosen" <hyrosen@mail.com> wrote in message
news:1079013337.572283@master.nyc.kbcfp.com...
> Robert I. Eachus wrote:
> > No, as I said, there are two common idioms, the first is to call the
> > parent Initialize AFTER doing any initialization work needed on the
> > fields that occur only in the child.
> >
> > procedure Initalize(Object: in out Child) is
> > begin
> >   Do_Something(Object);
> >   Initialize(Parent(Object));
> > end Initialize;
...

I have no idea what Robert is talking about here. The "common idiom" that I
use is:

procedure Initalize(Object: in out Child) is
begin
   Initialize(Parent(Object));
   Do_Something(Object);
end Initialize;

On the other issue, it's clear that a class-wide call in Initialize can be
forced to dispatch to a routine which can see uninitialized components. But
such a dispatch (which you always have to write explicitly) is a bug in
Initialize/Adjust/Finalize. You *never* want dispatching on the operative
object in the implementation of these routines, and doing such is a mistake.
It's true that Ada doesn't protect you from this mistake, but its one that
you have to work at pretty hard to make.

It's unfortunate that Ada doesn't have a sane way to call the parent
operation (using a type conversion to a specific type is ugly and
potentially a source of error if another type is inserted into the
hierarchy), because that is the most likely way to insert a dispatching call
by accident. DK, seems to want to outlaw redispatching altogether (which is
too fierce to me), but such a rule would eliminate any possibility of
accessing uninitialized components in these routines.

(Of course, in practice, default initialization of the components does most
of the work, so it's fairly rare that Initialize does anything important at
all. Which reduces the consequences of the [unlikely] error to very little
anyway.)

                     Randy.








^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-15 14:39                                                     ` Hyman Rosen
@ 2004-03-16 16:16                                                       ` Robert I. Eachus
  2004-03-16 16:51                                                         ` Hyman Rosen
                                                                           ` (2 more replies)
  0 siblings, 3 replies; 67+ messages in thread
From: Robert I. Eachus @ 2004-03-16 16:16 UTC (permalink / raw)


Hyman Rosen wrote:

> I think I still don't understand. If you say 'a := b;' and a has a
> Controlled type, isn't Finalize called on a before its bits are
> clobbered? So in your Initilaize example, when you say
> 'Object := (Parent_Type with ...);', isn't Finalize called on Object
> before its bits are replaced with the extension aggregate? And since
> we're in the first Initialize of Object, doesn't that mean that Finalize
> will be called on it before its first Initialize is complete?

Interesting thought.  RM 7.6 (17, 17.1, and 21) permit an implementation 
to omit that Finalization, and requires that it be omitted in some 
cases.  But you are right that a compiler could actually do that call in 
some circumstances.

You could write up an example and sent it to Ada Comment.  I can't 
imagine it being treated as high priority unless there are compilers 
that actually do the finalization.  Dimitry's two-stage initialization 
should mean that the unnecessary Finalize call is harmless, but there 
should probably be an AI to allow/require compilers to omit it in all 
cases.  (The problem as I see it is that Initialize should really have 
an out parameter instead of an in out parameter.)


-- 
                                           Robert I. Eachus

"The only thing necessary for the triumph of evil is for good men to do 
nothing." --Edmund Burke




^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-16 16:16                                                       ` Robert I. Eachus
@ 2004-03-16 16:51                                                         ` Hyman Rosen
  2004-03-16 19:54                                                         ` Hyman Rosen
  2004-03-16 23:14                                                         ` Randy Brukardt
  2 siblings, 0 replies; 67+ messages in thread
From: Hyman Rosen @ 2004-03-16 16:51 UTC (permalink / raw)


Robert I. Eachus wrote:
> You could write up an example and sent it to Ada Comment.

I wouldn't dare :-) I don't even see it as a bug, just another
aspect of the way Ada Controlled types work. I think that if it
matters, the way to deal with it is to keep a flag in the object
that tells you what state it's in.



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-16 16:16                                                       ` Robert I. Eachus
  2004-03-16 16:51                                                         ` Hyman Rosen
@ 2004-03-16 19:54                                                         ` Hyman Rosen
  2004-03-16 23:16                                                           ` Randy Brukardt
  2004-03-17  1:54                                                           ` Robert I. Eachus
  2004-03-16 23:14                                                         ` Randy Brukardt
  2 siblings, 2 replies; 67+ messages in thread
From: Hyman Rosen @ 2004-03-16 19:54 UTC (permalink / raw)


Robert I. Eachus wrote:
> Interesting thought.  RM 7.6 (17, 17.1, and 21) permit an implementation 
> to omit that Finalization, and requires that it be omitted in some 
> cases.  But you are right that a compiler could actually do that call in 
> some circumstances.

I don't think compilers can eliminate that Finalize call. 7.6/17 says,
without listing any exceptions, "The target of the assignment_statement
is then finalized." All the other permissions deal with the temporary
anonymous object that the canonical semantics create.



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-16 16:16                                                       ` Robert I. Eachus
  2004-03-16 16:51                                                         ` Hyman Rosen
  2004-03-16 19:54                                                         ` Hyman Rosen
@ 2004-03-16 23:14                                                         ` Randy Brukardt
  2004-03-17  2:43                                                           ` Robert I. Eachus
  2 siblings, 1 reply; 67+ messages in thread
From: Randy Brukardt @ 2004-03-16 23:14 UTC (permalink / raw)


"Robert I. Eachus" <rieachus@comcast.net> wrote in message
news:N_ydnXrG6NPEtcrdRVn-hg@comcast.com...
> Hyman Rosen wrote:
>
> > I think I still don't understand. If you say 'a := b;' and a has a
> > Controlled type, isn't Finalize called on a before its bits are
> > clobbered? So in your Initilaize example, when you say
> > 'Object := (Parent_Type with ...);', isn't Finalize called on Object
> > before its bits are replaced with the extension aggregate? And since
> > we're in the first Initialize of Object, doesn't that mean that Finalize
> > will be called on it before its first Initialize is complete?
>
> Interesting thought.  RM 7.6 (17, 17.1, and 21) permit an implementation
> to omit that Finalization, and requires that it be omitted in some
> cases.  But you are right that a compiler could actually do that call in
> some circumstances.

I don't see why you think that. 'Object' in this case is not a new object;
it is a parameter passed into Initialize, and the compiler certainly cannot
assume that it is uninitialized.  In any case, RM 7.6(21) is seriously
screwed up; AI-147 changes it quite a bit, mainly to eliminate bogus
'optimizations' like the one you seem to think is allowed here. (Eliminating
this Finalize call would make a library like Claw impossible to write,
because you could have objects that are never finalized and thus never
unhooked from the lists to which they belong.)

                Randy.






^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-16 19:54                                                         ` Hyman Rosen
@ 2004-03-16 23:16                                                           ` Randy Brukardt
  2004-03-17  1:54                                                           ` Robert I. Eachus
  1 sibling, 0 replies; 67+ messages in thread
From: Randy Brukardt @ 2004-03-16 23:16 UTC (permalink / raw)


"Hyman Rosen" <hyrosen@mail.com> wrote in message
news:1079466915.690786@master.nyc.kbcfp.com...
> Robert I. Eachus wrote:
> > Interesting thought.  RM 7.6 (17, 17.1, and 21) permit an implementation
> > to omit that Finalization, and requires that it be omitted in some
> > cases.  But you are right that a compiler could actually do that call in
> > some circumstances.
>
> I don't think compilers can eliminate that Finalize call. 7.6/17 says,
> without listing any exceptions, "The target of the assignment_statement
> is then finalized." All the other permissions deal with the temporary
> anonymous object that the canonical semantics create.

Right. 7.6(21) allows lots of bogus 'optimizations', but this has been
corrected by AI-147. That AI was a beast to get right, and I certainly don't
want to have to go back there...

                 Randy.






^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-16 19:54                                                         ` Hyman Rosen
  2004-03-16 23:16                                                           ` Randy Brukardt
@ 2004-03-17  1:54                                                           ` Robert I. Eachus
  1 sibling, 0 replies; 67+ messages in thread
From: Robert I. Eachus @ 2004-03-17  1:54 UTC (permalink / raw)




Hyman Rosen wrote:

> I don't think compilers can eliminate that Finalize call. 7.6/17 says,
> without listing any exceptions, "The target of the assignment_statement
> is then finalized." All the other permissions deal with the temporary
> anonymous object that the canonical semantics create.

Look at the last sentence, "As explained below, the implementation may 
eliminate the intermediate anonymous object..." then at 7.6(21): "For an 
aggregate or function call whose value is assigned into a target object, 
the implementation need not create a separate anonymous object if it can 
safely create the value of the aggregate or function call directly in 
the target object."

In the RM, then has a very specific meaning.  If the action before the 
then is not taken, then neither is the action following the then.  In 
this case, anything else would be silly, since finalizing the target 
after constructing the new value in it would be a disaster.

Of course, there are many cases where you can't eliminate the 
finalization, and that is covered by "...if it can safely create..." 
Certainly not finalizing an uninitialized object is safe.  (Or at least 
there is no guarantee that such objects are finalized.)

-- 

                                           Robert I. Eachus

"The only thing necessary for the triumph of evil is for good men to do 
nothing." --Edmund Burke




^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-16 23:14                                                         ` Randy Brukardt
@ 2004-03-17  2:43                                                           ` Robert I. Eachus
  2004-03-17 17:40                                                             ` Randy Brukardt
  0 siblings, 1 reply; 67+ messages in thread
From: Robert I. Eachus @ 2004-03-17  2:43 UTC (permalink / raw)


Randy Brukardt wrote:

> I don't see why you think that. 'Object' in this case is not a new object;
> it is a parameter passed into Initialize, and the compiler certainly cannot
> assume that it is uninitialized.  In any case, RM 7.6(21) is seriously
> screwed up; AI-147 changes it quite a bit, mainly to eliminate bogus
> 'optimizations' like the one you seem to think is allowed here. (Eliminating
> this Finalize call would make a library like Claw impossible to write,
> because you could have objects that are never finalized and thus never
> unhooked from the lists to which they belong.)

I was thinking of cases where Initialize is called implicitly, and the 
compiler can determine that Object is never referenced before it is 
overwriten. In that case, of course, the compiler can eliminate an 
implicit Finalize.  This shouldn't break your code.

But this could/should only happen when the an object is being created 
and assigned a new value by the implicit call to Initialize, with no 
reference to its previous value.  If an existing object is passed to 
Initialize explicitly, in this case the previous value would have to be 
finalized.

Notice that, in this particular case, the value not finalized would also 
not have any default components initialized.  I was suggesting to Hyman 
that he might want to try to find a case where compilers DO finalize a 
value that does not have default component values assigned.  As I 
understand AI-147, it allows some calls to user defined Finalize 
operations to be eliminated, but not Intialize and Finalize pairs where 
the Initialize is user defined.  Eliminating a Finalize call on an 
object that has not been initialized seems legal to me.

This can only happen AFAIK, in these cases where the object has an 
explicit  initial value, and the Finalize call occurs before the 
assignment of the initial value.  I can't imagine any compiler 
intentionally doing that Finalize call, in the ordinary case.  However, 
I can see that it could occur if the object is assigned a value by an 
aggregate assignment inside a user-defined initialize.

-- 

                                           Robert I. Eachus

"The only thing necessary for the triumph of evil is for good men to do 
nothing." --Edmund Burke




^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-17  2:43                                                           ` Robert I. Eachus
@ 2004-03-17 17:40                                                             ` Randy Brukardt
  2004-03-18  2:39                                                               ` Robert I. Eachus
  0 siblings, 1 reply; 67+ messages in thread
From: Randy Brukardt @ 2004-03-17 17:40 UTC (permalink / raw)


"Robert I. Eachus" <rieachus@comcast.net> wrote in message
news:zYydnWeqCtXdJsrdRVn-hQ@comcast.com...
> Randy Brukardt wrote:
>
> > I don't see why you think that. 'Object' in this case is not a new
object;
> > it is a parameter passed into Initialize, and the compiler certainly
cannot
> > assume that it is uninitialized.  In any case, RM 7.6(21) is seriously
> > screwed up; AI-147 changes it quite a bit, mainly to eliminate bogus
> > 'optimizations' like the one you seem to think is allowed here.
(Eliminating
> > this Finalize call would make a library like Claw impossible to write,
> > because you could have objects that are never finalized and thus never
> > unhooked from the lists to which they belong.)
>
> I was thinking of cases where Initialize is called implicitly, and the
> compiler can determine that Object is never referenced before it is
> overwriten. In that case, of course, the compiler can eliminate an
> implicit Finalize.  This shouldn't break your code.

OK, but that only works when the (user-defined) Initialize is in-lined. (I
doubt compilers are both recognizing user-defined Initialize as special, and
generating two bodies for it!)

> Notice that, in this particular case, the value not finalized would also
> not have any default components initialized.

I suppose that's possible, but only if user-defined Initialize is in-lined.
I don't know if any compilers actually do that.

> I was suggesting to Hyman
> that he might want to try to find a case where compilers DO finalize a
> value that does not have default component values assigned.

I don't think that can happen. Which is why Ada controlled objects usually
contain a "Valid" flag or the like.

> As I understand AI-147, it allows some calls to user defined Finalize
> operations to be eliminated, but not Intialize and Finalize pairs where
> the Initialize is user defined.  Eliminating a Finalize call on an
> object that has not been initialized seems legal to me.

Yes, but there is no such object in a user-defined Initialize call. It is
default-initialized before it is passed in. If the compiler actually
in-lined the call, it could eliminate the default-initialization and the
Finalize call -- except that there is no permission in the standard (as
amended by AI-147) to do either, and either operation could have detectable
side-effects. So I wonder if such an optimization is even allowed.

> This can only happen AFAIK, in these cases where the object has an
> explicit  initial value, and the Finalize call occurs before the
> assignment of the initial value.

If the object has an explicit initial value, Initialize is never called. And
if that initial value is an aggregate, it has to be built directly in the
object (and none of Finalize, Adjust, or Initialize are called). That's not
the case we're talking about.

> I can't imagine any compiler
> intentionally doing that Finalize call, in the ordinary case.

That would be wrong, so I hope no compiler is doing that! The Finalize call
of the target only occurs for assignment statements, not for other
assignment operations.

> However,
> I can see that it could occur if the object is assigned a value by an
> aggregate assignment inside a user-defined initialize.

It *has to* occur in that case, which was Hyman's point. I don't think it
can be optimized out.

             Randy.






^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-17 17:40                                                             ` Randy Brukardt
@ 2004-03-18  2:39                                                               ` Robert I. Eachus
  2004-03-18  5:57                                                                 ` Randy Brukardt
  0 siblings, 1 reply; 67+ messages in thread
From: Robert I. Eachus @ 2004-03-18  2:39 UTC (permalink / raw)


Randy Brukardt wrote:

>>I was thinking of cases where Initialize is called implicitly, and the
>>compiler can determine that Object is never referenced before it is
>>overwriten. In that case, of course, the compiler can eliminate an
>>implicit Finalize.  This shouldn't break your code.
>  
> OK, but that only works when the (user-defined) Initialize is in-lined. (I
> doubt compilers are both recognizing user-defined Initialize as special, and
> generating two bodies for it!)

I agree here.  My point was that it may be legal, but it is unlikely 
that any compiler does it.

>>Notice that, in this particular case, the value not finalized would also
>>not have any default components initialized.
> 
> I suppose that's possible, but only if user-defined Initialize is in-lined.
> I don't know if any compilers actually do that.

Again, agree, especially on the last line.

>>I was suggesting to Hyman
>>that he might want to try to find a case where compilers DO finalize a
>>value that does not have default component values assigned. 
> 
> I don't think that can happen. Which is why Ada controlled objects usually
> contain a "Valid" flag or the like.

The Valid flag (or checking if an access value is non-null which is what 
I sometimes do) is not relevant here.  My original point was that 
although the language rules seem to imply that there is a loophole, any 
compiler is going to have to do a lot of work--to be able to do more 
work proving that there are no external effects of the optimizations.

>>As I understand AI-147, it allows some calls to user defined Finalize
>>operations to be eliminated, but not Intialize and Finalize pairs where
>>the Initialize is user defined.  Eliminating a Finalize call on an
>>object that has not been initialized seems legal to me.
> 
> Yes, but there is no such object in a user-defined Initialize call. It is
> default-initialized before it is passed in. If the compiler actually
> in-lined the call, it could eliminate the default-initialization and the
> Finalize call -- except that there is no permission in the standard (as
> amended by AI-147) to do either, and either operation could have detectable
> side-effects. So I wonder if such an optimization is even allowed.

As I was writing this originally, I thought about the curse in Ruddigore 
and "Yesterday upon the stair I met a man who wasn't there. He wasn't 
there again today. I wish that man would go away." -- Hughes Mearns 
(1875-1965)

I don't wonder if the optimization is allowed, I wonder if there are 
inifinitely many uninitialized objects that are also not finalized, or 
if the number is somehow finite.  In any case it seems clear to me that 
an object that doesn't get far enough along the track toward existing as 
to begin default initialization does not need to be finalized.  The 
interesting questions concern partially and fully initialized objects.

>>This can only happen AFAIK, in these cases where the object has an
>>explicit  initial value, and the Finalize call occurs before the
>>assignment of the initial value. 
> 
> If the object has an explicit initial value, Initialize is never called. And
> if that initial value is an aggregate, it has to be built directly in the
> object (and none of Finalize, Adjust, or Initialize are called). That's not
> the case we're talking about.

Whoops, that one sent me back to the reference manual.  First, an 
aggregate of a controlled type always results in a call to some 
Initialize, except perhaps inside of Ada.Finalization where you can use 
a regular aggregate instead of an extension aggregate.  An interesting 
bounding case that has come up in this discussion, but AFAIK is not of 
interest here.  In the second sentence, I think you are thinking of RM 
7.6(17.1) which was added in the 2000 revision.  But that requirement 
does not apply to aggregates in assignment statements, and you are right 
that is not the case we are talking about here.

This is the case where an exception occurs during the evaluation of the 
aggregate, in particular in the call to Initialize there.  And this is 
where we get into whether partially intialized objects are finalized. 
As I read the rules and understand them, the answer is that to some 
extent partially initialized objects may have components that have been 
fully initialized, these must be finalized. We are back to that other 
comment you made.

What you as an author want is that any object that is correctly 
initialized will be finalized, that any object which is not initialized 
will not be finalized, and there are no objects in the middle.  This is 
why I like to use an extension aggregate as the first line of an 
Initialize procedure.  If there is an exception raised, Adjust doesn't 
get called, and I don't have to worry about cascading exceptions.  Once 
the aggregate assignment succeeds I have an object which is not 
abnormal, in either the Ada technical sense or the practical sense.


>>However,
>>I can see that it could occur if the object is assigned a value by an
>>aggregate assignment inside a user-defined initialize.
>  
> It *has to* occur in that case, which was Hyman's point. I don't think it
> can be optimized out.

And I think it can be--but it won't be.  This is one of those complex 
areas where the permissions in the RM envision an implementation 
strategy, and no one knows if there is any other way for a compiler to 
comply with the standard.  But there are things which are not required 
by the language, but even trying to imagine an implementation trying to 
take advantage of the opportunity to get into trouble is just not going 
to happen.

For example in early days (I think it persisted into Ada82) there were 
legal Ada source strings where the separation into tokens required 
semantic information.  Of course, if an implementation knew that there 
were no single character attributes, there was no real problem.  Then 
the scanner/lexer could assume that 'A' was never an attribute followed 
by an apostrophe, so it was either a character literal or an error.  By 
the time Ada 83 was approved, we had a way to do the scanning of very 
complex lines without trouble, but still no compiler, AFAIK has ever 
defined a single character attribute.

In this case the wording of 7.6(21) would allow some improper 
optimizations except for the phrase "...if it can safely create...", so 
every implementation only does this optimization in a way that is 
semantically equivalent to the canonical approach.  As all this 
discussion shows, there are cases where the rules lead into grey areas, 
but in practice there is no grey.  As I mentioned above, an 
implmentation is allowed to have regular aggregates in Ada.Finalization, 
but aside from that, all values of a controlled type must have been 
initialized.


-- 
                                           Robert I. Eachus

"The only thing necessary for the triumph of evil is for good men to do 
nothing." --Edmund Burke




^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-18  2:39                                                               ` Robert I. Eachus
@ 2004-03-18  5:57                                                                 ` Randy Brukardt
  2004-03-18 15:03                                                                   ` Hyman Rosen
  0 siblings, 1 reply; 67+ messages in thread
From: Randy Brukardt @ 2004-03-18  5:57 UTC (permalink / raw)


"Robert I. Eachus" <rieachus@comcast.net> wrote in message
news:3vydndmUiYRplsTdRVn-sQ@comcast.com...
> > If the object has an explicit initial value, Initialize is never called.
And
> > if that initial value is an aggregate, it has to be built directly in
the
> > object (and none of Finalize, Adjust, or Initialize are called). That's
not
> > the case we're talking about.
>
> Whoops, that one sent me back to the reference manual.  First, an
> aggregate of a controlled type always results in a call to some
> Initialize, except perhaps inside of Ada.Finalization where you can use
> a regular aggregate instead of an extension aggregate.

I'm not in general interested in other type's Initialize/Adjust/Finalize --
I presume the language is defined properly to prevent problems with those.
The only exception is the parent, and my personal rule is to always call
that operation first in order to avoid any problems with uninitialized
stuff. It's annoying that it has to be done explicitly, but real programs
need the flexibility (or so I'm told).

In most interesting cases, the extension aggregate parent part's Initialize
is the predefined one, which does nothing, and is indistinguishable from no
call at all. (I suspect that most compilers don't generate statically bound
calls to predefined Initialize.)

> In the second sentence, I think you are thinking of RM
> 7.6(17.1) which was added in the 2000 revision.  But that requirement
> does not apply to aggregates in assignment statements, and you are right
> that is not the case we are talking about here.

> This is the case where an exception occurs during the evaluation of the
> aggregate, in particular in the call to Initialize there.  And this is
> where we get into whether partially intialized objects are finalized.
> As I read the rules and understand them, the answer is that to some
> extent partially initialized objects may have components that have been
> fully initialized, these must be finalized. We are back to that other
> comment you made.

I'm pretty sure that there was an AI on that topic. Let me look...yup,
AI-193 (also in the Corrigendum). This AI confirms the language of the
standard for the case in question: if Initialize propagates an exception,
the (whole) object is not finalized. (Of course, controlled parts that
completed initialization will be finalized). That's about what you said.

Of course, it's usually a mistake to let any of the controlled routines
propagate an exception.

> What you as an author want is that any object that is correctly
> initialized will be finalized, that any object which is not initialized
> will not be finalized, and there are no objects in the middle.

Right, but that's a tough property to guarentee, particularly if you want to
to be reasonably secure from abort and the like. I think it is best to
depend primarily of a valid flag(s) of some sort (as you mentioned, a null
pointer works fine, Claw uses that in some cases). And each extension needs
its own flag if it has any state of its own

> This is why I like to use an extension aggregate as the first line of an
> Initialize procedure.  If there is an exception raised, Adjust doesn't
> get called, and I don't have to worry about cascading exceptions.  Once
> the aggregate assignment succeeds I have an object which is not
> abnormal, in either the Ada technical sense or the practical sense.

Fair enough. Of course, that's equivalent to what I wrote

    Initialize(Parent(Object));
    <<Set my own components>>

except that there are no extra Finalize and Adjust calls to confuse things.
(I'm assuming that I'm smart enough to avoid exceptions in setting my
components. If not, that's simply a bug, and I don't much care what happens
when a bug occurs -- it needs to be fixed.

> >>However,
> >>I can see that it could occur if the object is assigned a value by an
> >>aggregate assignment inside a user-defined initialize.
> >
> > It *has to* occur in that case, which was Hyman's point. I don't think
it
> > can be optimized out.
>
> And I think it can be--but it won't be.

I don't see any way that it can be. This is an explicit case of
finalization; it is not the finalization of leaving a master that the
majority of the rules in 7.6.1 are about. The only way that it could be
optimized out is by inspecting the body of the routine to see if it does
anything. But that is always a possible optimization, and is not interesting
(just as generic sharing that requires looking at the body is not
interesting).

...
> In this case the wording of 7.6(21) would allow some improper
> optimizations except for the phrase "...if it can safely create...", so
> every implementation only does this optimization in a way that is
> semantically equivalent to the canonical approach.

The (original) wording of 7.6(21) allowed many improper optimizations, and
implementers were doing some of them. (Adjusting one object, copying the
bits, and then finalizing another object. Nasty if you're storing a pointer
to the object somewhere...) That's why it was rewritten. "safely" is a noise
word in this paragraph; it has no technical meaning, and certainly doesn't
require semantic equivalence to the canonical approach. (Such a requirement
would be the same as saying that no optimization at all is allowed, because
you certainly can tell on which objects Adjust and Finalize are called.)

> As all this discussion shows, there are cases where the rules lead into
grey areas,
> but in practice there is no grey.

Well, there was a lot of it with certain compilers in the 1996-98 timeframe.
I think that is "in practice". I think its better now (everyone conforms to
the rewritten 7.6(21) so far as I know), but that was not true for a long
time.

> As I mentioned above, an
> implmentation is allowed to have regular aggregates in Ada.Finalization,
> but aside from that, all values of a controlled type must have been
initialized.

I don't see any relevancy of that statement at all. It's only useful to
reason about a single type's Initialize/Adjust/Finalize; anything further
leads directly to madness. Unless you're language lawyering to try to invent
a case where the language doesn't properly handle composition.

The only case where there's trouble is when you derive from a type with a
meaningful Initialize/Adjust/Finalize routines. Those routines are not going
to be called automatically, and the programmer had better take care of that,
because the parent object may not be initialized as it expects. (Similarly
for Finalize, of course.) If the parent type's definition of "properly
initialized" requires Initialize to do something, it's trivial for that to
be omitted.

Your use of extension aggregates does avoid that problem, but it adds extra
calls to Finalize and Adjust. The Finalize probably will not do anything
(presuming there is a validity flag), but the Adjust will be the full blown
deep copy. Moreover, that will be followed by a Finalize of the temporary
that the aggregate was built in (essentially the undoing of the deep copy).
For Claw, those routines are nearly 500 lines each, and that's not something
that you want to be invoking during default initialization. And Hyman's
point was simply that these extra calls happened. (For someone who claims to
not know Ada very well, Hyman knows Ada very well. He should join the ARG.
:-) We could use him.)

                             Randy.






^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-18  5:57                                                                 ` Randy Brukardt
@ 2004-03-18 15:03                                                                   ` Hyman Rosen
  2004-03-18 20:32                                                                     ` Randy Brukardt
  0 siblings, 1 reply; 67+ messages in thread
From: Hyman Rosen @ 2004-03-18 15:03 UTC (permalink / raw)


Randy Brukardt wrote:
>>What you as an author want is that any object that is correctly
>>initialized will be finalized, that any object which is not initialized
>>will not be finalized, and there are no objects in the middle.
> 
> Right, but that's a tough property to guarentee, particularly if you want to
> to be reasonably secure from abort and the like.

C++ does its best to guarantee this, but will abort the program if,
during the automatic destruction of objects as a result of a thrown
exception, another exception propagates out of one of these destructors.
There's only so much you can do in cases like that. Even Ada raises a
program error in such cases.

But if you write your destructors so that exceptions can never escape
(which is the recommended style, because throwing an exception out of
a function "means" that the function cannot fulfill its duty, whereas
a destructor has no choice in the matter, since the object is being
destroyed regardless of what the destructor thinks about it), then C++
provides that guarantee - objects are constructed once and any object
which is constructed will be destructed. (Barring manual intervention,
of course.)

 > I think it is best to depend primarily of a valid flag

The problem with a valid flag is that it pushes the responsibility for
validity testing out to all the methods. One of the goals of having a
constructor in OO was to localize object construction in one place, so
every other method could assume that the object was in a consistent and
valid state.

> (For someone who claims to not know Ada very well, Hyman knows Ada very
 > well. He should join the ARG. :-) We could use him.)

:-) It's mostly that I understand in pretty excruciating detail how
things work in C++ (and in Java, to some extent), and the reasons for
those behaviors. When I see that Ada does something differently, I can
try to figure out whether those reasons will have an effect that C++
was designed to avoid. (Note that I'm not claiming C++ is better, I'm
just comparing effects of different decisions.)



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-18 15:03                                                                   ` Hyman Rosen
@ 2004-03-18 20:32                                                                     ` Randy Brukardt
  2004-03-19  3:59                                                                       ` Hyman Rosen
  0 siblings, 1 reply; 67+ messages in thread
From: Randy Brukardt @ 2004-03-18 20:32 UTC (permalink / raw)


"Hyman Rosen" <hyrosen@mail.com> wrote in message
news:1079622255.263633@master.nyc.kbcfp.com...
> Randy Brukardt wrote:
> >>What you as an author want is that any object that is correctly
> >>initialized will be finalized, that any object which is not initialized
> >>will not be finalized, and there are no objects in the middle.
> >
> > Right, but that's a tough property to guarentee, particularly if you
want to
> > to be reasonably secure from abort and the like.
>
> C++ does its best to guarantee this, but will abort the program if,
> during the automatic destruction of objects as a result of a thrown
> exception, another exception propagates out of one of these destructors.
> There's only so much you can do in cases like that. Even Ada raises a
> program error in such cases.

I was thinking about task abort, which can leave stuff in pretty much any
state. You can protect against exceptions, but you can't protect against
abort. (With two important exceptions: protected operations and
Initialize/Adjust/Finalize routines are abort-deferred.)

> But if you write your destructors so that exceptions can never escape
> (which is the recommended style, because throwing an exception out of
> a function "means" that the function cannot fulfill its duty, whereas
> a destructor has no choice in the matter, since the object is being
> destroyed regardless of what the destructor thinks about it), then C++
> provides that guarantee - objects are constructed once and any object
> which is constructed will be destructed. (Barring manual intervention,
> of course.)

I think that is true in Ada as well. The question that Robert Eachus is
thinking about is what happens if an Initialize routine propagates an
exception (or a constructor in C++). In such a case, the object could be
partially initialized. Ada says that the top-level finalizer will not be
called in that case (although the finalize of controlled components that
completed initialization will be called).

This also covers abort during initialization (before Initialize is called,
since it is abort-deferred). But abort of an assignment can cause an object
to end up in a goofy state (say, if it happens between the target
finalization and the Adjust).

>  > I think it is best to depend primarily of a valid flag
>
> The problem with a valid flag is that it pushes the responsibility for
> validity testing out to all the methods. One of the goals of having a
> constructor in OO was to localize object construction in one place, so
> every other method could assume that the object was in a consistent and
> valid state.

Not necessarily. What I was thinking of was a flag for the use of
Initialize/Adjust/Finalize -- so that Finalize does nothing if it is not
set. That's absoletely necessary if you use Robert's aggregate assignment,
because Finalize will be called with an object on which Initialize has not
run. And it's always possible in the Ada model that Finalize will be called
twice. OTOH, any (other) method of the type can assume that the object is
valid. (This presumes that Initialize isn't calling methods, but that is
under the control of the programmer.)

> > (For someone who claims to not know Ada very well, Hyman knows Ada very
>  > well. He should join the ARG. :-) We could use him.)
>
> :-) It's mostly that I understand in pretty excruciating detail how
> things work in C++ (and in Java, to some extent), and the reasons for
> those behaviors. When I see that Ada does something differently, I can
> try to figure out whether those reasons will have an effect that C++
> was designed to avoid. (Note that I'm not claiming C++ is better, I'm
> just comparing effects of different decisions.)

Which was my point. Many ARG members don't know C++ that well, so we may
miss subtilies of the design. Your input is very useful that way. It would
be especially useful on the new features of Ada 2005 (like interfaces),
while we still can repair screw-ups.

                 Randy.






^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-18 20:32                                                                     ` Randy Brukardt
@ 2004-03-19  3:59                                                                       ` Hyman Rosen
  2004-03-19 19:37                                                                         ` Randy Brukardt
  0 siblings, 1 reply; 67+ messages in thread
From: Hyman Rosen @ 2004-03-19  3:59 UTC (permalink / raw)


Randy Brukardt wrote:
>  (This presumes that Initialize isn't calling methods, but that is
> under the control of the programmer.)

I think this brings us around full circle. In an inheritance hierarchy
which requires that Initialize methods call their parent's Initialize,
there is not just one programmer. The author of the derived class may
not know whether the base's Initialize will be calling methods, possibly
with dispatching.



^ permalink raw reply	[flat|nested] 67+ messages in thread

* Re: abstract sub programs overriding
  2004-03-19  3:59                                                                       ` Hyman Rosen
@ 2004-03-19 19:37                                                                         ` Randy Brukardt
  0 siblings, 0 replies; 67+ messages in thread
From: Randy Brukardt @ 2004-03-19 19:37 UTC (permalink / raw)


"Hyman Rosen" <hyrosen@mail.com> wrote in message
news:teu6c.24872$1g2.18676@nwrdny02.gnilink.net...
> Randy Brukardt wrote:
> >  (This presumes that Initialize isn't calling methods, but that is
> > under the control of the programmer.)
>
> I think this brings us around full circle. In an inheritance hierarchy
> which requires that Initialize methods call their parent's Initialize,
> there is not just one programmer. The author of the derived class may
> not know whether the base's Initialize will be calling methods, possibly
> with dispatching.

True, but re-dispatch (in the same type hierarchy) is an error in an
Initialize or Finalize routine. It's one that isn't caught by Ada, but since
you have to go out of your way to do it, it doesn't happen often. (Some
people would go further and say that redispatch is always an error, but that
is probably going too far. But it always should be avoided.) Besides,
calling methods of the type in an Initialize routine is a dubious practice
to begin with (because the object is not necessarily initialized properly).

There's nothing new about this. In OOP, you always have to trust that your
parent is implemented properly. If it isn't, there is nothing that you can
do that will make an appropriate abstraction. The parent has to be fixed.
Perhaps you can patch around it, but that doesn't change the fact that the
*parent* is broken.

                       Randy.






^ permalink raw reply	[flat|nested] 67+ messages in thread

end of thread, other threads:[~2004-03-19 19:37 UTC | newest]

Thread overview: 67+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2004-03-02 19:01 abstract sub programs overriding Evangelista Sami
2004-03-03  1:43 ` Stephen Leake
2004-03-05 15:02   ` Evangelista Sami
2004-03-05 16:15     ` Marius Amado Alves
2004-03-08 18:54       ` Adam Beneschan
2004-03-08 23:42         ` Marius Amado Alves
2004-03-05 16:26     ` Marius Amado Alves
2004-03-06  9:31       ` Simon Wright
2004-03-06 15:18         ` Evangelista Sami
2004-03-06 19:09           ` Marius Amado Alves
2004-03-07 12:35             ` Simon Wright
2004-03-07 13:39               ` Marius Amado Alves
2004-03-08 19:08               ` Adam Beneschan
2004-03-08 20:03                 ` Hyman Rosen
2004-03-09  8:51                   ` Dmitry A. Kazakov
2004-03-09 13:34                     ` Hyman Rosen
2004-03-09 14:49                       ` Dmitry A. Kazakov
2004-03-09 15:14                         ` Hyman Rosen
2004-03-09 15:56                           ` Dmitry A. Kazakov
2004-03-09 16:32                             ` Hyman Rosen
2004-03-10  9:32                               ` Dmitry A. Kazakov
2004-03-10 13:08                                 ` Hyman Rosen
2004-03-10 14:58                                   ` Robert I. Eachus
2004-03-10 16:00                                     ` Hyman Rosen
2004-03-10 18:07                                       ` Robert I. Eachus
2004-03-10 20:04                                         ` Hyman Rosen
2004-03-11  2:43                                           ` Robert I. Eachus
2004-03-11 13:55                                             ` Hyman Rosen
2004-03-12 23:02                                               ` Robert I. Eachus
2004-03-14 21:33                                                 ` Hyman Rosen
2004-03-15  5:59                                                   ` Robert I. Eachus
2004-03-15 14:39                                                     ` Hyman Rosen
2004-03-16 16:16                                                       ` Robert I. Eachus
2004-03-16 16:51                                                         ` Hyman Rosen
2004-03-16 19:54                                                         ` Hyman Rosen
2004-03-16 23:16                                                           ` Randy Brukardt
2004-03-17  1:54                                                           ` Robert I. Eachus
2004-03-16 23:14                                                         ` Randy Brukardt
2004-03-17  2:43                                                           ` Robert I. Eachus
2004-03-17 17:40                                                             ` Randy Brukardt
2004-03-18  2:39                                                               ` Robert I. Eachus
2004-03-18  5:57                                                                 ` Randy Brukardt
2004-03-18 15:03                                                                   ` Hyman Rosen
2004-03-18 20:32                                                                     ` Randy Brukardt
2004-03-19  3:59                                                                       ` Hyman Rosen
2004-03-19 19:37                                                                         ` Randy Brukardt
2004-03-16  6:00                                               ` Randy Brukardt
2004-03-11 10:09                                   ` Dmitry A. Kazakov
2004-03-11 14:10                                     ` Hyman Rosen
2004-03-11 14:59                                       ` Dmitry A. Kazakov
2004-03-11 15:40                                         ` Hyman Rosen
2004-03-11 16:28                                           ` Dmitry A. Kazakov
2004-03-11 17:26                                             ` Hyman Rosen
2004-03-12  8:53                                               ` Dmitry A. Kazakov
2004-03-12 13:09                                                 ` Hyman Rosen
2004-03-12 14:00                                                   ` Dmitry A. Kazakov
2004-03-12 14:56                                                     ` Hyman Rosen
2004-03-12 18:19                                                       ` Dmitry A. Kazakov
2004-03-12 18:34                                                         ` Hyman Rosen
2004-03-12 20:05                                                           ` Georg Bauhaus
2004-03-13 10:12                                                           ` Dmitry A. Kazakov
2004-03-12 18:07                                               ` Robert I. Eachus
2004-03-10 15:51                 ` Evangelista Sami
2004-03-11  1:38                   ` Dan Eilers
2004-03-06 23:20     ` Dan Eilers
2004-03-03 12:00 ` Marius Amado Alves
2004-03-13  7:51 ` Simon Wright

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox