Version 1.1 of acs/ac-00072.txt

Unformatted version of acs/ac-00072.txt version 1.1
Other versions for file acs/ac-00072.txt

!standard 3.02.01(03)          03-09-07 AC95-00072/01
!class amendment 03-09-12
!status received no action 03-09-12
!status received 03-09-07
!subject Initial values for types
!summary
!appendix

From: Robert I. Eachus
Sent: Sunday, September 7, 2003  1:33 PM

On comp.lang.ada, Randy wrote:

"If your project has "visible" (i.e. non-private) record types where the
components can change, it has maintenance problems to begin with.
Initializers are the least of your problems. Inside the package,
initialization should always be done with aggregates (exactly so that missed
changes cause compile-time errors).

"The problem here is the inconsistence of the language. Some types can be
implicitly initialized, and others cannot. That makes problems for private
types (because if you assume that they are implicitly initialized, you are
breaking privateness and/or significantly restricting the implementation (to
records and access types only). On the other hand, limited types can only be
initialized implicitly (that probably will change in Ada 200Y). Thus, it is
impossible for any rule to be completely consistent.

"I do use implicit initializers in some cases (especially for limited types,
where nothing else is possible). But I prefer to make it explicit when
possible (generally reserving implicit initialization to marking an object
as "not valid")."


Do we yet have a revision request for Ada 200X to include explicit initial
values for types and subtypes?  The original decision not to have a default
initial value for all scalars was right.  But I think it is about time we give
programmers the tools to do initial values right for types where it is
appropriate.

As I see it, if we do this, the right way is to allow all types and subtypes to
have an explicit initial value as part of the declaration.  I have in mind
something like:


full_type_declaration ::=
     type defining_identifier [known_discriminant_part] is
type_definition [ := expression];
   | task_type_declaration
   | protected_type_declaration

and:

subtype_declaration ::=
   subtype defining_identifier is subtype_indication [:= expression];

I'm trying to think of any semantic issues that have to be resolved.
 Hmm.  You won't be able to use the type name in the initial expression
which seems to rule out explicit type conversions (and qualified
expressions) in initial expressions unless we explicitly unhide the type
or subtype name in the initial expression.  I don't know that I care one
way or the other, but it bears thinking about, and an explicit decision.
 Also the initial value expression should be evaluated at the point of
