Version 1.2 of ais/ai-00195.txt

Unformatted version of ais/ai-00195.txt version 1.2
Other versions for file ais/ai-00195.txt

!standard 13.13.1 (00)          98-04-04 AI95-00195/02
!class binding interpretation 98-03-27
!status received 98-03-27
!reference AI95-00108
!reference AI95-00145
!priority High
!difficulty Hard
!subject Streams
!summary 98-04-04
When the predefined Input attribute creates an object, this object undergoes initialization and finalization.
In determining whether a stream-oriented attribute has been specified for a type, the normal visibility rules are applied to the attribute_definition_clause.
For a derived type which is limited, the attributes Read and Write are inherited, and the attributes Input and Output revert to their predefined definition (i.e. they cannot be called). (This amends AI95-00108.)
In the profiles of the stream-oriented attributes, the notation "italicized T" refers to the base type for a scalar type, and to the first subtype otherwise.
For an untagged derived type with new discriminants that have defaults, the predefined stream-oriented attributes read or write the new discriminants, not the old ones. (This amends AI95-00108.)
The predefined Read attribute for composite types with defaulted discriminants must ensure that, if exceptions are raised by the Read attribute for some discriminant, the discriminants of the actual object passed to Read are not modified. This may require the creation of an anonymous object, which undergoes initialization and finalization.
The predefined Read attribute for composite types with defaulted discriminants must raise Constraint_Error if the discriminants found in the stream differ from those of the actual parameter to Read, and this parameter is constrained.
If S is a subtype of an abstract type, an attribute_reference for S'Input is illegal unless this attribute has been specified by an attribute_definition_clause.
The number of calls performed by the predefined implementation of the stream- oriented attributes to the Read and Write operations of the stream type is unspecified, but there must be at least one such call for each top-level call to a stream-oriented attribute.
The predefined stream-oriented attributes for a scalar type shall only read or write the minimum number of stream elements required by the first subtype of the type. Constraint_Error is raised if such an attribute is passed (or would return) a value outside the range of the first subtype.
In an attribute_definition_clause for a stream-oriented attribute, the name shall not denote an abstract subprogram.
!question 98-04-04
1 - RM95 13.13.2(27) states that S'Input "creates an object (with the bounds or discriminants, if any, taken from the stream), initializes it with S'Read, and returns the value of the object."
Does the verb "initialize" in this sentence refer to the entire initialization process mentioned in RM95 3.3.1(18) and 7.6(10)? (yes) In particular, if S is a controlled subtype, or if it contains controlled components, is the Initialize subprogram called? (yes) Is the Finalize subprogram called when the intermediate object is finalized? (yes) For a record type whose components have initial values, are these values evaluated? (yes)
2 - RM95 13.13.2(36) states that "an attribute_reference for one of these attributes is illegal if the type is limited, unless the attribute has been specified by an attribute definition clause."
If some stream-oriented attribute has been specified in a private part, and we are at a point that doesn't have visibility over that private part, is a reference to the attribute legal? (no)
3 - Let T be a limited type with an attribute_definition_clause for attribute Read, and D a type derived from T, and assume that there is no attribute_definition_clause for D'Read. Is a usage of D'Read legal? (yes)
4 - The definition of the profiles of S'Read, S'Write, S'Input and S'Output given in RM95 13.13.2 uses the notation "italicized T" for the type of the Item parameter. What is the meaning of "italicized T" in this context? (T'Base for scalars, first subtype otherwise.)
5 - AI95-00108 states (in the !discussion section) that "for untagged derived types, there is no problem for the derived type inheriting the stream attributes."
This doesn't seem clear if the derived type includes a known discriminant part. Consider:
type Parent (D1, D2 : Integer := 1) is ...; type Child (D : Integer := 2) is new Parent (D1 => D, D2 => D);
Clearly Parent'Write writes two discriminant values. How many discriminants does Child'Write write? (one)
6 - RM95 13.13.2(9) states that for a record type, the predefined S'Read reads the components in positional aggregate order. However, the language doesn't seem to specify what happens when exceptions are raised by the calls to the Read attribute for the components. Consider for example the following type declarations:
type T1 is range ...; type T2 is range ...;
type R (D1 : T1 := ...; D2 : T2 := ...) is record ... end record;
Say that attributes_definition_clauses have been given for T1'Read and T2'Read, and consider a call to R'Read. Assume that, during this call, an exception is raised by T2'Read. Is the discriminant X.D1 modified? (no)
7 - Consider a call to T'Read where T is a type with defaulted discriminants. If the discriminants found in the stream have values different from those of the discriminants of the object passed to T'Read for the Item parameter, and that object is constrained, is Constraint_Error raised? (yes)
8 - If T is an abstract type, is the function T'Input abstract? (no, but it cannot be called)
9 - RM95 13.13.1(1) states that "T'Read and T'Write make dispatching calls on the Read and Write procedures of the type Root_Stream_Type." Is the number of those calls specified? (no)
10 - For a scalar type T, the second parameter of the predefined stream attributes is of type T'Base. Given the same type declaration for T, different compilers may choose different base ranges, and therefore write different numbers of storage units to the stream. This compromises portability, and makes it difficult to use streams to match a file format defined externally to Ada.
11 - In an attribute_definition_clause for a stream attribute, is it legal to give a name that denotes an abstract subprogram? (no)
!recommendation 98-04-04
See summary.
!wording 98-04-04
See summary.
!discussion 98-04-04
1 - It seems logical to make Input follow as closely as possible the semantics of code that could be written by a user, so the implementation of S'Input should obtain the discriminants or bounds from the stream, and then declare an object with appropriate discriminants or bounds, as in:
declare Anon : S (<discriminants or bounds taken from the stream>); begin S'Read (..., Anon); end;
Accordingly, the initialization described in RM95 3.3.1(8-9) and RM95 7.6(10) takes place when Anon is declared, and the finalization described in RM95 7.6.1 takes when Anon disappears (and before returning from the call to S'Input).
Note that as part of initialization, compiler-specific fields are initialized as required by the implementation (and as permitted by AARM 3.3.1(14.a)).
2 - Consider for example:
package P is type T is limited private; private procedure Read (Stream : ...; Item : out T); for T'Read use Read; end P;
with P; procedure Q is X : P.T; begin P.T'Read (..., X); -- Illegal? end Q;
The call to P.T'Read is illegal, because Q doesn't have visibility over the private part of P, which contains the attribute_definition_clause for attribute Read. On the other hand, at a place that has visibility over the private part of P (and that comes after the attribute_definition_clause) a reference to T'Read is legal. This rule is necessary to preserve the privacy of private types.
Note that it is the location of the attribute_definition_clause that counts, not that of the subprogram specified in that clause. Thus, if the procedure Read above were moved to the visible part of P, a reference to P.T'Read would still be illegal (but a reference to P.Read wouldn't).
3 - AI95-00108 states that "for a type extension, the predefined Read attribute is defined to call the Read of the parent type, followed by the Read of the non-inherited components, if any, in canonical order."
This rule doesn't work for limited (tagged) types, because the non- inherited components might include protected objects or tasks for which the predefined Read and Write attributes cannot be called.
For limited derived types (tagged or not), the only sensible rule is that Read and Write are inherited "as is". This is consistent with what happens with the operator "=". On the other hand the attributes Input and Output cannot be inherited, as explained in the discussion of AI95- 00108. Therefore, these attributes must revert to their predefined definition, which means that they cannot be called, as stated in RM95 13.13.2(36).
4 - AI95-00145 specifies the meaning of the notation "italicized T" for operators as follows:
"The italicized T shown in the definitions of predefined operators means:
- T'Base, for scalars - the first subtype, for tagged types - the type without any constraint, in other cases"
In the case of stream-oriented attributes the notation "italicized T" must be consistent with the parameter subtype required for attribute_definition_clauses. If we chose the same rule as for operators, we would have a discrepancy in the case of constrained untagged types, and this would unnecessarily complicate the static and dynamic semantics.
When one of the stream-oriented attributes is specified by an attribute definition clause, RM95 13.13.2(36) states that "the subtype of the Item parameter shall be the base subtype if scalar, and the first subtype otherwise."
Therefore, in the parameter profiles for the stream-oriented attributes in section 13.13, the notation "italicized T" means:
- T'Base, for scalars - the first subtype, in other cases
5 - The inheritance rule given in AI95-00108 should only apply to those attributes that have been specified for the parent type.
If this rule was applied to the predefined stream-oriented attributes, it would require, in the example given, to read or write two discriminants, because the predefined Read and Write attributes of type Parent do read or write two discriminants. But that would be inconsistent with the rule given in RM95 13.13.2(9): "the Read or Write attribute for each component is called in canonical order," since D1 and D2 are not components of type Child.
Furthermore, definiteness can be changed by type derivation, and the dynamic semantics of Read and Write depend on definiteness. Consider the following modification of the original example:
type Parent (D1, D2 : Integer) is ...; type Child (D : Integer := 2) is new Parent (D1 => D, D2 => D);
In this case the predefined stream-oriented attributes for type Parent do not read or write the discriminants, so applying the inheritance rule of AI95- 00108 would cause the stream-oriented attributes for Child to not read or write any discriminant, which doesn't make sense.
Therefore, RM95 13.13.2(9) must have precedence, and the predefined stream- oriented attributes for Child only read or write exactly one discriminant, D.
The underlying model is that the predefined stream-oriented attributes are created anew for each type declaration, based on the structure of the type, much like predefined operators.
6 - The problem mentioned in the question only exists for a type R that is passed by-reference to R'Read: obviously if the type is passed by copy, an exception raised by R'Read cannot affect the original object.
In the case of a type which is passed by-reference, we must consider two cases:
- If the exception in T2'Read is due to the failure of some
language-defined check, RM95 11.6(6) explains that the object or its parts may become abnormal, so we don't have to specify what happens to the discriminants.
- If the exception in T2'Read is due to an explicit raise, the object
obviously doesn't become abnormal, and therefore we must preserve the integrity of its discriminants. In other words, either all discriminants are updated (if the calls to the Read attributes for the discriminants were successful) or none (if any call to a Read attribute for a discriminant raised an exception).
This model requires an implementation to read the discriminants, create an anonymous object using the given discriminants and assign that object to the formal parameter Item. The normal initialization and finalization take place for this anonymous object.
Strictly speaking, the use of an anonymous object is only required for a type with defaulted discriminants which is passed by-reference, if the actual parameter of Read is not constrained. However, an implementation is free to use anonymous objects in other cases.
Use of an anonymous object is only required for discriminants. An implementation is free to read the other components directly in the Item parameter. For example, if we change the original example as follows:
type R is record C1 : T1; C2 : T2; end record;
then if T2'Read raises an exception when reading component C2, it is unspecified if C1 is modified or not.
7 - When the type has defaulted discriminants, the predefined Read attribute must read them from the stream. It can be the case that the actual object passed to Read is constrained. In this case, the discriminants found in the stream may or may not match those of the actual. If they don't match, Constraint_Error is raised, and this is a Discriminant_Check.
It is unspecified whether this effect is achieved by assigning a temporary object, as explained in #6 above, or by other means.
8 - If T is an abstract type, calling the function T'Input would effectively create an object whose tag designates T, which is absurd. We could decide that T'Input is abstract, but it seems simpler to say that any attribute_reference for this attribute is illegal, by analogy with the rule stated in RM95 13.13.2(36) for limited types.
9 - Surely the user could count the calls to the Read and Write operations for the stream type by writing a pervert implementation of these operations, but it seems that performance should have precedence: for the predefined implementation of String'Write, requiring a call to Ada.Streams.Write for each character would be a big performance hit, with no real benefits.
Therefore, the number of calls to the Read and Write operations is unspecified, and implementations are free (and in fact advised) to do internal buffering. However, we don't want to allow an implementation to buffer all stream output and do only one call to Write when the program terminates: the user must be able to assume reasonable properties regarding the underlying buffering mechanism. That's why we require at least one call to Read or Write for each top-level call to a stream-oriented attribute. In other words, an implementation should not perform buffering across top-level calls.
10 - Consider the declaration:
type T is range 1 .. 10;
This declaration "defines an integer type whose base range includes at least the values [1 and 10] and is symmetric around zero, excepting possibly an extra negative value," as explained in RM95 3.5.4(9).
Based on this rule (and assuming typical hardware), an implementation might choose an 8-, 16-, 32- or 64-bit base type for T.
RM95 13.13.2(17) advise the implementation to "use the smaller number of stream elements needed to represent all values in the base range of the scalar type" when reading or writing values of type T.
Clearly this is a portability issue: if two implementation use (as is typical) 8-bit stream elements, but have different rules for selecting base types, the number of elements read to or written from a stream will differ. This makes it very hard to write stream operations that comply with an externally defined format.
In the above case, it would seem reasonable to read or write only the minimum number of stream elements necessary to represent the range 1 .. 10. This would remove the dependency on the base type selection, and make it easier to write portable stream operations. (There is still the possibility that different implementations would choose different sizes for stream elements, but that doesn't seem to happen in practice on typical hardware.)
The only issue with that approach is that the stream-oriented attributes for scalar types have a second parameter of type T'Base, e.g.:
procedure S'Write (Stream : access Ada.Streams.Root_Stream_Type'Class;
Item : in T'Base);
So one might call T'Write with the value 1000 for the Item parameter, and this might exceed the range representable in the stream. However, this usage is non-portable in the first place (because it depends on the choice of base range), so it doesn't seem important to preserve it. In fact any attempt at reading or writing a value outside the range of the first subtype is highly suspicious.
Based on this reasoning, the following rules are added. Note that these rules are Dynamic Semantics rules, not Implementation Advices:
- The predefined stream-oriented attributes for a scalar type T shall
only read or write the minimum number of stream elements necessary to represent the first subtype. If S is the first subtype, the number of stream elements read to or written from the stream is exactly:
(S'Size + Stream_Element'Size - 1) / Stream_Element'Size
- If Write or Output is called with a value of the Item parameter outside
the range of the first subtype, Constraint_Error is raised. This check is a Range_Check.
- If the value extracted from the stream by Read or Input is outside the
range of the first subtype, Constraint_Error is raised. This check is a Range_Check.
11 - Obviously it should not be possible to perform a non-dispatching call to an abstract subprogram (stream-oriented attributes are always called in a non- dispatching manner). Therefore, we have two options:
- Make the attribute_definition_clause illegal. - Make the calls (explicit or implicit) illegal.
The second option is a significant implementation burden, and allowing the attribute_definition_clause only to reject all calls doesn't seem to do any good. That's why the first option was preferred.
!appendix

!section 13.13.1
!subject Various issues with the stream-oriented attributes
!reference RM95 13.13.1
!reference RM95 13.13.2
!reference AI95-00108
!from Pascal Leroy 97-09-02
!reference 1997-15783.a Pascal Leroy 1997-9-2>>
!discussion

1 - RM95 13.13.2(27) states that S'Input "creates an object (with the bounds
or discriminants, if any, taken from the stream), initializes it with S'Read,
and returns the value of the object.

Does the verb "initialize" in this sentence refer to the entire initialization
process mentioned in RM95 3.3.1(18) and 7.6(10)? In particular, if S is a
controlled subtype, or if it contains controlled components, is the Initialize
subprogram called?  Is the Finalize subprogram called when the intermediate
object is finalized?  For a record type whose components have initial values,
are these values evaluated?

It seems that the simplest model is that S'Input declares a local object,
which is then passed to S'Read, as in:

                X : S (<discriminants taken from the stream>);
        begin
                S'Read (..., X);

If we don't do that, the object passed to S'Read might have uninitialized
access values, or uninitialized compiler-specific fields (dopes, offsets, tag,
etc.).

2 - RM95 13.13.2(36) states that "an attribute_reference for one of these
attributes is illegal if the type is limited, unless the attribute has been
specified by an attribute definition clause."

If some stream-oriented attribute has been specified in a private part, and we
are at a point that doesn't have visibility over that private part, is a
reference to the attribute legal? For example:

        package P is
                type T is limited private;
        private
                procedure Read (Stream : ...; Item : out T);
                for T'Read use Read;
        end P;

        with P;
        procedure Q is
                X : T;
        begin
                T'Read (..., X);        -- Legal?
        end Q;

One hopes that the call to T'Read is illegal, overwise it would break the
privacy of private types.

3 - Let T a limited type with an attribute_definition_clause for attribute
Read, and D a type derived from T, and assume that there is no attribute
definition clause for D'Read:

        type T is limited record ... end record;
        for T'Read use ...;

        type D is new T;

Is a usage of D'Read legal or not?  In other words, shall we consider that,
for the purpose of checking RM95 13.13.2(36), "an attribute has been
specified" for D?

If the answer is "yes", how do the inheritance rules stated in AI95-00108
apply in the case where T is tagged, and D has a component of a task type in
its record extension part?  How is an implementation expected to read that
component?

4 - The definition of the profiles of the predefined S'Read, S'Write, S'Input
and S'Read given in RM95 13.13.2 uses the notation "T italic" for the type of
the Item parameter.  AI95-00145 explains that:

"The italicized T shown in the definitions of predefined operators means:

     - T'Base, for scalars
     - the first subtype, for tagged types
     - the type without any constraint, in other cases"

(Note that the above sentence says "operators", so strictly speaking it
doesn't apply to attributes; it seems that the wording of this AI should be
broadened.)

When one of these attributes is specified by an attribute definition clause,
RM95 13.13.2(36) states that "the subtype of the Item parameter shall be the
base subtype if scalar, and the first subtype otherwise."

There is a problem because these definitions don't coincide in the case of
constrained array types.  Consider:

        type T is new String (1 .. 10);

It appears that the type of the Item parameter of the predefined
stream-oriented attributes is "T without any constraint" (an anonymous
unconstrained array type ), while the type of the Item parameter for a
user-defined stream-oriented subprogram is T.

In particular, does the presence or absence of an attribute definition clause
have any bearing on the legality of the calls?  For example, consider the
legality of the 'others' choice in an array aggregate; it depends on the
existence on an applicable index constraint, and therefore on whether the type
of the parameter is constrained or not:

        package P is
                type T is new String (1 .. 10);
                procedure Write (...; Item : in T);
        private
                for T'Write use Write;
        end P;

        with P;
        procedure Q is
        begin
                T'Write (..., (others => 'a')); -- Legal?
        end;

Is the above call legal? If yes, would it become illegal if the
attribute_definition_clause were removed?

A possible model is to say that an attribute_definition_clause changes the
body of the Read attribute, but not its profile.  It's as if the predefined
Read was just a wrapper calling the user-specified subprogram.

5 - AI95-00108 states (in the !discussion section) that "for untagged derived
types, there is no problem for the derived type inheriting the stream
attributes."

This doesn't seem clear if the derived type includes a known discriminant
part.  Consider:

        type Parent (D1, D2 : Integer := 1) is ...;
        type Child (D : Integer := 2) is new Parent (D1 => D, D2 => D);

Clearly Parent'Write writes two discriminant values.  It would seem that
Child'Write should only write one discriminant value, which contradicts the
simple inheritance rule given in the AI.

6 - RM95 13.13.2(9) states that for a record type, the predefined S'Read reads
the components in positional aggregate order.  However, the RM95 doesn't seem
to specify what happens when exceptions are raised by the calls to the Read
attribute for the components.  Consider for example the following type
declarations:

        type T1 is range ...;
        type T2 is range ...;

        type R (D1 : T1 := ...; D2 : T2 := ...) is
                record
                        ...
                end record;

Say that attributes_definition_clauses have been given for T1'Read and
T2'Read, and consider the call:

        X : R;
        ...
        R'Read (..., X);

Assume that an exception is raised by T2'Read.  Is the discriminant X.D1
modified?  That would be unpleasant if there were components depending on this
discriminant!

Consider a different example, where there are no discriminants:

        type T1 is ...;
        type T2 is ...;

        type R is
                record
                        C1 : T1;
                        C2 : T2;
                end record;

Assume that an exception is raised by T2'Read.  Is the component X.C1
modified?

It would seem that we should stick to the notion that discriminants are only
modified by an assignment of entire objects.  This probably requires an
intermediate object in the case of discriminated types.  However, it would
seem quite expensive to require the creation of such an intermediate object
for types that don't have discriminants.

Note that if we adopt the idea that an intermediate object is needed (at least
in some cases) then we must define if initialization and finalization of this
object take place.

7 - Consider a call to T'Read where T is a type with defaulted discriminants.
 If the discriminants found in the stream have values different from those of
the discriminants of the object passed to T'Read for the Item parameter, and
that object is constrained, is Constraint_Error raised?

Note that if we adopt the model that an intermediate object is needed in this
case, then the constraint check performed when assigning the intermediate
object to the actual parameter will raise Constraint_Error.

8 - If T is an abstract type, is the function T'Input abstract?  One hopes so,
otherwise it makes it possible to create objects of an abstract type.

9 - RM95 13.13.1(1) states that "T'Read and T'Write make dispatching calls on
the Read and Write procedures of the type Root_Stream_Type."

Is the number of those calls specified?  For example, for the predefined
implementation of String'Write, is an implementation required to call
Ada.Streams.Write for each character? or is it allowed to perform internal
buffering and to call Ada.Streams.Write only once for the entire string?
 Obviously the user could tell the difference by writing a pervert
implementation of the Write subprogram, but it would seem that performance
considerations should have the priority in this case.

_____________________________________________________________________
Pascal Leroy                                    +33.1.30.12.09.68
pleroy@rational.com                             +33.1.30.12.09.66 FAX

****************************************************************

!section 13.13.2(17)
!subject Stream size of 16 bit integers
!reference RM95 13.13.2 (17)
!reference 1998-15826.a Stephen Leake 98-03-14
!reference 1998-15827.a Tucker Taft 1998-3-17
!from Randy Brukardt 98-03-18
!keywords base type, size clause, stream representation
!reference 1998-15828.a Randy Brukardt  1998-3-19>>
!discussion

We just ran into this problem.  We were trying to use Streams to read
Microsoft Windows bitmap files.  Since the file format is defined external
to Ada, and is not very friendly to Ada, it is fairly difficult.  We tried
various stearm solutions that worked on GNAT, but not on ObjectAda, because
of the 16-bit vs. 32-bit problem.  Since we can't control the stream
representation, we can't use Streams to portably read this externally
defined file format.

We ended up reading an array of Storage_Elements, then converting that with
Unchecked_Conversion.  Yuk!

This problem should be solved; otherwise, Ada 95 will not be able to
portably read files with a definition outside of Ada.

                        Randy.

****************************************************************

Questions? Ask the ACAA Technical Agent