Version 1.3 of acs/ac-00081.txt

Unformatted version of acs/ac-00081.txt version 1.3
Other versions for file acs/ac-00081.txt

!standard 3.08(12)          03-09-17 AC95-00081/01
!class amendment 03-09-17
!status received no action 03-09-17
!status received 03-09-13
!subject (null array) aggregate
!summary
!appendix

!topic Proposal for a "(null array)" aggregate
!reference RM95-4.3.3(2)
!from Niklas Holsti 03-09-26
!keywords null arrays, array aggregates, private types
!discussion

Summary

   The expression '(null array)' should be allowed as an array aggregate
   and understood to mean an array of the type and dimensions expected
   in the context, with null index ranges.

Existing similar constructs

   1. '(null record)' for record aggregates with no components.
   2. The expression "" for null strings.

Expected impact on compilers

   Mainly syntactic sugar. Possibly a new place where overloading
   resolution is needed, but then the existing array-aggregate
   notations can also include overloaded parts and need resolution.

Motivation

   Currently, a null array aggregate must be written with an explicit
   specification of the null index range and the value of the
   non-existent array component. For example, given

      type Vector_Type is array (Positive range <>) of Float;

   a null Vector_Type aggregate can be written as

      (1 .. 0 => 0.0)

   The drawbacks of this are:

   a) The index range is usually irrelevant, but must be given.
   b) The component value is certainly irrelevant, but must be given.

   Point (b) is particularly troublesome when the component type is a
   private type or a private formal generic parameter. Examples follow.

Example 1: An array with a private component type.

   Assume the following package declaration:

      package Secrets is
         type Secret is private;
         type Secret_List is array (Positive range <>) of Secret;
         procedure Publish (My_Secrets : in Secret_List);
      private
         ...
      end Secrets;

   A client of Secrets that wants to publish an empty list of secrets
   must currently declare a dummy variable with an apparently undefined
   initial value and probably irrelevant index bounds:

      Null_List : Secret_List (1 .. 0);
      ...
      Publish (My_Secrets => Null_List);

   The proposal would allow simply

      Publish (My_Secrets => (null array));

   which gets rid of the irrelevant index bounds 1 .. 0 and the
   apparent undefined value of the Null_List variable, and their
   potential to confuse the human reader and analysis tools.

   In the current standard, the provider of the private type may
   try to help its clients to create empty lists of secrets by
   defining a dummy constant secret value:

      package Secrets is
         ...
         No_Secret : constant Secret;
         ...
      private
         ...
      end Secrets

    Then, a client can publish an empty secrets list by

       Publish (My_Secrets => (1 .. 0 => No_Secret));

    However, in the private part of the Secrets package declaration
    there must be a definition of No_Secret, of the form

         No_Secret : constant Secret := <expression>;

    The <expression> is often an aggregate, and often this aggregate
    uses private types defined in other packages, forcing also these
    packages to provide dummy constants of these types, etc. The
    contagion spreads...

Example 2: Formal generic type parameter

   Assume the following declaration of a package that implements
   flexible but bounded one-dimensional arrays, generic in the index
   type, component type and a normal (fixed-length) array type:

      generic
         type Index is (<>);
         type Component is private;
         type Vector is array (Index range <>) of Component;
      package Bounded_Vectors is
         type Bounded_Vector (Max_Length : Natural) is private;
         ...
         function To_Vector (Item : Bounded_Vector) return Vector;
         -- Converts the bounded vector to an ordinary vector.
         ...
      end Bounded_Vectors;

   Depending on the services provided in the package, there may be
   several places in the package body where a null Vector is
   needed, but again a null array aggregate cannot be written
   because it needs to have a dummy Component value, which is not
   available. The body must resort to apparently undefined null
   Vector variables, as in Example 1.

   If a new formal parameter for a dummy Component is added to the
   generic formals, the instantiation must provide a concrete value.
   If the type is actually private even at the instantiation point,
   or contains private components, the contagion spreads as in
   Example 1.

