!standard 13.13.1 (4) 01-09-11 AI95-00227/05 !standard 13.13.1 (5) !standard 13.13.1 (8) !status Amendment 200Y 02-17-01 !class binding interpretation 00-03-15 !status WG9 approved 01-10-05 !status ARG Approved 6-0-1 00-11-19 !status work item 00-03-13 !status received 00-02-09 !priority Medium !difficulty Easy !qualifier Omission !subject Behavior of Ada.Streams.Read when at the end of stream !summary Ada.Streams.Read returns Item'First - 1 in Last when no elements are transferred. If Item'First = Stream_Element_Offset'First and no elements are transferred, Constraint_Error is raised. !question The declarations in Ada.Streams include: type Stream_Element_Offset is range ; subtype Stream_Element_Count is Stream_Element_Offset range 0 .. Stream_Element_Offset'Last; type Stream_Element_Array is array (Stream_Element_Offset range <>) of Stream_Element; procedure Read (Stream : in out Root_Stream_Type; Item : out Stream_Element_Array; Last : out Stream_Element_Offset) is abstract; Consider the following case: Strm : ; Item : Stream_Element_Array (Stream_Element_Offset'First .. Stream_Element_Offset'First + 10); Last : Stream_Element_Offset; ... Read (Strm, Item, Last); If Strm is at end of stream, such that no elements are read, what is the value of Last? !recommendation (See summary.) !wording (See corrigendum.) !discussion 13.13.1(8) says: The Read operation transfers Item'Length stream elements from the specified stream to fill the array Item. The index of the last stream element transferred is returned in Last. Last is less than Item'Last only if the end of the stream is reached. In order to answer the question, we need to know what the value of Last is when no elements are transferred. However, 13.13.1(8) doesn't say. The second sentence says that Last is set to the index of the last element transferred, but it says nothing about the value if no elements are transferred. We answer the question by appealing to analogy with the similar routine, Ada.Text_IO.Get_Line. A.10.7(20) says "If no characters are read, returns in Last an index value that is one less than Item'First." This result is known by every Ada programmer, and it seems best to define Ada.Streams.Read the same way. Having determined the value of Last, we can now answer the question. Last will be equal to Item'First - 1. If Item'First = Stream_Element_Offset'First, this value will be out of the range of the base type, and thus will raise Constraint_Error. The fact that this routine was designed to avoid raising an exception even when nothing was read is irrelevant. Users of Ada.Streams.Read ought to avoid passing in Item arrays whose lower bound is Item'First. !corrigendum 13.13.01(08) @drepl The Read operation transfers Item'Length stream elements from the specified stream to fill the array Item. The index of the last stream element transferred is returned in Last. Last is less than Item'Last only if the end of the stream is reached. @dby The Read operation transfers stream elements from the specified stream to fill the array Item. Elements are transferred until Item'Length elements have been transferred, or until the end of the stream is reached. If any elements are transferred, the index of the last stream element transferred is returned in Last. Otherwise, Item'First - 1 is returned in Last. Last is less than Item'Last only if the end of the stream is reached. !corrigendum 13.13.01(10) @dinsa See A.12.1, ``The Package Streams.Stream_IO'' for an example of extending type Root_Stream_Type. @dinst If the end of stream has been reached, and Item'First is Stream_Element_Offset'First, Read will raise Constraint_Error. !ACATS test A C-test should be created (or better, added to an existing test) to test this case. !appendix From: Randy Brukardt Sent: Wednesday, February 09, 2000 3:04 PM To: 'Ada Comment' Subject: [Ada-Comment] Behavior of Ada.Streams.Read when Last is not in the base type. The declarations in Ada.Streams include: type Stream_Element_Offset is range ; subtype Stream_Element_Count is Stream_Element_Offset range 0 .. Stream_Element_Offset'Last; type Stream_Element_Array is array (Stream_Element_Offset range <>) of Stream_Element; procedure Read (Stream : in out Root_Stream_Type; Item : out Stream_Element_Array; Last : out Stream_Element_Offset) is abstract; Consider the following case: Strm : ; Item : Stream_Element_Array (Stream_Element_Offset'First .. Stream_Element_Offset'First + 10); Last : Stream_Element_Offset; ... Read (Strm, Item, Last); If Strm is at end of stream, such that no elements are read, what is the value of Last? It ought to be Stream_Element_Offset'First - 1, but that value is out of range of the base type. Thus it appears that such a call raises Constraint_Error, which appears surprising. (At least it is preventable by the programmer.) It's surprising because the design of these routines goes out of their way to avoid raising End_Error or something like it in this case, yet here we still can get an exception. I don't see anything about this in the ARM or in any of the existing AIs. Aside: There doesn't seem to be any reason for Stream_Element_Count in Ada.Streams. It is never used anywhere so far as I can tell. I wonder if the intent was to declare the array type as type Stream_Element_Array is array (Stream_Element_Count range <>) of Stream_Element; which would have avoided the Constraint_Error problem above. I think this would be too large a change to make now anyway, so that is mainly of academic interest. ************************************************************* From: Christoph Grein Sent: Wednesday, February 09, 2000 11:53 PM Subject: Re: [Ada-Comment] Behavior of Ada.Streams.Read when Last is not in the base type. > The declarations in Ada.Streams include: > > type Stream_Element_Offset is range ; > subtype Stream_Element_Count is Stream_Element_Offset > range 0 .. Stream_Element_Offset'Last; > type Stream_Element_Array is array (Stream_Element_Offset range <>) > of Stream_Element; > > procedure Read (Stream : in out Root_Stream_Type; > Item : out Stream_Element_Array; > Last : out Stream_Element_Offset) is abstract; > ... (snip) > Aside: There doesn't seem to be any reason for Stream_Element_Count in > Ada.Streams. It is never used anywhere so far as I can tell. I wonder if the > intent was to declare the array type as > > type Stream_Element_Array is array (Stream_Element_Count range <>) of > Stream_Element; > > which would have avoided the Constraint_Error problem above. I think this > would be too large a change to make now anyway, so that is mainly of > academic interest. > > Randy Brukardt. OOPS, you're nearly right with your proposal; my 2 Euro-cents: I gather they meant something like (see Text_IO.Get_Line): type Stream_Element_Count is range 0 .. ; subtype Stream_Element_Offset is Stream_Element_Count range 1 .. Stream_Element_Count'Last; type Stream_Element_Array is array (Stream_Element_Offset range <>) of Stream_Element; procedure Read (Stream: in out Root_Stream_Type; Item : out Stream_Element_Array; Last : out Stream_Element_Count) is abstract; Then Last can safely be 0 if there is nothing in the stream. ************************************************************* From: Robert A Duff Sent: Thursday, February 10, 2000 10:51 AM Subject: Re: [Ada-Comment] Behavior of Ada.Streams.Read when Last is not in the base type. Randy, You suggest maybe we meant: > type Stream_Element_Array is array (Stream_Element_Count range <>) of > Stream_Element; The history, as I recall, is that I wrote it that way first. Then Offer Pazy argued that it should start at 1 instead of 0. Tucker settled the argument by saying the programmer can choose, and they can choose anything else, too, if they like. I don't see a big problem with the way it is -- the programmer just has to know not to declare arrays starting at the most negative number. ************************************************************* From: Randy Brukardt Sent: Thursday, February 10, 2000 11:41 AM Subject: Re: [Ada-Comment] Behavior of Ada.Streams.Read when Last is not in the base type. Bob said: "I don't see a big problem with the way it is -- the programmer just has to know not to declare arrays starting at the most negative number." You're right about it not being a big problem, but the latter part of your statement is what I was commenting on: how do they know not to do that? *I* didn't know it until I stumbled over it yesterday. There is nothing in the standard or the AARM mentioning that Constraint_Error can be raised if the Item'First = Stream_Element_Offset'First. I've started arrays at Index_Subtype'First without ever checking what that value might be, and I can imagine others doing so. There also is an implementation issue: we needed to code an explicit check of this case (since Janus/Ada runtime packages always have been distributed with checking suppressed). I wouldn't be surprised to find other compilers that failed to make this check and do something bad in this case. ************************************************************* From: Robert Dewar Sent: Thursday, February 10, 2000 2:05 PM Subject: Re: [Ada-Comment] Behavior of Ada.Streams.Read when Last is not in the base type. <<"I don't see a big problem with the way it is -- the programmer just has to know not to declare arrays starting at the most negative number." >> Note that this is a standard problem, slices don't work well in this situation either, and the problem is even more severe with enumeration types used as subscript indexes. This simply reflects an annoying flaw in the language design which cannot be fixed. It's no big deal anyway. ************************************************************* From: Christoph Grein Sent: Thursday, February 10, 2000 11:59 PM Subject: Re: [Ada-Comment] Behavior of Ada.Streams.Read when Last is not in the base type. > <<"I don't see a big problem with the way it is -- the programmer just has > to know not to declare arrays starting at the most negative number." > >> > > Note that this is a standard problem, slices don't work well in this > situation either, and the problem is even more severe with enumeration > types used as subscript indexes. > > This simply reflects an annoying flaw in the language design which cannot > be fixed. It's no big deal anyway. Robert, please elaborate. I do not see enumerations involved here. I'm no compiler builder, so I do not see a flaw that cannot be fixed by declaring Stream_Element_Array like String. Is there a fundamental need to use negative indices? If this is really a standard problem, it should be obvious to simple programmers. To me at least it's not. ************************************************************* From: Ted Baker Sent: Friday, February 11, 2000 6:22 AM Subject: Re: [Ada-Comment] Behavior of Ada.Streams.Read when Last is not in the base type. While we are revisiting Stream_Element array (and I agree with Christoph) it would not hurt to revisit the issue of needless copying of buffers that this interface imposes by not requiring the array to have aliased components. Since Stream_Elements are used to represent data of other types, the uses of this type will involve unchecked conversions of pointers to map sections of a stream_element_array onto objects of various types. In my experience, this has required me to first copy the data to/from the stream_element_array from/to an array of aliased stream elements (or aliased elements of some other byte-like type, with unchecked conversion at that point), and then do the actual I/O operation. This useless copying could be avoided if the elements of stream_element_array were aliased. ************************************************************* From: Randy Brukardt Sent: Friday, February 11, 2000 10:27 AM Subject: Re: [Ada-Comment] Behavior of Ada.Streams.Read when Last is not in the base type. The aliased element issue was already addressed by the approved AI-00181, so let's not revisit that. Indeed, this rule will be in the Corrigendum. Of course, the ARG having made the components aliased and having compilers actually implement them that way are two different things. We tried to take advantage of this change in Claw last year, and found that exactly zero out of four compilers that we tried supported it. I suspect that will change only when there is an ACATS test for it. Of course, the ARG is supposed to be creating tests for AIs, and this one is no exception. I looked to see who this test was assigned to, and it is assigned to one Ted Baker! So Ted, if you *REALLY* want to see this happen, you'll write that test. Otherwise, I doubt that implementors will actually comply with the AI; probably many of them aren't aware of it. ************************************************************* From: Pascal Leroy Sent: Friday, February 11, 2000 12:21 PM Subject: Re: [Ada-Comment] Behavior of Ada.Streams.Read when Last is not in the base type. > it would not hurt to revisit the issue of needless copying of > buffers that this interface imposes by not requiring the array to > have aliased components. This was fixed years ago. See AI95-00181. ************************************************************* From: Robert Dewar Sent: Saturday, February 12, 2000 6:51 AM Subject: Re: [Ada-Comment] Behavior of Ada.Streams.Read when Last is not in the base type. <> The point with enumerations (happens with modular types too) is that it is natural to declare arrays subscript types whose lower bound is type'first (which does not happen with signed types). The problem is that if you say type x is array (character) of integer; there is no way to reference the null string just before the first character, so you have to special case this (it particularly comes up in generics). ************************************************************* From: Ted Baker Sent: Monday, February 14, 2000 1:20 PM Subject: Re: [Ada-Comment] Behavior of Ada.Streams.Read when Last is not in the base type. ooops!........ My memory is sometimes stuck in the past. I should write the test. --Ted ************************************************************* From: Randy Brukardt Sent: Monday, March 13, 2000 1:31 PM I was writing up a confirmation AI on the stream elements question of last month (we have to consider an AI because I stupidly posted the message to Ada-Comment rather than here). Anyway, I had to fudge it a bit. 13.13.1(8) says in part: The index of the last stream element transferred is returned in Last. Last is less than Item'Last only if the end of the stream is reached. This says nothing about the value of Last if no elements are transferred. I think everyone has *assumed* that it is Item'First - 1, but the wording above seems to leave it undefined (although it ought to be less than Item'Last). Is this defined somewhere else? If not, perhaps we ought to use the AI to repair this??? ************************************************************* From: Tucker Taft Sent: Tuesday, March 14, 2000 12:54 PM Why is this necessary to specify? Isn't it Ok to leave it implementation-defined (i.e., the proper check is "Last < Item'First" rather than "Last = Item'First - 1")? ************************************************************* From: Randy Brukardt Sent: Tuesday, March 14, 2000 3:01 PM I suppose it isn't necessary to specify a particular value (although why we would want to bring all of the problems of super-null arrays into this case is beyond me), but it certainly is necessary to specify *something* about the value. The text does *not* specify that the value of Last must be less than Item'First; indeed it makes no requirements at all. Note that it says "Last is less than Item'Last only if the end of stream is reached." It does *not* say "If the end of stream is reached, Last is less than Item'Last.". So, returning Last = Item'Last + 1 would meet all of the requirements of the paragraph in the case where nothing was transferred. Indeed, if I wanted to split hairs, I could argue that the sentence "The index of the last stream element transferred is returned in Last." doesn't apply at all in this case, because nothing was transferred. That would make Last an uninitialized variable in that case: certainly a result we don't want. ************************************************************* From: Tucker Taft Sent: Wednesday, March 15, 2000 9:26 AM Randy Brukardt wrote: > ... > Indeed, if I wanted to split hairs, I could argue that the sentence "The > index of the last stream element transferred is returned in Last." doesn't > apply at all in this case, because nothing was transferred. That would make > Last an uninitialized variable in that case: certainly a result we don't > want. I now agree with you that the wording needs fixing. I would rewrite it in the spirit of the wording used for Get_Line -- A.10.7(20). ************************************************************* From: Tucker Taft Sent: Wednesday, March 15, 2000 2:45 PM Randy Brukardt wrote: > ... > > @drepl > The Read operation transfers Item'Length stream elements from the > specified stream to fill the array Item. The index of the last stream > element transferred is returned in Last. Last is less than Item'Last only if > the end of the stream is reached. > @dby > The Read operation transfers Item'Length stream elements from the > specified stream to fill the array Item. If elements are transferred, the > index > of the last stream element transferred is returned in Last. If no elements > are > transferred, Item'First - 1 is returned in Last. Last is less than Item'Last > only if the end of the stream is reached. This wording still seems a little screwy. Why say "the Read operation transfers Item'Length..." and then immediately thereafter contradict it? We should indicate that it transfers Item'Length stream elements, or all of the stream elements until the end of the stream, whichever is less. ************************************************************* From: Randy Brukardt Sent: Wednesday, March 15, 2000 7:34 PM I of course made the minimum change. I have no objection to a more sweeping change. *************************************************************