AI22-0103-1
!standard 4.3.3(20.2/5) 25-06-26 AI22-0103-1/04
!standard 4.3.3(20.3/6)
!standard 4.3.3(20.4/6)
!standard 4.3.3(32/6)
!standard 5.5.2(9/3)
!standard 5.5.2(10/5)
!standard 5.5.2(10.1/5)
!standard 5.5.2(11/5)
!standard 5.5.2(12/5)
!class Binding Interpretation
!status Amendment 1-2022 24-05-02
!status WG9 Approved 24-10-10
!status ARG Approved 14-0-0 24-05-02
!status work item 24-04-17
!status received 24-04-01
!submitter Stephen Baird
!priority Low
!difficulty Medium
!qualifier Clarification
!subject Array Aggregate Double Iteration
When performing a "double" iteration as part of evaluating an array aggregate that involves iterator specifications, every variable part of the iterator specifications is evaluated only once.
When evaluating an array aggregate that involves an iterator specification, 4.3.3(20.2/5-20.4/5) requires that two iterations are performed, the first to determine the number of components, and the second to actually compute the values of the components and store them into the aggregate object. If the iterator specifications have parts that involve expression or name evaluation, the current rules of 5.5.2(9/3-10.1/5) imply that these expressions or names are evaluated twice, once for each of the two iterations. This increases the likelihood that the two iterations will not agree in terms of the number of components. This would be especially true if the iterator specification involves a name that calls a function that destructively converts an existing collection into a container that can, say, more easily be iterated.
For example:
X : Array_Of_Int := [for E of Build_Container(A, G(D)) => E * F(C)];
If A is an in out parameter to Build_Container, it might modify (or destroy) A as part of building a new container.
The current rules require that in aggregates such as the one above, two iterations be performed, the first to count how many values are produced by iterating over the container returned by Build_Container, and the second to actually retrieve the values and compute the expression E * F(C) for each one. The rules of 5.5.2(9/3-10.1/5) imply that Build_Container(A, G(D)) is evaluated twice, once for each pass.
Should the rules be adjusted to require that such expressions and names are evaluated only once? (Yes.)
A secondary issue is that the description of the dynamic semantics for a parallel iterator does not specify when the iterator_name is evaluated.
Should the description be adjusted to indicate when the iterator_name for a parallel iterator is evaluated? (Yes.)
The issue can be solved by specifying that the "elaboration" of an iterator_specification includes the evaluation of any names or expressions that appear in the iterator_specification (other than the filter), and then adjusting the wording for the double iteration used to evaluate array aggregates with iterator_specificattons to perform the elaboration once, and then do the two iterations.
Modify 4.3.3(20.2/5-20.4/6):
For an array_aggregate that contains only array_component_associations that are iterated_component_associations with iterator_specifications, evaluation proceeds in [two] {three} steps:
[Editor’s note: This wording includes the changes of AI22-0006-1 and AI22-0085-1.]
Modify 4.3.3(32/6):
When evaluating iterated_component_associations for an array_aggregate that contains only iterated_component_associations with iterator_specifications, the {second}[first] step of evaluating an iterated_component_association may be omitted if the implementation can determine the number of values by some other means. [Redundant: Such "other means" can include making use of an applicable index constraint or the Length function of a suitable container type.]
Modify 5.5.2(9/3-10.1/5):
For the execution of a loop_statement with an iterator_specification, {[Redundant: after elaborating the chunk_specification, if any]}, the iterator_specification is [first] elaborated. [This]{The} elaboration {of an iterator_specification} elaborates the subtype_indication{ or access_definition}, if any{, and evaluates the iterator_name or the iterable_name}.
For a sequential generalized iterator, the loop parameter is created, [the iterator_name is evaluated,] and the [denoted] iterator object {denoted by the iterator_name} becomes the loop iterator. In a forward generalized iterator, …
For a parallel generalized iterator, the chunk_specification, if any, of the associated parallel construct, is [first elaborated,]{used} to determine the maximum number of chunks (see 5.5), and then the operation Split_Into_Chunks of the iterator type is called, with the determined maximum passed as the Max_Chunks parameter, …
Modify 5.5.2(11/5):
For an array component iterator, the chunk_specification of the associated parallel construct, if any, is [first elaborated]{used} to determine the maximum number of chunks (see 5.5)[, and then the iterable_name is evaluated and the denoted]{. The} array object {denoted by the iterable_name} becomes the array for the loop. If the array for the loop is a null array, then the execution of the loop_statement is complete. Otherwise, …
Modify 5.5.2(12/5):
For a container element iterator, the chunk_specification of the associated parallel construct, if any, is [first elaborated]{used} to determine the maximum number of chunks (see 5.5)[, and then the iterable_name is evaluated]. If the container type has Iterator_View specified, …
We considered two ways to solve this issue, one by simply saying that only one evaluation is performed of the iterator or iterable name even though there are two iterations to be performed. This would not have solved the problem with parallel iterations, where it was never stated when the iterator or iterable names were to be evaluated, though that was not the main justification for choosing the approach proposed here.
The main reason for choosing to expand the notion of iterator_specification elaboration was to cleanly separate the work that can be done once before beginning an iteration, from the work of actually iterating. This is also analogous to how loop_parameter_specifications are handled in 5.5, where the elaboration of the loop_parameter_specification evaluates any expressions appearing within it (other than the filter, which by definition needs to be reevaluated on each iteration).
Note that chunk_specification elaboration was already described in 5.5 as happening as part of any parallel loop statement, so we mention it here inside "Redundant:" brackets, and remove any mention of elaboration from later in the dynamic semantics section.
With the above changes the example given in the !issue section above will now perform the evaluation of Build_Container(...) just once, as part of elaborating the iterator_specification.
@drepl
For an @fa{array_aggregate} that contains only @fa{array_component_association}s that are @fa{iterated_component_association}s with @fa{iterator_specification}s, evaluation proceeds in two steps:
@dby
For an @fa{array_aggregate} that contains only @fa{array_component_association}s that are @fa{iterated_component_association}s with @fa{iterator_specification}s, evaluation proceeds in three steps:
@drepl
@xhang{@xterms{1.}Each @fa{iterator_specification} is elaborated (in an arbitrary order) and an iteration is performed solely to determine a count for the number of values conditionally produced by the iteration (see @ref{5.5} and @ref{5.5.2}); all of these counts are combined to determine the overall length of the array, and ultimately the bounds of the array (defined below);}
@dby
@xhang{@xterms{1.}Each @fa{iterator_specification} is elaborated (in an arbitrary order).}
@xhang{@xterms{2.}An iteration is performed for each of these @fa{iterator_specification}s solely to determine a count for the number of values conditionally produced by the iteration (see @ref{5.5} and @ref{5.5.2}); all of these counts are combined to determine the overall length of the array, and ultimately the bounds of the array (defined below);}
@drepl
@xhang{@xterms{2.}A second iteration is performed for each of the @fa{iterator_specification}s, in the order given in the @fa{aggregate}, and for each value conditionally produced by the iteration, the associated @fa{expression} is evaluated, its value is converted to the component subtype of the array type, and used to define the value of the next component of the array starting at the low bound and proceeding sequentially toward the high bound. As part of this second iteration, a check is made that it results in the same number of elements as the first iteration; Constraint_Error is raised if this check fails. This check is performed before any attempt to access any nonexistent element of the array object.}
@dby
@xhang{@xterms{3.}A second iteration is performed for each of the @fa{iterator_specification}s, in the order given in the @fa{aggregate}, and for each value conditionally produced by the iteration, the associated @fa{expression} is evaluated, its value is converted to the component subtype of the array type, and used to define the value of the next component of the array starting at the low bound and proceeding sequentially toward the high bound. As part of this second iteration, a check is made that it results in the same number of elements as the first iteration; Constraint_Error is raised if this check fails. This check is performed before any attempt to access any nonexistent element of the array object.}
@drepl
When evaluating @fa{iterated_component_association}s for an
@fa{array_aggregate} that contains only @fa{iterated_component_association}s
with @fa{iterator_specification}s, the first step of evaluating an
@fa{iterated_component_association} may be omitted if the implementation can
determine the maximum number of values by some other means.
Such "other means" can include making use of an applicable index constraint
or the Length function of a suitable container type.
@dby
When evaluating @fa{iterated_component_association}s for an
@fa{array_aggregate} that contains only @fa{iterated_component_association}s
with @fa{iterator_specification}s, the second step of evaluating an
@fa{iterated_component_association} may be omitted if the implementation can
determine the maximum number of values by some other means.
Such "other means" can include making use of an applicable index constraint
or the Length function of a suitable container type.
@drepl
For the execution of a @fa{loop_statement} with an @fa{iterator_specification}, the @fa{iterator_specification} is first elaborated. This elaboration elaborates the @fa{subtype_indication}, if any.
@dby
For the execution of a @fa{loop_statement} with an @fa{iterator_specification}, after elaborating the @fa{chunk_specification}, if any, the @fa{iterator_specification} is elaborated. The elaboration of an @fa{iterator_specification} elaborates the @fa{subtype_indication} or @fa{access_definition}, if any, and evaluates the @i{iterator_}@fa{name} or the @i{iterable_}@fa{name}.
@drepl
For a sequential generalized iterator, the loop parameter is created, the @i{iterator_}@fa{name} is evaluated, and the denoted iterator object becomes the @i{loop iterator}. In a forward generalized iterator, the operation First of the iterator type is called on the loop iterator, to produce the initial value for the loop parameter. If the result of calling Has_Element on the initial value is False, then the execution of the @fa{loop_statement} is complete. Otherwise, the @fa{sequence_of_statements} is conditionally executed and then the Next operation of the iterator type is called with the loop iterator and the current value of the loop parameter to produce the next value to be assigned to the loop parameter. This repeats until the result of calling Has_Element on the loop parameter is False, or the loop is left as a consequence of a transfer of control. For a reverse generalized iterator, the operations Last and Previous are called rather than First and Next.
@dby
For a sequential generalized iterator, the loop parameter is created, and the iterator object denoted by the @i{iterator_}@fa{name} becomes the @i{loop iterator}. In a forward generalized iterator, the operation First of the iterator type is called on the loop iterator, to produce the initial value for the loop parameter. If the result of calling Has_Element on the initial value is False, then the execution of the @fa{loop_statement} is complete. Otherwise, the @fa{sequence_of_statements} is conditionally executed and then the Next operation of the iterator type is called with the loop iterator and the current value of the loop parameter to produce the next value to be assigned to the loop parameter. This repeats until the result of calling Has_Element on the loop parameter is False, or the loop is left as a consequence of a transfer of control. For a reverse generalized iterator, the operations Last and Previous are called rather than First and Next.
@drepl
For a parallel generalized iterator, the @fa{chunk_specification}, if any, of the associated parallel construct, is first elaborated, to determine the maximum number of chunks (see @ref{5.5}), and then the operation Split_Into_Chunks of the iterator type is called, with the determined maximum passed as the Max_Chunks parameter, specifying the upper bound for the number of loop parameter objects (and the number of logical threads of control) to be associated with the iterator. In the absence of a @fa{chunk_specification}, the maximum number of chunks is determined in an implementation-defined manner.
@dby
For a parallel generalized iterator, the @fa{chunk_specification}, if any, of the associated parallel construct, is used to determine the maximum number of chunks (see @ref{5.5}), and then the operation Split_Into_Chunks of the iterator type is called, with the determined maximum passed as the Max_Chunks parameter, specifying the upper bound for the number of loop parameter objects (and the number of logical threads of control) to be associated with the iterator. In the absence of a @fa{chunk_specification}, the maximum number of chunks is determined in an implementation-defined manner.
@drepl
For an array component iterator, the @fa{chunk_specification} of the associated parallel construct, if any, is first elaborated to determine the maximum number of chunks (see @ref{5.5}), and then the @i{iterable_}@fa{name} is evaluated and the denoted array object becomes the @i{array for the loop}. If the array for the loop is a null array, then the execution of the @fa{loop_statement} is complete. Otherwise, the @fa{sequence_of_statements} is conditionally executed with the loop parameter denoting each component of the array for the loop, using a @i{canonical} order of components, which is last dimension varying fastest (unless the array has convention Fortran, in which case it is first dimension varying fastest). For a forward array component iterator, the iteration starts with the component whose index values are each the first in their index range, and continues in the canonical order. For a reverse array component iterator, the iteration starts with the component whose index values are each the last in their index range, and continues in the reverse of the canonical order. For a parallel array component iterator, the iteration is broken up into contiguous chunks of the canonical order, such that all components are covered with no overlaps; each chunk has its own logical thread of control with its own loop parameter and iteration within each chunk is in the canonical order. The number of chunks is implementation defined, but is limited in the presence of a @fa{chunk_specification} to the determined maximum. The loop iteration proceeds until the @fa{sequence_of_statements} has been conditionally executed for each component of the array for the loop, or until the loop is left as a consequence of a transfer of control.
@dby
For an array component iterator, the @fa{chunk_specification} of the associated parallel construct, if any, is used to determine the maximum number of chunks (see @ref{5.5}). The array object denoted by the @i{iterable_}@fa{name} becomes the @i{array for the loop}. If the array for the loop is a null array, then the execution of the @fa{loop_statement} is complete. Otherwise, the @fa{sequence_of_statements} is conditionally executed with the loop parameter denoting each component of the array for the loop, using a @i{canonical} order of components, which is last dimension varying fastest (unless the array has convention Fortran, in which case it is first dimension varying fastest). For a forward array component iterator, the iteration starts with the component whose index values are each the first in their index range, and continues in the canonical order. For a reverse array component iterator, the iteration starts with the component whose index values are each the last in their index range, and continues in the reverse of the canonical order. For a parallel array component iterator, the iteration is broken up into contiguous chunks of the canonical order, such that all components are covered with no overlaps; each chunk has its own logical thread of control with its own loop parameter and iteration within each chunk is in the canonical order. The number of chunks is implementation defined, but is limited in the presence of a @fa{chunk_specification} to the determined maximum. The loop iteration proceeds until the @fa{sequence_of_statements} has been conditionally executed for each component of the array for the loop, or until the loop is left as a consequence of a transfer of control.
@drepl
For a container element iterator, the @fa{chunk_specification} of the associated parallel construct, if any, is first elaborated to determine the maximum number of chunks (see @ref{5.5}), and then the @i{iterable_}@fa{name} is evaluated. If the container type has Iterator_View specified, an object of the Iterator_View type is created with the discriminant referencing the iterable container object denoted by the @i{iterable_}@fa{name}. This is the @i{iterable container object for the loop}. Otherwise, the iterable container object denoted by the @i{iterable_}@fa{name} becomes the iterable container object for the loop. The default iterator function for the type of the iterable container object for the loop is called on the iterable container object and the result is the @i{loop iterator}. For a sequential container element iterator, an object of the default cursor subtype is created (the @i{loop cursor}). For a parallel container element iterator, each chunk of iterations will have its own loop cursor, again of the default cursor subtype.
@dby
For a container element iterator, the @fa{chunk_specification} of the associated parallel construct, if any, is used to determine the maximum number of chunks (see @ref{5.5}). If the container type has Iterator_View specified, an object of the Iterator_View type is created with the discriminant referencing the iterable container object denoted by the @i{iterable_}@fa{name}. This is the @i{iterable container object for the loop}. Otherwise, the iterable container object denoted by the @i{iterable_}@fa{name} becomes the iterable container object for the loop. The default iterator function for the type of the iterable container object for the loop is called on the iterable container object and the result is the @i{loop iterator}. For a sequential container element iterator, an object of the default cursor subtype is created (the @i{loop cursor}). For a parallel container element iterator, each chunk of iterations will have its own loop cursor, again of the default cursor subtype.
Clearly we will want ACATS tests where there are side-effects from evaluating an iterator_ or iterable_name, to verify that these side effects occur only once when used inside an array aggregate.
ARG Editor
June 9, 2025
In looking at another AI, I happened to notice that this AI changes the number of steps for evaluating array aggregate iterators from two to three. That causes a problem with the permission of 4.3.3(32/6), which as written applies to the first step of evaluation, but it clearly is the second step (after the changes of this AI) that can be omitted (one still has to elaborate everything, of course). I’ve added the appropriate change wording as a (very late) Editorial Review change to the Corrigendum.
ARG Editor
June 26, 2025
Niklas Holsti noted that the !wording for 5.5.2(11/5) showed deleting “used” and adding “first elaborated”, rather than the reverse (which is the only sane possibility since the Ada 2022 wording uses “first elaborated”). This fix is applied as a (very very late) Editorial Review change to the AI.