Minor things

   The qualified form Some_Array_Type'(null array) should also be
   allowed.

   The issue of multi-dimensional arrays may need more thought.
   It seems more likely that the actual index ranges are significant
   for multi-dimensional null arrays than for single-dimensional
   null arrays; it may be significant to know which dimensions
   are null ranges. This can be expressed by the ordinary aggregate
   notation. The '(null array)' notation should be usable as a
   subaggregate, which allows the index range of the "top"
   dimensions to be specified. For example, a two-dimensional
   array aggregate with the first index range 3 .. 7 and the
   second index range null could be expressed as

      (3 .. 7 => (null array))

   The analogous aggregate with the first index range null and
   the second index range 3 .. 7 cannot be expressed with the
   '(null array)' form, but has to be written in the currently
   accepted form and must specify both index ranges and a dummy
   component value.

Potential problems

   1. Although the index range of a null array is usually irrelevant
      to the application program, clearly '(null array)' must have
      some First and Last attributes. What should they be? Can this
      be solved in the same way as for an array aggregate in
      positional notation?

   2. If the index type contains only a single value (for example,
      an enumeration with a single literal) it is not clear to me
      if null arrays can exist. Perhaps '(null array)' should be
      illegal or cause a bounded error in such cases.

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

From: Adam Beneschan
Sent: Friday, September 26, 2003  3:08 PM


> !topic Proposal for a "(null array)" aggregate
> !reference RM95-4.3.3(2)
> !from Niklas Holsti 03-09-26
> !keywords null arrays, array aggregates, private types
> !discussion
>
> Summary
>
>    The expression '(null array)' should be allowed as an array aggregate
>    and understood to mean an array of the type and dimensions expected
>    in the context, with null index ranges.

It seems to me that this proposal has come up before, possibly before
the Ada 95 standard was written, and was rejected.  Personally, I
think it's a good idea.  I do find myself writing things like (1 .. 0
=> blah-blah-blah) often, or defining a *variable* with a null range
(which I can't define as a constant, even though it is a constant),
and it seems silly.

> Potential problems
>
>    1. Although the index range of a null array is usually irrelevant
>       to the application program, clearly '(null array)' must have
>       some First and Last attributes. What should they be? Can this
>       be solved in the same way as for an array aggregate in
>       positional notation?

It probably can be solved in the same way as for the null string
literal.  For string literals, 4.2(10) says "The bounds of this array
value are determined according to the rules for positional_array_-
aggregates (see 4.3.3), except that for a null string literal, the
upper bound is the predecessor of the lower bound.  4.3.3(26) says
that in such a case, the lower bound is "that of the corresponding
index range in the applicable index constraint, if defined, or that of
the corresponding index subtype, if not".  The index range of a null
array could be determined in the same way.  (If (null array) is
allowed to refer to an entire multi-dimensional array, this would
apply to each index range.)

The fly in the ointment here is that it doesn't handle cases in which
the lower bound of the index subtype has no predecessor.  In fact,
this is already the case with string literals:

    procedure Test89 is

        type Str is array (Integer range <>) of Character;

        procedure Proc (S : Str) is
        begin
            ...
        end Proc;

    begin
        Proc ("");
    end Test89;

This program is illegal, because for the array represented by the null
string literal, the language rules say the lower bound is
Integer'First and the upper bound is the predecessor of the lower
bound, which does not exist.  (See also AI95-138.)  This feature is
unlikely to cause a problem for string literals, since the predefined
String type has an index range of Positive.  However, it would cause
serious problems for a (null array) type, since it would be much more
common to use this for an array whose index subtype is Integer (or
some other integer type) or an enumeration type.

If (null array) is added to the language, I'd propose modifying
4.3.3(26) so that if there is no applicable index constraint, the
lower bound is that of the corresponding index subtype, except that in
the case of a null array aggregate or null string literal, if the
lower bound of the corresponding index subtype has no predecessor,
then the lower bound of the aggregate is the *successor* of the lower
bound of the corresponding index subtype.  If, in this case, the lower
bound has no successor, Constraint_Error would be raised, as specified
in 3.5(22).  (We could make 4.9(34) apply too, but we'd have to
modify the rest of 4.9 to indicate when (null array) would be
considered a "static expression".)  This would take care of the next
issue:

>    2. If the index type contains only a single value (for example,
>       an enumeration with a single literal) it is not clear to me
>       if null arrays can exist. Perhaps '(null array)' should be
>       illegal or cause a bounded error in such cases.

More likely, "should be illegal or cause Constraint_Error to be
raised".

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

From: Randy Brukardt
Sent: Monday, September 29, 2003  7:55 PM

AI-287 gives us <> to mean the default value of a component. So
    (1..0 => <>)
is a null array. It's a minor pain to look up the index subtype (but I usually
just use 1..0 and see if the compiler chokes), but it's not that big of a deal.

