!standard 13.13.2(9/2) 08-04-09 AI05-0023-1/06 !standard 13.13.2(27/2) !standard 13.13.2(56/2) !class binding interpretation 06-11-13 !status ARG Approved 7-0-2 08-02-08 !status work item 06-11-13 !status received 06-06-27 !priority Medium !difficulty Hard !qualifier Omission !subject 'Read on records with variant parts !summary Compilers should not violate the finalization rules when creating 'Read. !question AI-195 and the section on 'Read seems to have a small hole in it when it comes to discriminated records with default discriminants and variant parts. Consider for example: type Customer_Description( Is_Company : Boolean := True ) is record case Is_Company is when True => Foo : Integer := 0; Bar : Integer := 0; Company_Name : Some_Controlled_Type; when False => Persons_Name : Some_Other_Controlled_Type; end case; end record; Suppose we do a 'Read on an unconstrained object OBJ of type Customer_Description, whose Is_Company discriminant is currently TRUE. We read the discriminant, and the discriminant changes to FALSE. What happens to the Company_Name and Persons_Name fields, and when? Can an anonymous object be used? If it is, is Initialize called on the controlled components? Must it be called? Or is this case more like an aggregate? !recommendation (See summary.) !wording Add after 13.13.2(9/2): If T is a discriminated type and its discriminants have defaults then S'Read first reads the discriminants from the stream without modifying Item. S'Read then creates an object of type T constrained by these discriminants. The value of this object is then converted to the subtype of Item and is assigned to Item. Finally, the Read attribute for each non-discriminant component of Item is called in canonical order as described above. Normal default initialization and finalization take place for the created object. Modify 13.13.2(27/2) as follows: S'Output then calls S'Write to write the value of Item to the stream. S'Input then creates an object {of type T,} [(]with the bounds or {(when without defaults) the} discriminants, if any, taken from the stream[)], passes it to S'Read, and returns the value of the object. {If T has discriminants, then this object is unconstrained if and only the discriminants have defaults.} Normal default initialization and finalization take place for this object (see 3.3.1, 7.6, and 7.6.1). Add after 13.13.2(56/2): Implementation Permissions If T is a discriminated type and its discriminants have defaults then in two cases an execution of the default implementation of S'Read is not required to create an anonymous object of type T: If the discriminants values that are read in are equal to the corresponding discriminant values of Item, then no object of type T need be created and Item may be used instead. If they are not equal and Item is a constrained variable, then Constraint_Error may be raised at that point, before any further values are read from the stream and before the object of type T is created. A default implementation of S'Input that calls the default implementation of S'Read may create a constrained anonymous object with discriminants that match those in the stream. AARM Implementation Note: This allows the combined executions of S'Input and S'Read to create one object of type T instead of two. If this option is exercised, then: - The discriminants are read from the stream by S'Input, not S'Read. - S'Input declares an object of type T constrained by the discriminants read from the stream, not an unconstrained object. - The discriminant values which S'Read would normally have read from the stream are read from Item instead. - The permissions of the preceding paragraph then apply and no object of type T need be created by the execution of S'Read. !example declare Counter : Natural := 0; function Default_Value return Integer is begin Text_Io.Put_Line ("F was called"); -- a user-visible side-effect Counter := Counter + 1; -- another side-effect return Counter; end Default_Value; type R (D : Boolean := True) is record F : Integer := Default_Value; end record; procedure P (Stream : access Ada.Streams.Root_Stream_Type'Class) is Saved_Counter : constant Natural := Counter; R_Val : constant R := (D => False, F => 123); begin R'Output (Stream, R_Val); pragma Assert (R_Val = R'Input (Stream); pragma Assert ((Counter - Saved_Counter) in 1 .. 2); end; procedure Do_Some_Stuff_Including_A_Call_To_P is ... ; begin Do_Some_Stuff_Including_A_Call_To_P; end; Without these implementation permissions, the execution of P's call to R'Input must include two calls to Default_Value. R'Input would declare a local temporary and initialize it; this initialization would include one call to Default_Value. R'Input would then call R'Read, which would declare a second temporary and initialize it; this initialization would include the second call to Default_Value. With these implementation permissions, it is permitted to call Default_Value only once. !discussion This problem applies to all depends-on-discriminant components with non-trivial finalization. When we say that "the value of this object ... is assigned to Item", does it need to be explicitly stated that this is an assignment operation? Does the list of assignment operations in 5.2(3) need to be updated? I think not. The proposed changes will reduce the number of times the default S'Read needs to create a separate anonymous object. Nevertheless, it will still be necessary when the caller provides an unconstrained Item with initial discriminant values not matching those in the stream. In this case, S'Read will need to create an anonymous object using the discriminants read from the stream, and then assign it to the caller-provided Item, with all the attendant initialization, adjustment, finalization, etc. required if any parts are controlled. We permit the default implementation of S'Input for a discriminated type with defaults to "peek" into the stream to arrange to create an object with the proper discriminant values and then pass it off to the default implementation of S'Read. Of course the actual implementation may effectively "inline" the default implementation of S'Read within S'Input to avoid the need for "peeking." If the type has a user-defined Initialize routine, then S'Input must call that before passing the object to S'Read, and we require that the anonymous object be constrained so Initialize can't change the values of the discriminants which had been so carefully set up to match those in the stream. !corrigendum 13.13.2(9/2) @dinsa For elementary types, Read reads (and Write writes) the number of stream elements implied by the Stream_Size for the type @i; the representation of those stream elements is implementation defined. For composite types, the Write or Read attribute for each component is called in canonical order, which is last dimension varying fastest for an array, and positional aggregate order for a record. Bounds are not included in the stream if @i is an array type. If @i is a discriminated type, discriminants are included only if they have defaults. If @i is a tagged type, the tag is not included. For type extensions, the Write or Read attribute for the parent type is called, followed by the Write or Read attribute of each component of the extension part, in canonical order. For a limited type extension, if the attribute of the parent type or any progenitor type of @i is available anywhere within the immediate scope of @i, and the attribute of the parent type or the type of any of the extension components is not available at the freezing point of @i, then the attribute of @i shall be directly specified. @dinst If @i is a discriminated type and its discriminants have defaults then S'Read first reads the discriminants from the stream without modifying @i. S'Read then creates an object of type @i constrained by these discriminants. The value of this object is then converted to the subtype of @i and is assigned to @i. Finally, the Read attribute for each non-discriminant component of @i is called in canonical order as described above. Normal default initialization and finalization take place for the created object. !corrigendum 13.13.2(27/2) @drepl @xbullet to the stream. S'Input then creates an object (with the bounds or discriminants, if any, taken from the stream), passes it to S'Read, and returns the value of the object. Normal default initialization and finalization take place for this object (see 3.3.1, 7.6, and 7.6.1).> @dby @xbullet to the stream. S'Input then creates an object of type @i, with the bounds or (when without defaults) the discriminants, if any, taken from the stream, passes it to S'Read, and returns the value of the object. If @i has discriminants, then this object is unconstrained if and only the discriminants have defaults. Normal default initialization and finalization take place for this object (see 3.3.1, 7.6, and 7.6.1).> !corrigendum 13.13.2(56/2) @dinsa The number of calls performed by the predefined implementation of the stream-oriented attributes on the Read and Write operations of the stream type is unspecified. An implementation may take advantage of this permission to perform internal buffering. However, all the calls on the Read and Write operations of the stream type needed to implement an explicit invocation of a stream-oriented attribute must take place before this invocation returns. An explicit invocation is one appearing explicitly in the program text, possibly through a generic instantiation (see 12.3). @dinss If @i is a discriminated type and its discriminants have defaults then in two cases an execution of the default implementation of S'Read is not required to create an anonymous object of type @i: If the discriminants values that are read in are equal to the corresponding discriminant values of @i, then no object of type @i need be created and @i may be used instead. If they are not equal and @i is a constrained variable, then Constraint_Error may be raised at that point, before any further values are read from the stream and before the object of type @i is created. A default implementation of S'Input that calls the default implementation of S'Read may create a constrained anonymous object with discriminants that match those in the stream. !ACATS test Add an ACATS C-Test to check the proper operation of examples like the one in the question, taking care to respect the implementation permissions. !appendix !topic 'Read on records with variant parts !reference RM05 13.13.2, AI95-195 !from Adam Beneschan 06-06-27 !discussion In looking into a test case posted on comp.lang.ada, I've found that AI-195 and the section on 'Read seems to have a small hole in it when it comes to discriminant records with default discriminants and variant parts. I noticed that the word "variant" does not appear at all in the AI-195 text (including discussions, e-mail, etc.) Consider for example: type Customer_Description( Is_Company : Boolean := True ) is record case Is_Company Is when True => Foo : Integer := 0; Bar : Integer := 0; Company_Name : Some_Controlled_Type; when False => Persons_Name : Some_Other_Controlled_Type; end case; end record; Suppose we do a 'Read on an unconstrained object OBJ of type Customer_Description, whose Is_Company discriminant is currently TRUE. We read the discriminant, and the discriminant changes to FALSE. What happens to the Company_Name and Persons_Name fields, and when? AI-195 suggests that when a record (not necessarily a variant record) has discriminants, then the model requires the implementation to create an anonymous object. This will work; the implementation reads the discriminants into an anonymous object, initializes the anonymous object, then (perhaps) performs an assignment to copy the anonymous object to OBJ, which requires OBJ to be finalized first; then 'Read on the components can be performed directly into OBJ. This will work fine. However, the language about the anonymous object didn't make it into the RM. 13.13.2(55/2) says only, as an Implementation Requirement, "If Constraint_Error is raised during a call to Read because of failure of one of the above checks, the implementation must ensure that the discriminants of the actual parameter of Read are not modified". Unless there's some other part of the RM I've missed, this appears to make it legal to, say, copy the discriminants into temporary buffer, read the new discriminants directly into OBJ, and set up exception-handling code that copies the previous discriminants back to OBJ if necessary. This will be a disaster, though, since it will leave garbage in Company_Name or Persons_Name and will cause problems as soon as a user-defined 'Read on the component tries to finalize it. This may be just a small nitpick (like lots of my contributions); perhaps it should be "just obvious" that the implementation is supposed to do it right. But my impression is that there's a hole in the RM's logic that needs plugging; particularly because of 13.13.2(9) in the RM and AARM that seems to imply that a discriminant with a default is simply treated like any other component by 'Read. **************************************************************** From: Randy Brukardt Date: Tuesday, June 27, 2006 1:45 PM > In looking into a test case posted on comp.lang.ada, I've found that > AI-195 and the section on 'Read seems to have a small hole in it when > it comes to discriminant records with default discriminants and > variant parts. I noticed that the word "variant" does not appear at > all in the AI-195 text (including discussions, e-mail, etc.) That's not surprising; the same issues occur for any depends-on-a-discriminant component, whether that is by a variant or an array constraint or whatever. There is rarely a reason to treat variants specially. ... > However, the language about the anonymous object didn't make it into > the RM. 13.13.2(55/2) says only, as an Implementation Requirement, > "If Constraint_Error is raised during a call to Read because of > failure of one of the above checks, the implementation must ensure > that the discriminants of the actual parameter of Read are not > modified". Unless there's some other part of the RM I've missed, this > appears to make it legal to, say, copy the discriminants into > temporary buffer, read the new discriminants directly into OBJ, and > set up exception-handling code that copies the previous discriminants > back to OBJ if necessary. This will be a disaster, though, since it > will leave garbage in Company_Name or Persons_Name and will cause > problems as soon as a user-defined 'Read on the component tries to > finalize it. > > This may be just a small nitpick (like lots of my contributions); > perhaps it should be "just obvious" that the implementation is > supposed to do it right. But my impression is that there's a hole in > the RM's logic that needs plugging; particularly because of 13.13.2(9) > in the RM and AARM that seems to imply that a discriminant with a > default is simply treated like any other component by 'Read. Well, I guess I don't see a problem. The RM only gives a permission for erroneous behavior in very specific cases. I don't see that any of those cases applies here (a well-defined Read of an entire record). For instance, there's nothing abnormal because there is no exception. An implementation that leads to disaster (that is, erroneous execution) is just wrong in the absence of a specific rule allowing erroneous execution. The compiler in the original question had a bug in an obscure case, and that's about all there is to say. Certainly the stream wording isn't the best wording ever, but messing with it seems to lead to new problems. Perhaps an AARM note about the anonymous objects as described in AI-195 would be a good idea, but that is about as far as I think we should go. **************************************************************** From: Adam Beneschan Date: Friday, July 7, 2006 8:23 PM On second thought, it's probably not necessary to create an entire anonymous object. If just the discriminants are read into a temporary buffer(s), then after all the discriminants are read in, the object being 'Read can be finalized, the discriminants can be copied in, and the object can be re-initialized with the new discriminants. It still feels to me like something is missing, however. It's pretty clear to me that, when you do a 'Read on a record of type T that has discriminants with defaults, and there are components of T that depend on the discriminants, then *some* object of type T has to be initialized (in the sense of 7.6) immediately after the discriminants are read, and before any other components are read from the stream. Suppose you have a record type like this: type Rec (N : My_Int_Type := 0) is record Component1 : Controlled_Type_1; Component2 : Controlled_Type_2; case N is ... -- assume some controlled types or arrays whose -- bounds depend on N are in here end case; end record; Suppose also that user-defined 'Read routines were present for My_Int_Type, Controlled_Type_1, and Controlled_Type_2, but not for Rec; and that Initialize routines were defined for Controlled_Type_1 and _2 that set some global flags. Now we do Rec'Read on some object. I maintain that, if the user-defined 'Read routines checked the global flags, the 'Read routine on My_Int_Type would always find that the initialization flags were clear, and the 'Read routines on Controlled_Type_1 and Controlled_Type_2 would always find that the flags were set, because *some* object of type Rec has to be initialized after N is read (in order to get the components in the variant part initialized correctly), and this initialization would have to call the user-defined Initialize routine for Controlled_Type_1 and _2. In fact, I'd say this is so definitely true that an ACATS test could be written that tests this. Would you agree with this? Is this behavior implied by the language? Suppose that there were no components of Rec that depended on N. Would it still be true that Rec's "initialization" operation needs to be called right after the discriminant is read? (Now it's possible to make things work without it.) I guess what disturbs me about this is that the rest of the language appears to be very explicit about when "initialize" and "finalize" operations are performed (including when an implementation has permission to omit them). But here's a case where, it seems, initialize may be performed---or *must* be performed---and the language doesn't say anything about it, even in a case where initialization appears to be necessary. The language says that objects are initialized after being created, but it doesn't say anything about 'Read creating an object, and now it appears to me that this is a case where an anonymous object may not be necessary, which means that no object would be created by 'Read, but an initialize operation would need to be done anyway. Maybe all this doesn't bother you as much as it bothers me. To me, something seems to be missing. **************************************************************** From: Randy Brukardt Date: Friday, July 7, 2006 9:51 PM This doesn't bother me because there has to be more than missing wording for there to be a problem with the standard. There also has to be a sensible interpretation of the Standard that both leads to an undesirable result and doesn't violate any other rules. There are *lots* of things that aren't spelled out in the Standard! I don't see why you say "an initialize operation would need to be done anyway". In the absence of some wording requiring Initialize to be called, it shouldn't happen (as you note). Indeed, I think this case is very similar to the initialization of the (optional) anonymous object for an aggregate. That is, there is no explicit call to Initialize or Adjust on the empty space before it is set to the aggregate values. That means that technically, there is no initialization going on! In your example: > type Rec (N : My_Int_Type := 0) is record > Component1 : Controlled_Type_1; > Component2 : Controlled_Type_2; > case N is > ... -- assume some controlled types or arrays whose > -- bounds depend on N are in here > end case; > end record; > > Suppose also that user-defined 'Read routines were present for > My_Int_Type, Controlled_Type_1, and Controlled_Type_2, but not for > Rec; and that Initialize routines were defined for Controlled_Type_1 > and _2 that set some global flags. Now we do Rec'Read on some object. > I maintain that, if the user-defined 'Read routines checked the global > flags, the 'Read routine on My_Int_Type would always find that the > initialization flags were clear, and the 'Read routines on > Controlled_Type_1 and Controlled_Type_2 would always find that the > flags were set, because *some* object of type Rec has to be > initialized after N is read (in order to get the components in the > variant part initialized correctly), and this initialization would > have to call the user-defined Initialize routine for Controlled_Type_1 > and _2. In fact, I'd say this is so definitely true that an ACATS > test could be written that tests this. I don't see this at all. If you had an aggregate for type Rec and you were creating an anonymous object for it, you wouldn't call Initialize at all unless the components were (explicitly) default initialized. Why would you have to do something else here?? The rules say Initialize is called when something is "initialized by default" -- which has a very specific technical meaning. That doesn't happen here (it would have to say so). Moreover, the value after the entire Read operation isn't going to be the default one. I do agree that you have to Finalize old components before inserting the new values, and Adjust and Finalize appropriately if there is an anonymous object. If you don't do that, you'll get erroneous execution, which is not possible here. It is a little weird that the predefined 'Read needs magic (as a user-defined one would need explicit objects, which of course would require Initialize), but the alternative doesn't make sense. (Especially if the type is limited! The object better be built-in-place, no matter how hard that is.) It would have been nice if 'Read was mentioned with aggregates in the notes of the controlled type section. But streaming issues probably weren't considered at all when the semantics of controlled types were defined. Not that it matters, it seems to fall out properly. It does appear to me that you need to override 'Read if you need something other than pure value copying to happen during read-in (such as adjustment of reference counts). That seems OK, it's necessary to do that in every other case. (Again, this seems like more of a problem with controlled types than with 'Read.) As I said before, it would be good if there were AARM notes detailing the interactions between Read and controlled objects, but I don't see any language problems. Anyway, if you still think there is a problem here (and there being a problem relating to streams wouldn't surprise me!), please give an example where the rules could be interpreted to get an incorrect answer but without erroneous execution. It also would help if you would suggest what you think the rules for 'Read *should* say, in as much detail as possible. **************************************************************** From: Tucker Taft Date: Friday, July 7, 2006 10:48 PM I think it might be better to make a requirement in this case (unconstrained, discriminated actual) that *no* part of the actual parameter be modified if any of the "above checks" fail (the phrase "above checks" in this paragraph is unfortunately a bit vague). I agree with Randy that Initialize wouldn't need to be called if the values are read directly into an "empty" anonymous object, similar to an aggregate. But I would also permit an implementation to create a default-initialized temp using the values of the discriminants, and then overwrite the fields one at a time. Effectively this would permit 'Read to be implemented by using 'Input, where a temporary default-initialized object is essentially *required* (by RM 13.3.2(27/2)). This permission would imply that you couldn't call 'Read until Initialize for any controlled parts had been elaborated. But that seems pretty reasonable to me, since a call on 'Read is visibly a procedure call. Aggregates don't permit calls on Initialize/Adjust because they don't look like subprogram calls, and they are used to define deferred constants. To summarize, I would recommend: 1) Require the actual not be modified if any of the "above checks" fail, when the actual is unconstrained with discriminants. 2) Permit the default implementation of 'Read for types with defaulted discriminants to create temporary objects, optionally default-initialized. **************************************************************** From: Randy Brukardt Date: Saturday, July 8, 2006 6:11 PM > I agree with Randy that Initialize wouldn't need to > be called if the values are read directly into an "empty" > anonymous object, similar to an aggregate. But I > would also permit an implementation to create > a default-initialized temp using the values of > the discriminants, and then overwrite the fields > one at a time. Effectively this would permit 'Read > to be implemented by using 'Input, where a temporary > default-initialized object is essentially *required* > (by RM 13.3.2(27/2)). *Now* I'm "bothered", as Adam puts it. This permission would mean that you couldn't reason in any way about the behavior of a predefined 'Read. In particular, with this permission, it might call Initialize, and it might not. That is, this might be an unpaired creation (like an aggregate) and it might be a paired creation (like an object declaration). If you're doing reference counts, for instance, you could not allow the predefined read to be used because of this permission. Indeed, I don't know if there would be any real controlled types where you could use the predefined Read. Note that I'm not talking about a case where an anonymous object is introduced and properly finalized. I don't think any permission is needed for that for non-limited types, that's always allowed. Currently, there is a well-defined (if not well-documented) semantics for predefined 'Read: it works like an aggregate (with or without an anonymous object). That may not be the nicest possible semantics, but at least it is defined and consistent. Allowing a semantics which is inconsistent with the rest of controlled types isn't going to help. [Truth is, I can't think of any meaningful semantics for 'Read for controlled types. It can't pair properly, no matter what definition you give it, and if it doesn't, you have no choice but to override it. The implementer of a controlled type can simply avoid aggregates to avoid the pairing problem with them, but that doesn't work with 'Read. It would be best to make it unavailable for such types -- except that would be a contract model violation.] > This permission would imply that you couldn't call 'Read > until Initialize for any controlled parts > had been elaborated. But that seems pretty reasonable > to me, since a call on 'Read is visibly a procedure call. I don't think that such a call is possible; 'Read is a procedure call and as such has to occur in a body. Either the body hasn't been elaborated yet (error) or Initialize has been elaborated. That's good, too, because requiring an elaboration check on the *predefined* version of 'Read could be very painful to implement. > Aggregates don't permit calls on Initialize/Adjust > because they don't look like subprogram calls, and > they are used to define deferred constants. > > To summarize, I would recommend: > 1) Require the actual not be modified if any of the > "above checks" fail, when the actual is unconstrained > with discriminants. > 2) Permit the default implementation of 'Read for types > with defaulted discriminants to create > temporary objects, optionally default-initialized. If by this you mean that a temporary object can be introduced (with all of the rules of that), OK, but I think that's already allowed so I don't know why a permission would be necessary. The semantics of the predefined 'Read of a type with a controlled part is: 1) Read the discriminants if necessary, and perform the "above checks". 2) Finalize the object passed. 3) Read the rest of the data, and build it directly in the object. or (for a type where 'Read has to read the discriminants): 1) Read the discriminants. 2) Create and initialize an anonymous object with the appropriate discriminants. 3) Read the rest of the data into the anonymous object. 4) Finalize the object passed. 5) Copy the anonymous object into the object passed, and Adjust the data. 6) Finalize the anonymous object. Nope, the latter doesn't work: beside the extra Initialize/Finalize pair, there is an extra Adjust that doesn't exist in the first scenario. That breaks all of the invariants. The second case has to look like an anonymous aggregate: 1) Read the discriminants. 2) Create an anonymous object with the appropriate discriminants, but do not initialize! 3) Read the rest of the data into the anonymous object. 4) Finalize the object passed. 5) Copy the anonymous object into the object passed, and Adjust the data. 6) Finalize the anonymous object. Now it's consistent with the first case. So, I guess my point is that Initialize must never, ever be called in the predefined 'Read. Or we have to change the semantics dramatically so that it is *always* called. Else you end up with many cases where the pairing of controlled operations changes depending on the implementation, and that makes no sense. **************************************************************** From: Tucker Taft Date: Saturday, July 8, 2006 10:10 PM > *Now* I'm "bothered", as Adam puts it. This permission would mean that you > couldn't reason in any way about the behavior of a predefined 'Read. In > particular, with this permission, it might call Initialize, and it might > not. That is, this might be an unpaired creation (like an aggregate) and it > might be a paired creation (like an object declaration). If you're doing > reference counts, for instance, you could not allow the predefined read to > be used because of this permission. Indeed, I don't know if there would be > any real controlled types where you could use the predefined Read. Sorry, I didn't mean to imply that the normal invariants may be violated. If you default initialize controlled components of the anonymous object, then when you overwrite them you must finalize them immediately prior to their being overwritten. > Note that I'm not talking about a case where an anonymous object is > introduced and properly finalized. I don't think any permission is needed > for that for non-limited types, that's always allowed. I am probably only talking about this kind of anonymous object. But can you say where this permission is to be found in the RM? > Currently, there is a well-defined (if not well-documented) semantics for > predefined 'Read: it works like an aggregate (with or without an anonymous > object). That may not be the nicest possible semantics, but at least it is > defined and consistent. Allowing a semantics which is inconsistent with the > rest of controlled types isn't going to help. > > [Truth is, I can't think of any meaningful semantics for 'Read for > controlled types. It can't pair properly, no matter what definition you give > it, and if it doesn't, you have no choice but to override it. The > implementer of a controlled type can simply avoid aggregates to aviod the > pairing problem with them, but thet doesn't work with 'Read. It would be > best to make it unavailable for such types -- except that would be a > contract model violation.] I don't completely understand your problem here. Can you give a simple example? >> To summarize, I would recommend: >> 1) Require the actual not be modified if any of the >> "above checks" fail, when the actual is unconstrained >> with discriminants. >> 2) Permit the default implementation of 'Read for types >> with defaulted discriminants to create >> temporary objects, optionally default-initialized. > > If by this you mean that a temporary object can be introduced (with all of > the rules of that), OK, but I think that's already allowed so I don't know > why a permission would be necessary. If such a permission already exists, great. I just don't know where it is. > The semantics of the predefined 'Read of a type with a controlled part is: > > 1) Read the discriminants if necessary, and perform the "above checks". > 2) Finalize the object passed. > 3) Read the rest of the data, and build it directly in the object. > > or (for a type where 'Read has to read the discriminants): > > 1) Read the discriminants. > 2) Create and initialize an anonymous object with the appropriate > discriminants. > 3) Read the rest of the data into the anonymous object. > 4) Finalize the object passed. > 5) Copy the anonymous object into the object passed, and Adjust the > data. > 6) Finalize the anonymous object. > > Nope, the latter doesn't work: beside the extra Initialize/Finalize pair, > there is an extra Adjust that doesn't exist in the first scenario. That > breaks all of the invariants. The second case has to look like an anonymous > aggregate: > > 1) Read the discriminants. > 2) Create an anonymous object with the appropriate discriminants, but do > not initialize! > 3) Read the rest of the data into the anonymous object. > 4) Finalize the object passed. > 5) Copy the anonymous object into the object passed, and Adjust the > data. > 6) Finalize the anonymous object. > > Now it's consistent with the first case. I would claim you can default initialize, so long as you finalize each controlled component prior to overwriting it with the data from the stream. > So, I guess my point is that Initialize must never, ever be called in the > predefined 'Read. Or we have to change the semantics dramatically so that it > is *always* called. Else you end up with many cases where the pairing of > controlled operations changes depending on the implementation, and that > makes no sense. I don't see the problem so long as each component that is Initialized is Finalized prior to being overwritten from the data from the stream. That is what would happen if you implemented it in "normal" Ada source, in any case. **************************************************************** From: Adam Beneschan Date: Monday, July 10, 2006 11:42 AM > Currently, there is a well-defined (if not well-documented) semantics for > predefined 'Read: it works like an aggregate (with or without an anonymous > object). Actually, I'd say "not documented at all" rather than "not well-documented". I couldn't find anything in the RM that says, or implies, that predefined 'Read works like an aggregate. And if it's well-defined but not well-defined in the RM, where is the definition? Is it somewhere in a locked safe in the ARG offices? Do you need a special key to get in? Do you need to know a secret password? OK, I'm just being silly here, and I'd better stop now lest I start sounding like a certain c.l.a poster who thinks that ARG is a big conspiracy to hijack the language and turn themselves into a priesthood who prevents ordinary lay users from knowing what the language really says and rob Americans of their precious body fluids........... But maybe that's one of the things that bothered me. If there are well-defined semantics for predefined 'Read but they were missing from the RM (or an important aspect of them was missing), perhaps that's what caused the vague feeling on my part that something was amiss. > Nope, the latter doesn't work: beside the extra Initialize/Finalize pair, > there is an extra Adjust that doesn't exist in the first scenario. That > breaks all of the invariants. The second case has to look like an anonymous > aggregate: > > 1) Read the discriminants. > 2) Create an anonymous object with the appropriate discriminants, but do > not initialize! > 3) Read the rest of the data into the anonymous object. > 4) Finalize the object passed. > 5) Copy the anonymous object into the object passed, and Adjust the > data. > 6) Finalize the anonymous object. > > Now it's consistent with the first case. > > So, I guess my point is that Initialize must never, ever be called in the > predefined 'Read. Or we have to change the semantics dramatically so that it > is *always* called. Else you end up with many cases where the pairing of > controlled operations changes depending on the implementation, and that > makes no sense. I'm not sure exactly which Initialize you mean must never be called---on the entire record, or on individual components? Initialize *must* be called on each individual component before 'Read is called on the component. Perhaps this isn't necessary if the predefined 'Read for the component is called; but if the component type has a user-defined 'Read, the parameter must not be uninitialized garbage. [That's what led to the bug in the original test case.] Assuming the call looks like "Rec'Read (Str, R);": I probably created part of the confusion by assuming that an "initialize" operation must be called on the type Rec. Maybe that's not strictly true. It seems certainly true that, if Rec is a discriminant record with defaults, any components that depend on the discriminant have to be Initialized before being passed to 'Read (particularly to a user-defined 'Read). If a component X does not depend on a discriminant, then R.X could be passed to 'Read without an Initialize call, and things would still work; but if Anon.X is passed to 'Read where Anon is an anonymous object, then Anon.X still has to be initialized at some point. [And if R is actually constrained, then none of the Initialize routines really need to be called, since no array bounds will change and no components will come into existence that didn't already exist in R.] Given all this, it seemed to me that since a compiler may have already generated a subroutine for initializing objects of type Rec, it would make sense for the compiler to generate a call to this subroutine before calling 'Read on the components. This isn't the only possible implementation---it could call Initialize on each individual component before passing it to 'Read---but it seems like one possibility. Now, though, it's beginning to look like this implementation would be incorrect---or that it would be correct according to the current RM but wrong according to what the semantics *should* be. Anyway, unless I've grossly misunderstood this conversation (a good possibility), it does look like some guidance is needed as far as what Initialize operations are required, which ones are allowed but not required, and which ones are disallowed. **************************************************************** From: Randy Brukardt Date: Thursday, April 5, 2007 11:35 PM A comment on AI05-0023/02: I'm not very happy with the required temporary in S'Read. S'Read should default to build-in-place; that was done, the horribly complex second permission for S'Input wouldn't be necessary. OTOH, it may be too hard to describe that; but essentially I would use the semantics you describe for the permission as the canonical ones, and then allow a temporary to be created and assigned instead. If that doesn't work, I think you need to say something in the description about why. I also don't like that you appear to be *requiring* a temporary to be built if the discriminants change. If the implementation can handle build-in-place with changed discriminants, it should be allowed to do so. We'd allow an assignment temporary to be removed in this case, why not here? [I'm also dubious about the early Constraint_Error, as that would make it hard to keep a stream in sync. It could be a security issue. But that I'll leave for another time.] I completely hate the description of the second implementation permission. I think it is important to recognize that it is OK to combine the two operations if both S'Read and S'Input are the default versions, but I don't think that much else needs to be said. If the canonical semantics was to not declare a temporary, you wouldn't need this at all. In any case, this is so complex, I have no idea at all if the controlled operations are paired properly (which is critical). Come to think of it, I can't imagine any reason it is needed, even with your current semantics. It's clearly the case that S'Input needs to create a temporary object, which is then passed to S'Read. S'Read has a permission to eliminate its temporary. Surely combining the operations is allowed! What more do you need? You certainly have to call Initialize at least once, and not call Finalize on the returned object. I could see describing the model you do in an AARM note, but since it says nothing at all normative (you can't tell the difference between it and a canonical call with S'Read eliminating its temporary with the first permission). [Later: I see, you failed to allow the temporary to be eliminated in all cases. I think you're over-specifying: clever implementers should be allowed to remove the S'Read temporary all the time.] Summary: This one needs more work. **************************************************************** From: Stephen W. Baird Date: Friday, April 5, 2007 2:36 PM > I'm not very happy with the required temporary in S'Read. S'Read should > default to build-in-place; if that was done, the horribly complex second > permission for S'Input wouldn't be necessary. OTOH, it may be too hard to > describe that; but essentially I would use the semantics you describe for > the permission as the canonical ones, and then allow a temporary to be > created and assigned instead. If that doesn't work, I think you need to say > something in the description about why. I think that we have to have a temp. There are lots of good reasons that the only way to modify a discriminant is by assignment to the entire enclosing object. That means that in order to modify the discriminants of Item, we need a value of type T to assign to it. That means we need a temp. There is an alternative to the model I described in the AI: read in discriminants, declare a temp constrained to those discriminants (this includes default initialization), assign the temp to Item, and then invoke Read for each of the non-discriminant components of Item. The difference is that we are invoking Read on the non-discriminant components of Item, not of the temp. In many common cases, it might be easier for a compiler to eliminate the temp if we do it this way. Do you think this approach would be better? > I also don't like that you appear to be *requiring* a temporary to be built > if the discriminants change. If the implementation can handle build-in-place > with changed discriminants, it should be allowed to do so. As always, the implementation can change things around however it wants as long as the user can't tell the difference. You don't need any special permission for that. Can you give a specific example where you think that my proposal is too restrictive? > We'd allow an > assignment temporary to be removed in this case, why not here? > [I'm also dubious about the early Constraint_Error, as that would make it > hard to keep a stream in sync. It could be a security issue. But that I'll > leave for another time.] > Keeping streams in sync in the face of exceptions is already something of a mess. I don't think this makes matters any worse. > I completely hate the description of the second implementation permission. I > think it is important to recognize that it is OK to combine the two > operations if both S'Read and S'Input are the default versions, but I don't > think that much else needs to be said. If you think there is a simpler way to specify the semantics of combining the two operations, I'd like to hear about it. > If the canonical semantics was to not > declare a temporary, you wouldn't need this at all. But those are the canonical semantics, for reasons described above. > In any case, this is so > complex, I have no idea at all if the controlled operations are paired > properly (which is critical). If an implementation ignores this permission, then it certainly doesn't cause problems. If it makes use of this permission, then that is supposed to mean that the Read/Input combination simply behaves as if the type lacked default discriminant values. If that case is not well-defined, then that is a problem outside of the scope of this AI. > Come to think of it, I can't imagine any reason it is needed, even with your > current semantics. It's clearly the case that S'Input needs to create a > temporary object, which is then passed to S'Read. S'Read has a permission to > eliminate its temporary. Surely combining the operations is allowed! What > more do you need? You certainly have to call Initialize at least once, and > not call Finalize on the returned object. I could see describing the model > you do in an AARM note, but since it says nothing at all normative (you > can't tell the difference between it and a canonical call with S'Read > eliminating its temporary with the first permission). [Later: I see, you > failed to allow the temporary to be eliminated in all cases. I think you're > over-specifying: clever implementers should be allowed to remove the S'Read > temporary all the time.] I'm confused. Are you saying that the implementation permissions I'm proposing do not grant the implementation any permissions that it didn't already have? Would it be helpful if I tried to construct an example to demonstrate that this is not the case? > Summary: This one needs more work. I remain unconvinced It sounds like we'll have some things to discuss at the meeting. **************************************************************** From: Randy Brukardt Date: Friday, April 5, 2007 3:19 PM ... > There is an alternative to the model I described in the AI: > read in discriminants, declare a temp constrained to those discriminants > (this includes default initialization), assign the temp to Item, and then > invoke Read for each of the non-discriminant components of Item. The > difference is that we are invoking Read on the non-discriminant components of Item, > not of the temp. In many common cases, it might be easier for a compiler to > eliminate the temp if we do it this way. > > Do you think this approach would be better? Yes, slightly. (I'm not sure that there is any functional difference, however.) > > I also don't like that you appear to be *requiring* a temporary to be built > > if the discriminants change. If the implementation can handle build-in-place > > with changed discriminants, it should be allowed to do so. > > As always, the implementation can change things around however it wants as > long as the user can't tell the difference. You don't need any special > permission for that. Can you give a specific example where you think that > my proposal is too restrictive? If there are controlled parts, it is impossible use build-in-place to emulate the canonical semantics, because you have to call Initialize/Finalize on the temporary. And you can't do that on the item passed in (it already has values). So a temporary is required as there is no permission to remove it. Note that my concern is the cost of the calls to Initialize/Finalize, not really the cost of copying the temporary: but they can only be removed if the entire temporary is. > > We'd allow an assignment temporary to be removed in this case, why not here? > > [I'm also dubious about the early Constraint_Error, as that would make it > > hard to keep a stream in sync. It could be a security issue. But that I'll > > leave for another time.] > > Keeping streams in sync in the face of exceptions is already > something of a mess. I don't think this makes matters any worse. Could be, I haven't tried to do it. Perhaps it doesn't make sense to try with the default composite attributes (as opposed to the user-defined ones I've generally used). ... > > In any case, this is so > > complex, I have no idea at all if the controlled operations are paired > > properly (which is critical). > > If an implementation ignores this permission, then it certainly doesn't > cause problems. If it makes use of this permission, then that is > supposed to mean that the Read/Input combination simply behaves > as if the type lacked default discriminant values. If that case is not > well-defined, then that is a problem outside of the scope of this AI. > > > Come to think of it, I can't imagine any reason it is needed, even with your > > current semantics. It's clearly the case that S'Input needs to create a > > temporary object, which is then passed to S'Read. S'Read has a permission to > > eliminate its temporary. Surely combining the operations is allowed! What > > more do you need? You certainly have to call Initialize at least once, and > > not call Finalize on the returned object. I could see describing the model > > you do in an AARM note, but since it says nothing at all normative (you > > can't tell the difference between it and a canonical call with S'Read > > eliminating its temporary with the first permission). [Later: I see, you > > failed to allow the temporary to be eliminated in all cases. I think you're > > over-specifying: clever implementers should be allowed to remove the S'Read > > temporary all the time.] > > I'm confused. Are you saying that the implementation permissions I'm > proposing do not grant the implementation any permissions that it > didn't already have? Would it be helpful if I tried to construct an > example to demonstrate that this is not the case? I don't get it. Where is the permission to eliminate the temporary if the discriminants change? I suppose you could claim that this is an assignment operation and that 7.6(21/2) applies. But in that case, you don't need any separate permission to eliminate the temporary here at all (and you surely need an AARM note to point out that the assignment permission applies). You still would need the permission to raise Constraint_Error early, of course, because it causes what is read to change (surely not covered by 7.6(21/2)). But the involvement of what is read (especially if something component's read raises an exception) suggests the whole permission ought to be here. In that case, it should always be acceptable to eliminate the temporary (and the 4 calls to Initialize/Finalize/Adjust/Finalize), whether or not the discriminants changes. 7.6(21/2) has no such restrictions on applicability, and neither should this permission. So, either make this a *complete* permission, or use 7.6(21/2) to the maximum extent. Don't half-and-half, which begs the question of why you would mention some of the cases when you can eliminate the temporary and not all. > > Summary: This one needs more work. > > I remain unconvinced > It sounds like we'll have some things to discuss at the meeting. I'd be stunned if we didn't. In the worst case, Pascal could give us a special tour of Paris :-), but I don't see that happening. **************************************************************** From: Randy Brukardt Date: Friday, April 5, 2007 3:19 PM At the risk of beating a dead horse, there was one question here I didn't answer... ... > > I completely hate the description of the second implementation permission. I > > think it is important to recognize that it is OK to combine the two > > operations if both S'Read and S'Input are the default versions, but I don't > > think that much else needs to be said. > > If you think there is a simpler way to specify the semantics of > combining the two operations, I'd like to hear about it. Yes. The null string. :-) Presuming you meant to invoke 7.6(21/2) [and if you didn't, its effect needs to be added to the first permission], I cannot think of a single case where an implementation that generated S'Input and it's "call" to default S'Read together could not optimize the result. And where the heck does it say that a compiler has to generate a call just because the RM says it? You need to show an example where an appropriate optimization of S'Input would not be allowed without an additional permission. It needs to be in the discussion of the AI. ****************************************************************