the declaration.  So for a complex expression, the compiler can just
store away the value.  In theory that leads to a problem for array types
with variable bounds.  Instead of just ruling that case out, I think we
can say that for an array with indefinite bounds, the only legal initial
value is an aggregate with others:  "type Bar is array (Integer range
<>) of Foo := (others => Foo'First);" or some such.  The same rule of
course should apply to array subtypes.

Why allow/have default initial expressions for both type and subtypes?
 Because in most of the cases where someone wants to use this feature,
it will be to give an initial value to a type (or first-named subtype).
 In the other cases, someone else will have defined the type, and the
user of the type will need a subtype with a different initial value.

Oh, one last case, that I think we need to consider .. and discard or
rule out.  What about generic formal and actual types?  I don't think
allowing the user to override the initial value--or lack of one--as part
of the formal parameter is useful.  No problem with allowing it, other
than it is work that is not really worth doing.  As for actual
parameters, I have trouble convincing myself that overriding or
providing an initial value as part of the generic instantiation is
necessary.  The user can always declare a subtype and use that subtype
in the generic declaration.  Having said that, then there is a piece of
potential distributed overhead to consider.  What does happen when a
generic body declares an internal object of a formal type, for a subtype
without an explicit initial value?  If it is in the body and the initial
value matters or can be detected, IMHO that is a bug in the generic.
 (If the generic formal is a type or subtype with an initial value, that
case should work and is obviously not distributed overhead.  It is only
the cases where the formal type does not have an initial value but the
type used in the instantiation does that we are discussing here.)  Of
course, if the object is in the specification of a generic generic
formal package, I see that as not a bug.  But I also think that it
probably counts as not being distributed overhead.  In effect the actual
package spec will or will not have a default initialization in that case
depending on whether the actual subtype does or does not have an initial
expression.

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

From: Tucker Taft
Sent: Sunday, September 7, 2003  4:33 PM

I don't see a compelling argument here, since you can
generally wrap a type used to complete a private type
in a record if default initialization is desired.
Also, default initialization tends to muddy
the waters when it comes to static analysis, because you
don't know whether the default initialization is meaningful,
or just a safety net.

If we *do* think there is a compelling argument for having
numeric type defaults (and as I say, I am not convinced), then it
might make sense to use a rule analogous to that which is used for
the bounds, namely that the default can be of any integer type for an integer
default, and any real type for a real default, and there
is a conversion to the new type applied implicitly.

For subtypes, there doesn't seem to be a similar problem --
one can just use a value of the type.

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

From: Robert I. Eachus
Sent: Sunday, September 7, 2003  1:33 PM

Tucker Taft wrote:

>I don't see a compelling argument here, since you can
>generally wrap a type used to complete a private type
>in a record if default initialization is desired.
>Also, default initialization tends to muddy
>the waters when it comes to static analysis, because you
>don't know whether the default initialization is meaningful,
>or just a safety net.

I agree for private types implemented as scalars.  The painful case is
an indefinite array type.  Most Ada programmers still don't know that
you CAN use two enclosing record types, the inner with a discriminant,
the outer without.  And even if you do know that, it isn't all that
pretty, and it effectively requires putting a hard bounds limit on
objects of the type.

But there is another case, which IMHO is much more important.  What
about non-private types?  If a type is known by the compiler to have no
uninitialized values this would be very useful for optimization
purposes, and there could be little or no distributed cost.  The
distributed of actually initializing objects only applies to objects
with no explicit initial value.  It is not a distributed overhead, since
it only occurs for types that use the feature.  And there is a
corresponding  benefit, that all objects of the type are known to be
in-range.  You can't get that benefit otherwise.  The Annex H pragma
Normalize_Scalars has a different intended effect (initialize to an
invalid value if possible), and a cost that applies to all scalar and
composite objects.

So as I see it, this feature would be very useful, and result in both
simpler and more efficient code if used properly.  Of course, if you use
it for a type that is a subcomponent of a large array, and where not all
of the array subcomponents are ever referenced, you will pay a run-time
cost.  But I refuse to get too worked up about the potential costs of
improper use.

>If we *do* think there is a compelling argument for having
>numeric type defaults (and as I say, I am not convinced), then it
>might make sense to use a rule analogous to that which is used for
>the bounds, namely that the default can be of any integer type for an integer
>default, and any real type for a real default, and there
>is a conversion to the new type applied implicitly.

That would be nice.  I am not sure if it is needed, but it is nice.  I
think my suggestion of going "the extra mile" and allowing the type
name, and predefined operations and attributes in the inital value would
be nicer, so your suggestion is sort of an intermediate approach.
 Again, that is why I think we should supply the option for both types
and subtypes.  There will be cases where you can't write the initial
value expression until after the type declaration, perhaps well after.
 This comes back to the issue of where the initial value is elaborated.
 I think it should be evaluated once, at the  point where the type (or
subtype) is declared, and that will lead to some cases where you have a
type, operations on that type, a subtype declaration, and finally a
derived type to wrap it all up into a type again.  That would be a bit
heavy handed for at least 99% of the scalar cases, but could easily
occur with array types.

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

From: Matthew Heaney
Sent: Sunday, September 14, 2003  11:39 AM

Here's another issue.  In C++, if I have a default constructor for a
type (class), then I can declare constants of the type sans an
initializer, like this:

class C
{
public:
   C();
...
};

const C obj;  //has "default" value

I wish there a way to do something similar in Ada.  For example, I have
this:

   type Iterator_Type is private;

   Null_Iterator : constant Iterator_Type;

What I'd like is to be able to say:

  I : Iterator_Type := First (Container);
  J : constant Iterator_Type;  --has "default" value, Null_Iterator
begin
  while I /= J loop ...;

The issue is that I can't declare J as a constant.  I have to either
declare J as a variable, or specify Null_Iterator as the initial value.

It would be great if we could associate a "default" value with a type.
For example, in the type declaration above I could say:

   type Iterator_Type is private;

   Null_Iterator : constant Iterator_Type;

   for Iterator_Type'Default_Value use Null_Iterator;

and then an object whose type was marked as having a default could be
declared as constant, but without an explicit initial value expression.

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

From: Randy Brukardt
Sent: Tuesday, September 16, 2003  9:51 PM

> The issue is that I can't declare J as a constant.  I have to either
> declare J as a variable, or specify Null_Iterator as the initial value.

Why would you want to declare the constant locally rather than use the
perfectly good constant declared with the type? Certainly, I'd much prefer
seeing

    while I /= Something.Null_Iterator loop ...;

than what you have above. (I realize the toy object names don't help.)

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

From: Matthew Heaney
Sent: Wednesday, September 17, 2003  8:25 AM

Fair enough.  I guess I was thinking more about default constructors in C++,
for example:

  const Timer curr;

Here the default ctor for class Timer constructs the object on-the-spot with
the current value of the time.  You could implement something similar in Ada95
using controlled operations, e.g.

  Current_Time : constant Time_Type;

Here we're not referring to any value that is declared elsewhere.  We want
merely to initialize the object Current_Time, and then in the rest of its scope
we just use it in expressions.

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

Questions? Ask the ACAA Technical Agent