The alternative is writing a number of pages of RM rules to make these useful
(and then having people confused by the rules, whatever they are). Hard to say
if it is worth it.

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

From: Tucker Taft
Sent: Monday, September 29, 2003  8:16 PM

Good point.  That seems to answer the need for a null array
adequately.  Interestingly, "(null record)" can presumably
be represented with "(others => <>)" obviating the need
for the special "(null record)" syntax as well.

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

From: Gautier de Montmollin
Sent: Tuesday, September 30, 2003  5:07 AM

# AI-287 gives us <> to mean the default value of a component. So
#      (1..0 => <>)
# is a null array. It's a minor pain to look up the index subtype (but I
# usually just use 1..0 and see if the compiler chokes), but it's not that big
# of a deal.

But it is still a tricky workaround for a missing expression (with the
exception of the String type). Try to imagine you just discover Ada now...

# The alternative is writing a number of pages of RM rules to make these
# useful (and then having people confused by the rules, whatever they are).
# Hard to say if it is worth it.

If the (null array) corresponds, say, to an array expression with one
dimension having a null range, then it *should not* take pages. On the other
hand, if it you have to write pages, it means that the null array, including
in the present workaround form(s), is an undocumented feature of Ada95 which
should be defined in the next standard! In both cases it is worth...

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

From: Randy Brukardt
Sent: Tuesday, September 30, 2003  5:48 PM

Gautier de Montmollin said:

> But it is still a tricky workaround for a missing expression (with the
> exception of the String type). Try to imagine you just discover Ada now...

The standard way to write array literals is tricky???

> # The alternative is writing a number of pages of RM rules to make these
> # useful (and then having people confused by the rules, whatever they are).
> # Hard to say if it is worth it.
>
> If the (null array) corresponds, say, to an array expression with one
> dimension having a null range, then it *should not* take pages.

Please go back and read Adam's message; he explains why the rules used for
string literals or array aggregates won't work for (null array).

> On the other hand, if it you have to write pages, it means that the null
> array, including in the present workaround form(s), is an undocumented
> feature of Ada95 which should be defined in the next standard! In both cases
> it is worth...

The existing rules are fine for the existing forms; there's no problem with
the RM. (null array) is quite different.

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

From: Gautier de Montmollin
Sent: Tuesday, September 30, 2003  9:10 PM

# > But it is still a tricky workaround for a missing expression (with the
# > exception of the String type). Try to imagine you just discover Ada now...

# The standard way to write array literals is tricky???

The standard way to write *empty* array literals *is* tricky.
I did search in vain in the RM a mention of it (how do they compare ?
do they have an address ?) or at least an example. Is it documented
somewhere ?

[...]
# Please go back and read Adam's message; he explains why the rules used
# for string literals or array aggregates won't work for (null array).

You are right. I just waked up dreaming of Matrix'((null array)) and things
like (others=>(null array)), (null array). Yes, it will take lines. But yes,
it is worth the effort. Ada should have a proper expression for an empty set
(mathematically speaking)!

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

From: Nick Roberts
Sent: Tuesday, September 30, 2003 10:37 PM

If we introduce (null array) and define it as strictly equivalent to (A..B
=> <>) where A is S'First and S is the expected subtype and B is S'Pred(A),
that shouldn't be too difficult to define. It would not be permitted for
multidimensional arrays. RM95 4.2 (10) could be very slightly simplified to
define a null string literal as equivalent to (null array).

