AI22-0168-1

!standard 3.3.1(2/3)                                    26-07-21  AI22-0168-1/03

!standard 3.3.1(3)

!standard 3.3.1(4)

!standard 3.3.1(5/2)

!standard 3.3.1(6/5)

!standard 3.3.1(10)

!class Amendment 26-07-06

!status work item 26-07-06

!status received 26-01-24

!assigned author Randy Brukardt

!submitter Liam Powell

!priority Low

!difficulty Easy

!subject Default-initialized constants

!summary

Constants can be created using default-initialized values using a new syntax := <>.

!issue

If one wants to create a default-initialized variable of a private type (or a task or protected type as well), one can declare a stand-alone object without an initializer. But if one needs a default-initialized constant, that doesn’t work as constants always have to be initialized. If one needs a default-initialized value in an expression context, only constants can be included in a declare expression, and those also need an initializer.

!recommendation

Add a new syntax to object initialization.

!wording

Replace 3.3.1(2/3) by:

object_declaration ::=

    defining_identifier_list : [aliased] [constant] subtype_indication [:= initial_specification]

        [aspect_specification];

  | defining_identifier_list : [aliased] [constant] access_definition [:= initial_specification]

        [aspect_specification];

  | defining_identifier_list : [aliased] [constant] array_type_definition

        [:= initial_specification][aspect_specification];

  | single_task_declaration

  | single_protected_declaration

Add after 3.3.1(3):

initial_specification ::= expression

  | <>

[Author’s note: I originally called this syntax “initialization_expression”, but that would be confusing with the wording I ultimately chose; I then used “initial_value”, but that was rightly criticized for being inaccurate as <> is not a value.]

Modify 3.3.1(4):

For an object_declaration with an {initial_specification that is an }expression[ following the compound delimiter :=], the type expected for the expression is that of the object. This expression is called the initialization expression.

[Author’s note: I avoided using an equivalence rule here, as we know that those always seem to run into trouble in corner cases. Instead, I have defined <> as not having an initialization expression, which is already defined.]

Add after 3.3.1(5/2):

For a full constant declaration with an initial_specification that is a <>, the type of the object shall not be a nongeneric scalar type without an implicit initial value.

AARM Reason: A constant with no initial value makes no sense. Any use of such an object is a Bounded Error or worse. We therefore make such objects illegal.

AARM Ramification: This rule does not apply to private types completed by scalar types or any generic formal types (in order to avoid breaking privacy, and in order to avoid having generic body issues). Thus it is possible to create uninitialized objects of such types. Since any use of such an object is a bounded error, an implementation can raise Program_Error for any use of such an object. We do not mandate this as it seems no worse than using uninitialized variables, whatever implementations do to detect that is likely sufficient.

Modify 3.3.1(6/5):

An object_declaration with the reserved word constant declares a constant object. If it has an {initial_specification}[initialization expression], then it is called a full constant declaration. Otherwise, it is called a deferred constant declaration. The rules for deferred constant declarations are given in 7.4. The rules for full constant declarations are given in this subclause.

Modify 3.3.1(10):

For an object_declaration without an initialization expression{Redundant[(including[a][b][c] the case of an object_declaration with an initial_specification that is <>)]}, any initial values for the object or its subcomponents are determined by the implicit initial values defined for its nominal subtype, as follows:

[Author’s note: We don’t need this change, but it seems best to clarify that <> is not an initialization expression while it is an initial_specification.]

!discussion

We used <> for this usage as it has the same meaning when it occurs in an aggregate, so the understanding should be natural.

The one difference is making the use of <> illegal for a scalar type when there is no default value. There is no use for an uninitialized constant, as the value can never be set to something known, and any use of such a constant is potentially a bounded error. It makes more sense to disallow it when possible. We did not try to detect all such cases, thus it remains legal to use <> in the body of a generic unit regardless of whether the actual subtype is initialized. (This also avoids privacy breaking, as a private type can be completed with a scalar type without a default value.) If an implementation wants to detect such uses, it can do so as touching such an object can always be a bounded error (the value can always be treated as invalid regardless of the actual value) and thus Program_Error can be raised unconditionally if desired.

One rejected idea was to have a new aggregate type (private). This was tried for Ada 2005 (see AI-00413), indeed, we spent several hours discussing it during a snowy Paris ARG meeting (meeting #26), before abandoning it. One problem is that the private aggregate capability would go away when the full type is visible. Private types should have strict subsets of the operations of the full type, lest weird effects happen (such as a subprogram declaration that can not be legally completed).

We also considered several slight alternatives. One could imagine supporting this notation in all expressions as a primary. That seems like going too far, as no one wants to see things like:

     type Nat is range 0 .. 10000 with Default_Value => 0;
     A : constant Nat := <> + 1;

The result is well-defined (it is 1 in this example), but this seems mostly confusing.

We also considered allowing any full expression to be <>. That is better, but still leads to stuff like:

     type New_Bool is new Boolean with Default_Value => False;
     if <> then ...

This is well-defined, but more confusing than helpful. Thus, we limited the use of <> to initialization (as well as the existing aggregate uses).

Both of these more general options also lose the ability to set specific discriminant values along with default initializing the rest of the object. When used as an expression, <> necessarily has to mean a fully default initialized object (including discriminants), which necessarily would not be compatible with any constrained subtype other than one using the default discriminant values. The proposed definition allows setting of discriminants (see the !example).

Finally, we considered extending this syntax to default_expressions (used for discriminants, components, and parameters). This seems like it would be useful in some circumstances, but it would add additional wording, and a declare expression can always be used to create the needed default-initialized object in the case that one is needed.

The additional wording needed would be at 3.7(6) [update the syntax], 3.7(15) [new legality rule like the one after 3.3.1(5/2), and 3.7(26):

A default_expression specified as <> is equivalent to an object of the type of the default_expression that is initialized by default.


 

Another use for this feature is to explicitly show when an object needs to be default-initialized, as opposed to when the initialization of an object is unimportant. Sometimes, program code depends upon the default initialization (for instance, that a container object is initially empty), and it is valuable to mark that fact explicitly in the code. One of course can use a comment, but that is much harder to usefully enforce as a programming standard.

Conversely, objects for which the initialization is truly unimportant (as the object will be initialized by the result of a complex calculation, or by the result of an out parameter) can be left without an explicit initialization. This provides a clear and positive indication of what matters and what does not.

!example

This solution can be used to create default-initialized constants with specific discriminant values:

package P is
   type T (A : Natural := 0) is limited private;
private
   type T (A : Natural := 0) is record ...
end P;

A : array (Integer range 1 .. 2) of P.T :=
  [1 => (declare V : P.T(1) := <> begin V),
   2 => (declare V : P.T(2) := <> begin V)];

B : array (Integer range 1 .. 4) of P.T :=

  [for I in 1 .. 4 => (declare V : P.T(I) := <> begin V)];

!ACATS test

An ACATS C-Test should be constructed for the basic use cases, and an ACATS B-Test should be constructed for the one difference from an aggregate.

!appendix

See ARG GitHub issue #163 for the original question and answers.

[a]This is hard to parse.

[b]I'll suggest another wording.

[c]I tried to improve it, but I haven't been able to avoid using "including". Adding the "the case of" and parenthesizing the expression should help. Does it help enough?