* 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-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 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-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-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-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-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 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-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 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-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 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-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-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 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 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 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
* 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-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 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-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-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-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-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 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-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-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-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
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