It would have the consequence that if A turns out to be the first value of
the underlying type (so it has no predecessor), the construct is illegal
(and raises Constraint_Error). I think this would be just right; in such
cases you must supply the bounds explicitly. You must supply the bounds for
multidimensional arrays.

For example:

   type Day is (Mon, Tue, Wed, ..., Sun); -- RM95 3.5.1 (14)
   subtype Weekday is Day range Mon..Fri; -- ibid (16)
   subtype Weekend is Day range Sat..Sun;

   type Basic_Pay is array (Day range <>) of Money;
   type Piece_Pay is array (Weekday range <>) of Money;
   type Overtime_Pay is array (Weekend range <>) of Money;

   No_Basic: Basic_Pay := (Tue..Mon => <>); -- (null array) would be illegal
   No_Piece: Piece_Pay := (Tue..Mon => <>); -- (null array) would be illegal
   No_Overtime: Overtime_Pay := (null array); -- legal (bounds Sat..Fri)
   No_Name: String := (null array); -- exactly the same as ""

It would be syntactic sugar, but we could say that Ada has a sweet tooth.

I think it's interesting to note that it would be pointless to declare any
of the above four variables constant; no assignment to any of them is
possible. If we did, the 'constant' would, in a way, be syntactic sugar.
It's hard to avoid.

Also, if we were to declare:

   type Day is (Mon, Tue, Wed, ..., Sun, 'X');
   ...

then we could legally write:

   No_Overtime: Overtime_Pay := "";

in Ada 95, showing, I think, how syntactic sugar can always be misused.

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

From: Pascal Leroy
Sent: Wednesday, October 1, 2003  6:28 AM

Nick proposed:

> If we introduce (null array) and define it as strictly equivalent to (A..B
> => <>) where A is S'First and S is the expected subtype and B is S'Pred(A),
> that shouldn't be too difficult to define.

If S is a modular type, this would not have the effect that you intend,
I think, unless you and I have very different notions of what a null
array is.

More seriously, a construct that would raise Constraint_Error more often
than not, and would do so not to indicate the run-time violation of some
invariant, but because of definitional problems, would be actively
harmful in my opinion.

I see this thread as unimportant, and I'd rather let that sleeping dog
lie.  But if we were to ever pursue this proposal, we should properly
solve the issues related to multidimensional aggregates and to index
bounds.

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

From: Nick Roberts
Sent: Wednesday, October 1, 2003  9:10 AM

> If S is a modular type, this would not have the effect that
> you intend, I think, unless you and I have very different
> notions of what a null array is.

Ahem, yes. Well, it goes to show that the most difficult thing about
defining things is not making mistakes.

> More seriously, a construct that would raise Constraint_Error
> more often than not, and would do so not to indicate the run-
> time violation of some invariant, but because of definitional
> problems, would be actively harmful in my opinion.

I contend that the conditions that would cause C_E to be raised would be
unusual in practice. I think you'll find, in working code, that most
unconstrained array types (which have 'range <>' for the indices) have index
subtypes of or based on Standard.Positive. I think multidimensional arrays
could be reasonably easily accommodated, but this would add a little more
definition work.

> I see this thread as unimportant, and I'd rather let that
> sleeping dog lie.

Woof!

> But if we were to ever pursue this proposal, we should
> properly solve the issues related to multidimensional
> aggregates and to index bounds.

Pascal, you seem to rebuff the more elaborate suggestions by saying they
involve too much work, and then rebuff the simpler suggestion by saying it's
insufficiently elaborate. If you think that there is a need for a new
construct that provides null arrays for all kinds of array, how can you then
argue that there is no need for a new construct that provides null arrays
for some kinds of array? It doesn't make sense.

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

From: Adam Beneschan
Sent: Wednesday, October 1, 2003 11:04 AM

> I contend that the conditions that would cause C_E to be raised would be
> unusual in practice. I think you'll find, in working code, that most
> unconstrained array types (which have 'range <>' for the indices) have index
> subtypes of or based on Standard.Positive. I think multidimensional arrays
> could be reasonably easily accommodated, but this would add a little more
> definition work.

In one medium-sized Ada program I wrote, I found that when it declared
178 unconstrained array types, 158 used Natural or Positive as the
index type (I use Natural much more often than Positive), and 20 used
something else.  A few of these 20 were enumeration types, and some
were other integer types for which I didn't bother to declare a
"positive" subtype or the like.

But I agree with Pascal that if this syntactic sugar were to be added,
it would be best to make it work for all cases (in fact, I think it
would be "best" if the null string literal were legal for string types
where the index subtype is a modular type [AI-138], since not having
it work seems to be a rather surprising result).  Even though most of
the array types I defined in my program wouldn't cause a problem, it
would still be annoying to have things not work in the other 20 cases
where they would, and to have to define fake subtypes of enumeration
types just to make this work so that the compiler would get the bounds
of a (null array) right in a case where I couldn't care less what the
bounds are.  The fact is, in most cases where I use a positional array
aggregate in a statement (i.e. not in an initializer), I really don't
care what bounds the compiler gives the array; the routine it's being
passed to is just going to use Arr'First and Arr'Last anyway.  The
same would apply to a null array, and I wouldn't appreciate having the
language force me to care about something irrelevant.

Anyway, that's just my feeling as an Ada programmer.  I realize there
are costs involved in making things work the "best" way.

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

From: Jeffrey Carter
Sent: Wednesday, October 1, 2003  1:28 PM

Adam Beneschan wrote:

Consider

type Index is (One);

type Str is array (Index range <>) of Character;
type Num is array (Index range <>) of Integer;

Are we supposed to be able to write

Null_Str : constant Str := "";

?

Other messages in this thread seem to imply that this is legal. I ask
because GNAT 3.15p rejects this:

test_str.adb:7:31: null string literal not allowed for type "Str"
defined at line 4
test_str.adb:7:31: static expression raises "Constraint_Error"

so I'm wondering if this is an error in GNAT or a misunderstanding of
the ARM.

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

From: Adam Beneschan
Sent: Wednesday, October 1, 2003  1:53 PM

No; and under the current rules this would be illegal even if Index
had more than one value, e.g.:

  type Index is (One, Two, Five);

So GNAT is correct.  Furthermore, unless I missed something, I don't
think anyone has implied that this is legal.

What I've said is that perhaps the rules should be changed to make
this legal (big emphasis on "perhaps").  However, even with the change
to 4.3.3(26) I mentioned as a possibility, this could *not* make your
above example legal, in which Index is an enumeration with just one
element.  Since there is only one possible value for Index, there is
no way that an array of type Str or Num can have a 'Length other than
one.

Hope this clarifies things,

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

From: Jeffrey Carter
Sent: Wednesday, October 1, 2003  2:39 PM

> No; and under the current rules this would be illegal even if Index
> had more than one value, e.g.:
>
>   type Index is (One, Two, Five);

Three, Sir!

>
> So GNAT is correct.  Furthermore, unless I missed something, I don't
> think anyone has implied that this is legal.

Nick Roberts wrote

> Also, if we were to declare:
>
>    type Day is (Mon, Tue, Wed, ..., Sun, 'X');
>    ...
>
> then we could legally write:
>
>    No_Overtime: Overtime_Pay := "";
>
> in Ada 95, showing, I think, how syntactic sugar can always be misused.

But I see now that Overtime_Pay is indexed by a subtype of Day excluding
Mon, so this is a different case.

> What I've said is that perhaps the rules should be changed to make
> this legal (big emphasis on "perhaps").  However, even with the change
> to 4.3.3(26) I mentioned as a possibility, this could *not* make your
> above example legal, in which Index is an enumeration with just one
> element.  Since there is only one possible value for Index, there is
> no way that an array of type Str or Num can have a 'Length other than
> one.

It seems to me that "range <>" should alway include a null range. The
problem is not the concept, but that the language has no way to express
that null range. It would be nice to be able to write a zero-length
value for any unconstrained array type. The question is if it's worth
the effort.

> Hope this clarifies things,

Yes, thanks. I was working from memory, and misremembered.

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


Questions? Ask the ACAA Technical Agent