Skip to main content

5.5 Loop Statements

danger

This Reference Manual output has not been verified, and may contain omissions or errors. Report any problems on the tracking issue

1/5

[A loop_statement includes a sequence_of_statements that is to be executed repeatedly, zero or more times with the iterations running sequentially or concurrently with one another.]

Syntax

2

loop_statement ::=
[loop_statement_identifier:]
[iteration_scheme] loop
sequence_of_statements
end loop [loop_identifier];

3/5

iteration_scheme ::= while condition
| for loop_parameter_specification
| for iterator_specification
| [parallel [aspect_specification]]
for procedural_iterator
| parallel [(chunk_specification)] [aspect_specification]
for loop_parameter_specification
| parallel [(chunk_specification)] [aspect_specification]
for iterator_specification

3.1/5

chunk_specification ::=
integer_simple_expression
| defining_identifier in discrete_subtype_definition

4/5

loop_parameter_specification ::=
defining_identifier in [reverse] discrete_subtype_definition
[iterator_filter]

4.1/5
iterator_filter ::= whencondition
5

If a loop_statement has a loop_statement_identifier, then the identifier shall be repeated after the end loop; otherwise, there shall not be an identifier after the end loop.

5.1/5

An iteration_scheme that begins with the reserved word parallel shall not have the reserved word reverse in its loop_parameter_specification.

Name Resolution Rules

5.2/5

In a chunk_specification that is an integer_simple_expression, the integer_simple_expression is expected to be of any integer type.

Static Semantics

6/5

A loop_parameter_specification declares a loop parameter, which is an object whose subtype (and nominal subtype) is that defined by the discrete_subtype_definition.

6.1/5

In a chunk_specification that has a discrete_subtype_definition, the chunk_specification declares a chunk parameter object whose subtype (and nominal subtype) is that defined by the discrete_subtype_definition.

Dynamic Semantics

6.2/5

The filter of an iterator construct (a loop_parameter_specification, iterator_specification, or procedural_iterator) is defined to be satisfied when there is no iterator_filter for the iterator construct, or when the condition of the iterator_filter evaluates to True for a given iteration of the iterator construct.

6.a/5

Term entry: iterator filter — construct that is used to restrict the elements produced by an iteration to those for which a boolean condition evaluates to True

6.3/5

If a sequence_of_statements of a loop_statement with an iterator construct is said to be conditionally executed, then the statements are executed only when the filter of the iterator construct is satisfied.

6.4/5

The loop iterators loop_parameter_specification and iterator_specification can also be used in contexts other than loop_statements (for example, see 4.3.5 and 4.5.8). In such a context, the iterator conditionally produces values in the order specified for the associated construct below or in 5.5.2. The values produced are the values given to the loop parameter when the filter of the iterator construct is satisfied for that value. [No value is produced when the condition of an iterator_filter evaluates to False.]

7/5

For the execution of a loop_statement, the sequence_of_statements is executed zero or more times, until the loop_statement is complete. The loop_statement is complete when a transfer of control occurs that transfers control out of the loop, or, in the case of an iteration_scheme, as specified below.

8

For the execution of a loop_statement with a while iteration_scheme, the condition is evaluated before each execution of the sequence_of_statements; if the value of the condition is True, the sequence_of_statements is executed; if False, the execution of the loop_statement is complete.

8.1/5

If the reserved word parallel is present in the iteration_scheme of a loop_statement (a parallel loop), the iterations are partitioned into one or more chunks, each with its own separate logical thread of control (see Clause 9). If a chunk_specification is present in a parallel loop, it is elaborated first, and the result of the elaboration determines the maximum number of chunks used for the parallel loop. If the chunk_specification is an integer_simple_expression, the elaboration evaluates the expression, and the value of the expression determines the maximum number of chunks. If a discrete_subtype_definition is present, the elaboration elaborates the discrete_subtype_definition, which defines the subtype of the chunk parameter, and the number of values in this subtype determines the maximum number of chunks. After elaborating the chunk_specification, a check is made that the determined maximum number of chunks is greater than zero. If this check fails, Program_Error is raised.

9/5

For the execution of a loop_statement that has an iteration_scheme including a loop_parameter_specification, after elaborating the chunk_specification and aspect_specification, if any, the loop_parameter_specification is elaborated. This elaborates the discrete_subtype_definition, which defines the subtype of the loop parameter. If the discrete_subtype_definition defines a subtype with a null range, the execution of the loop_statement is complete. Otherwise, the sequence_of_statements is conditionally executed once for each value of the discrete subtype defined by the discrete_subtype_definition that satisfies the predicates of the subtype (or until the loop is left as a consequence of a transfer of control). Prior to each such iteration, the corresponding value of the discrete subtype is assigned to the loop parameter associated with the given iteration. If the loop is a parallel loop, each chunk has its own logical thread of control with its own copy of the loop parameter; otherwise (a sequential loop), a single logical thread of control performs the loop, and there is a single copy of the loop parameter. Each logical thread of control handles a distinct subrange of the values of the subtype of the loop parameter such that all values are covered with no overlaps. Within each logical thread of control, the values are assigned to the loop parameter in increasing order unless the reserved word reverse is present, in which case the values are assigned in decreasing order. In the absence of a transfer of control, the associated parallel construct of a loop_parameter_specification is complete when all of its logical threads of control are complete.

9.a.1/5

To be honest: This wording does not describe when the loop parameter object(s) are created. That creation has no side-effects (other than possibly raising Storage_Error, but anything can do that), so we simplified the wording by leaving it out. Each object has to be created before any iteration that depends on it starts, but we do not (for instance) require that the objects are all created at once at the start of the loop, nor that the objects are created after the elaboration of the discrete_subtype_definition.

9.a/5
This paragraph was deleted.
9.b/3
ramification

The predicate (if any) necessarily has to be a static predicate as a dynamic predicate is explicitly disallowed — see 3.2.4.

9.c/3
reason

If there is a predicate, the loop still visits the values in the order of the underlying base type; the order of the values in the predicate is irrelevant. This is the case so that the following loops have the same sequence of calls and parameters on procedure Call for any subtype S:

9.d

for I in S loop Call (I); end loop; 9.e for I in S'Base loop if I in S then Call (I); end if; end loop;

9.f/5
discussion

The rules for completing a parallel construct when there is a transfer of control are given in 5.1.

10/5

If a chunk_specification with a discrete_subtype_definition is present, then the logical thread of control associated with a given chunk has its own copy of the chunk parameter initialized with a distinct value from the discrete subtype defined by the discrete_subtype_definition. The values of the chunk parameters are assigned such that they increase with increasing values of the ranges covered by the corresponding loop parameters.

11/5

Whether or not a chunk_specification is present in a parallel loop, the total number of iterations of the loop represents an upper bound on the number of logical threads of control devoted to the loop.

12/5

[For details about the execution of a loop_statement with the iteration_scheme including an iterator_specification, see 5.5.2. For details relating to a procedural_iterator, see 5.5.3.]

13/5

NOTE 1 A loop parameter declared by a loop_parameter_specification is a constant; it cannot be updated within the sequence_of_statements of the loop (see 3.3).

14/5

NOTE 2 No separate object_declaration is expected for a loop parameter, since the loop parameter is automatically declared by the loop_parameter_specification. The scope of a loop parameter extends from the loop_parameter_specification to the end of the loop_statement, and the visibility rules are such that a loop parameter is only visible within the sequence_of_statements of the loop.

14.a
implementation note

An implementation could give a warning if a variable is hidden by a loop_parameter_specification.

15/5

NOTE 3 The discrete_subtype_definition of a for loop is elaborated just once. Use of the reserved word reverse does not alter the discrete subtype defined, so that the following iteration_schemes are not equivalent; the first has a null range.

16

for J in reverse 1 .. 0 for J in 0 .. 1

16.a
ramification

If a loop_parameter_specification has a static discrete range, the subtype of the loop parameter is static.

Examples

17

Example of a loop statement without an iteration scheme:

18

loop Get(Current_Character); exit when Current_Character = '*'; end loop;

19

Example of a loop statement with a while iteration scheme:

20

while Bid(N).Price < Cut_Off.Price loop Record_Bid(Bid(N).Price); N := N + 1; end loop;

21

Example of a loop statement with a for iteration scheme:

22

for J in Buffer'Range loop -- works even with a null range if Buffer(J) /= Space then Put(Buffer(J)); end if; end loop;

23

Example of a loop statement with a name:

24

Summation: while Next /= Head loop -- see 3.10.1 Sum := Sum + Next.Value; Next := Next.Succ; end loop Summation;

25/5

Example of a simple parallel loop:

26/5

-- see 3.6 parallel for I in Grid'Range(1) loop Grid(I, 1) := (for all J in Grid'Range(2) => Grid(I,J) = True); end loop;

27/5

Example of a parallel loop with a chunk specification:

28/5

declare subtype Chunk_Number is Natural range 1 .. 8; 29/5 Partial_Sum, Partial_Max : array (Chunk_Number) of Natural := (others => 0); Partial_Min : array (Chunk_Number) of Natural := (others => Natural'Last); 30/5 begin parallel (Chunk in Chunk_Number) for I in Grid'Range(1) loop declare True_Count : constant Natural := [for J in Grid'Range(2) => (if Grid (I, J) then 1 else 0)]'Reduce("+",0); begin Partial_Sum (Chunk) := @ + True_Count; Partial_Min (Chunk) := Natural'Min(@, True_Count); Partial_Max (Chunk) := Natural'Max(@, True_Count); end; end loop; 31/5

Put_Line ("Total=" & Partial_Sum'Reduce("+", 0)'Image & ", Min=" & Partial_Min'Reduce(Natural'Min, Natural'Last)'Image & ", Max=" & Partial_Max'Reduce(Natural'Max, 0)'Image); end;

32/5

For an example of an iterator_filter, see 4.5.8.

Wording Changes from Ada 83

32.a

The constant-ness of loop parameters is specified in 3.3, “Objects and Named Numbers”.

Wording Changes from Ada 2005

32.b/3

Generalized iterator_specifications are allowed in for loops; these are documented as an extension in the appropriate subclause.

Extensions to Ada 2012

32.c/5

Parallel loops are new.

32.d/5

An iterator_filter is now allowed on loop_parameter_specifications. This is mainly for consistency with aggregate and reduction iterators, where it eliminates the need for temporary objects.

Wording Changes from Ada 2012

32.e/4

Corrigendum: Updated wording of loop execution to use the new term "satisfies the predicates" (see 3.2.4).

32.f/5

Added text so that the nominal subtype of a loop parameter is clearly defined.

5.5.1 User-Defined Iterator Types

Static Semantics

1/3

The following language-defined generic library package exists:

2/5

generic type Cursor; with function Has_Element (Position : Cursor) return Boolean; package Ada.Iterator_Interfaces with Pure, Nonblocking => False is 3/3 type Forward_Iterator is limited interface; function First (Object : Forward_Iterator) return Cursor is abstract; function Next (Object : Forward_Iterator; Position : Cursor) return Cursor is abstract; 4/3 type Reversible_Iterator is limited interface and Forward_Iterator; function Last (Object : Reversible_Iterator) return Cursor is abstract; function Previous (Object : Reversible_Iterator; Position : Cursor) return Cursor is abstract; 4.1/5

type Parallel_Iterator is limited interface and Forward_Iterator; 4.2/5

subtype Chunk_Index is Positive; 4.3/5

function Is_Split (Object : Parallel_Iterator) return Boolean is abstract; 4.4/5

procedure Split_Into_Chunks (Object : in out Parallel_Iterator; Max_Chunks : in Chunk_Index) is abstract with Pre'Class => not Object.Is_Split or else raise Program_Error, Post'Class => Object.Is_Split and then Object.Chunk_Count <= Max_Chunks; 4.5/5

function Chunk_Count (Object : Parallel_Iterator) return Chunk_Index is abstract with Pre'Class => Object.Is_Split or else raise Program_Error; 4.6/5

function First (Object : Parallel_Iterator; Chunk : Chunk_Index) return Cursor is abstract with Pre'Class => (Object.Is_Split and then Chunk <= Object.Chunk_Count) or else raise Program_Error; 4.7/5

function Next (Object : Parallel_Iterator; Position : Cursor; Chunk : Chunk_Index) return Cursor is abstract with Pre'Class => (Object.Is_Split and then Chunk <= Object.Chunk_Count) or else raise Program_Error; 4.8/5

type Parallel_Reversible_Iterator is limited interface and Parallel_Iterator and Reversible_Iterator; 5/3 end Ada.Iterator_Interfaces;

5.a/5
reason

This package must allow blocking (Nonblocking => False) for compatibility. The purpose of this package is to provide a template for overriding user-defined routines; and such routines can only allow blocking if the root type does so. Users can still declare their overridding routines nonblocking if they wish.

6/5

An iterator type is a type descended from the Forward_Iterator interface from some instance of Ada.Iterator_Interfaces. A reversible iterator type is a type descended from the Reversible_Iterator interface from some instance of Ada.Iterator_Interfaces. A parallel iterator type is a type descended from the Parallel_Iterator interface from some instance of Ada.Iterator_Interfaces. A type descended from the Parallel_Reversible_Iterator interface from some instance of Ada.Iterator_Interfaces is both a parallel iterator type and a reversible iterator type. An iterator object is an object of an iterator type. A reversible iterator object is an object of a reversible iterator type. A parallel iterator object is an object of a parallel iterator type. The formal subtype Cursor from the associated instance of Ada.Iterator_Interfaces is the iteration cursor subtype for the iterator type.

7/3

The following type-related operational aspects may be specified for an indexable container type T (see 4.1.6):

8/5

Default_Iterator
This aspect is specified by a name that denotes exactly one function declared immediately within the same declaration list in which T, or the declaration completed by T, is declared, whose first parameter is of type T or T'Class or an access parameter whose designated type is type T or T'Class, whose other parameters, if any, have default expressions, and whose result type is an iterator type. This function is the default iterator function for T. Its result subtype is the default iterator subtype for T. The iteration cursor subtype for the default iterator subtype is the default cursor subtype for T. This aspect is inherited by descendants of type T (including T'Class).
8.a/3

Aspect Description for Default_Iterator: Default iterator to be used in for loops.

9/5

Iterator_Element
This aspect is specified by a name that denotes a subtype. This is the default element subtype for T. This aspect is inherited by descendants of type T (including T'Class).
9.a/3

Aspect Description for Iterator_Element: Element type to be used for user-defined iterators.

9.1/5

Iterator_View
This aspect is specified by a name that denotes a type T2 with the following properties:
9.2/5
  • T2 is declared in the same compilation unit as T;
  • 9.3/5
  • T2 is an iterable container type;
  • 9.4/5
  • T2 has a single discriminant which is an access discriminant designating T; and
  • 9.5/5
  • The default iterator subtypes for T and T2 statically match.
9.6/5
This aspect is never inherited[, even by T'Class].
9.b/5
reason

Iterator_View allows specifying an alternative type to be automatically used by container element iterators; see 5.5.2. This allows setting state for an iteration only once rather than for each individual reference.

9.c/5
ramification

Since Iterator_View is not inherited, it does not apply to T'Class. Otherwise, the type of the iterator object would not be known at compile-time (since it necessarily has to be different for each descendant).

9.d/5

Aspect Description for Iterator_View: An alternative type to used for container element iterators.

10/5

This paragraph was deleted.

11/5

An iterable container type is an indexable container type with specified Default_Iterator and Iterator_Element aspects. A reversible iterable container type is an iterable container type with the default iterator type being a reversible iterator type. A parallel iterable container type is an iterable container type with the default iterator type being a parallel iterator type. An iterable container object is an object of an iterable container type. A reversible iterable container object is an object of a reversible iterable container type. A parallel iterable container object is an object of a parallel iterable container type.

11.a/5

Term entry: iterable container type — type that has user-defined behavior for iteration, via the Default_Iterator and Iterator_Element aspects

11.1/4

The Default_Iterator and Iterator_Element aspects are nonoverridable (see 13.1.1).

11.b/4
reason

This ensures that all descendants of an iterable container type have aspects with the same properties. This prevents generic contract problems with formal derived types.

Legality Rules

12/3

The Constant_Indexing aspect (if any) of an iterable container type T shall denote exactly one function with the following properties:

13/3
  • the result type of the function is covered by the default element type of T or is a reference type (see 4.1.5) with an access discriminant designating a type covered by the default element type of T;
  • 14/3
  • the type of the second parameter of the function covers the default cursor type for T;
  • 15/3
  • if there are more than two parameters, the additional parameters all have default expressions.
16/3

This function (if any) is the default constant indexing function for T.

16.a/3
ramification

This does not mean that Constant_Indexing has to designate only one subprogram, only that there is only one routine that meets all of these properties. There can be other routines designated by Constant_Indexing, but they cannot have the profile described above. For instance, map containers have a version of Constant_Indexing that takes a key instead of a cursor; this is allowed.

17/3

The Variable_Indexing aspect (if any) of an iterable container type T shall denote exactly one function with the following properties:

18/3
  • the result type of the function is a reference type (see 4.1.5) with an access discriminant designating a type covered by the default element type of T;
  • 19/3
  • the type of the second parameter of the function covers the default cursor type for T;
  • 20/3
  • if there are more than two parameters, the additional parameters all have default expressions.
21/3

This function (if any) is the default variable indexing function for T.

Erroneous Execution

22/5

A call on the First or Next operation on a given Parallel_Iterator object with a given Chunk value, which does not propagate an exception, should return a Cursor value that either yields False when passed to Has_Element, or that identifies an element distinct from any Cursor value returned by a call on a First or Next operation on the same Parallel_Iterator object with a different Chunk value. If the First or Next operations with a Chunk parameter behave in any other manner, execution is erroneous.

22.a/5
reason

This describes the expectations from a user-written parallel iterator. If the expectations are not met, execution is erroneous so that implementations do not need to go to heroic efforts to avoid problems caused by bad iterators. This is similar to the handling of storage pools, see 13.11.

Extensions to Ada 2005

22.b/3

User-defined iterator types are new in Ada 2012.

Incompatibilities With Ada 2012

22.c/4

Corrigendum: Defined Default_Iterator and Iterator_Element to be nonoveridable, which makes redefinitions and hiding of these aspects illegal. It's possible that some program could violate one of these new restrictions, but in most cases this can easily be worked around by using overriding rather than redefinition.

22.d/5

Various new types and subprograms are newly added to Ada.Iterator_Interfaces. Therefore, a use clause conflict is possible; see the introduction of Annex A for more on this topic.

Extensions to Ada 2012

22.e/5

Aspect Iterator_View is new; it allows container element iterators to set the tampering state once rather than for each use of the element.

22.f/5

Parallel iterator interfaces are new; they allow user-defined parallel loops to be defined.

5.5.2 Generalized Loop Iteration

1/3

Generalized forms of loop iteration are provided by an iterator_specification.

Syntax

2/5

iterator_specification ::=
defining_identifier [: loop_parameter_subtype_indication] in [reverse] iterator_name
[iterator_filter]
| defining_identifier [: loop_parameter_subtype_indication] of [reverse] iterable_name
[iterator_filter]

2.1/5

loop_parameter_subtype_indication ::= subtype_indication | access_definition

2.2/5

If an iterator_specification is for a parallel construct, the reserved word reverse shall not appear in the iterator_specification.

Name Resolution Rules

3/3

For the first form of iterator_specification, called a generalized iterator, the expected type for the iterator_name is any iterator type. For the second form of iterator_specification, the expected type for the iterable_name is any array or iterable container type. If the iterable_name denotes an array object, the iterator_specification is called an array component iterator; otherwise it is called a container element iterator.

3.a/5

Term entry: iterator — construct that is used to loop over the elements of an array or container
Note: Iterators can be user defined, and can perform arbitrary computations to access elements from a container.

Legality Rules

4/5

If the reserved word reverse appears, the iterator_specification is a reverse iterator. If the iterator_specification is for a parallel construct, the iterator_specification is a parallel iterator. Otherwise, it is a forward iterator. Forward and reverse iterators are collectively called sequential iterators. In a reverse generalized iterator, the iterator_name shall be of a reversible iterator type. In a parallel generalized iterator, the iterator_name shall be of a parallel iterator type. In a reverse container element iterator, the default iterator type for the type of the iterable_name shall be a reversible iterator type. In a parallel container element iterator, the default iterator type for the type of the iterable_name shall be of a parallel iterator type.

5/5

The subtype defined by the loop_parameter_subtype_indication, if any, of a generalized iterator shall statically match the iteration cursor subtype. The subtype defined by the loop_parameter_subtype_indication, if any, of an array component iterator shall statically match the component subtype of the type of the iterable_name. The subtype defined by the loop_parameter_subtype_indication, if any, of a container element iterator shall statically match the default element subtype for the type of the iterable_name.

6/3

In a container element iterator whose iterable_name has type T, if the iterable_name denotes a constant or the Variable_Indexing aspect is not specified for T, then the Constant_Indexing aspect shall be specified for T.

6.1/4

The iterator_name or iterable_name of an iterator_specification shall not denote a subcomponent that depends on discriminants of an object whose nominal subtype is unconstrained, unless the object is known to be constrained.

6.a/4
reason

This is the same rule that applies to renames; it serves the same purpose of preventing the object from disappearing while the iterator is still using it.

6.2/4

A container element iterator is illegal if the call of the default iterator function that creates the loop iterator (see below) is illegal.

6.b/4
ramification

This can happen if the parameter to the default iterator function is in out and the iterable_name is a constant. The wording applies to any reason that the call would be illegal, as it's possible that one of the default parameters would be illegal, or that some accessibility check would fail.

6.3/4

A generalized iterator is illegal if the iteration cursor subtype of the iterator_name is a limited type at the point of the generalized iterator. A container element iterator is illegal if the default cursor subtype of the type of the iterable_name is a limited type at the point of the container element iterator.

6.c/4
reason

If the cursor type is limited, the assignment to the loop parameter for a generalized iterator would be illegal. The same is true for a container element iterator. We have to say "at the point of the iterator" as the limitedness of a type can change due to visibility.

Static Semantics

7/5

An iterator_specification declares a loop parameter. In a generalized iterator, an array component iterator, or a container element iterator, if a loop_parameter_subtype_indication is present, it determines the nominal subtype of the loop parameter. In a generalized iterator, if a loop_parameter_subtype_indication is not present, the nominal subtype of the loop parameter is the iteration cursor subtype. In an array component iterator, if a loop_parameter_subtype_indication is not present, the nominal subtype of the loop parameter is the component subtype of the type of the iterable_name. In a container element iterator, if a loop_parameter_subtype_indication is not present, the nominal subtype of the loop parameter is the default element subtype for the type of the iterable_name.

8/3

In a generalized iterator, the loop parameter is a constant. In an array component iterator, the loop parameter is a constant if the iterable_name denotes a constant; otherwise it denotes a variable. In a container element iterator, the loop parameter is a constant if the iterable_name denotes a constant, or if the Variable_Indexing aspect is not specified for the type of the iterable_name; otherwise it is a variable.

8.a/5
ramification

The loop parameter of a generalized iterator has the same accessibility as the loop statement. This means that the loop parameter object is finalized when the loop statement is left. (It also may be finalized as part of assigning a new value to the loop parameter.) For array component iterators, the loop parameter directly denotes an element of the array and has the accessibility of the associated array. For container element iterators, the loop parameter denotes the result of the indexing function call (in the case of a constant indexing) or a generalized reference thereof (in the case of a variable indexing). Roughly speaking, the loop parameter has the accessibility level of a single iteration of the loop. More precisely, the function result (or the generalized reference thereof) is considered to be renamed in the declarative part of a notional block statement which immediately encloses the loop's sequence_of_statements; the accessibility of the loop parameter is that of the block statement.

Dynamic Semantics

9/3

For the execution of a loop_statement with an iterator_specification, the iterator_specification is first elaborated. This elaboration elaborates the subtype_indication, if any.

10/5

For a sequential generalized iterator, the loop parameter is created, the iterator_name is evaluated, and the denoted iterator object becomes the 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 loop_statement is complete. Otherwise, the 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.

10.a/4
ramification

The loop parameter of a generalized iterator is a variable of which the user only has a constant view. It follows the normal rules for a variable of its nominal subtype. In particular, if the nominal subtype is indefinite, the variable is constrained by its initial value. Similarly, if the nominal subtype is class-wide, the variable (like all variables) has the tag of the initial value. Constraint_Error may be raised by a subsequent iteration if Next or Previous return an object with a different tag or constraint.

10.1/5

For a parallel generalized iterator, the chunk_specification, if any, of the associated parallel construct, is first elaborated, 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, 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 chunk_specification, the maximum number of chunks is determined in an implementation-defined manner.

10.b/5
implementation defined

The maximum number of chunks for a parallel generalized iterator without a chunk_specification.

10.c/5
discussion

The Max_Chunks parameter of the Split_Into_Chunks procedure is an upper bound for the number of chunks to be associated with a loop. A container implementation may opt for a lower value for the number of chunks if a more optimal split can be determined. For instance, a tree-based container might create the split based on the number of branches at the top levels of the tree.

10.2/5

Upon return from Split_Into_Chunks, the actual number of chunks for the loop is determined by calling the Chunk_Count operation of the iterator, at which point one logical thread of control is initiated for each chunk, with an associated chunk index in the range from one to the actual number of chunks.

10.3/5

Within each logical thread of control, a loop parameter is created. If a chunk_specification with a discrete_subtype_definition is present in the associated parallel construct, then a chunk parameter is created and initialized with a value from the discrete subtype defined by the discrete_subtype_definition, so that the order of the chosen chunk parameter values correspond to the order of the chunk indices associated with the logical threads of control. The operation First of the iterator type that has a Chunk parameter is called on the loop iterator, with Chunk initialized from the corresponding chunk index, to produce the initial value for the loop parameter. If the result of calling Has_Element on this initial value is False, then the execution of the logical thread of control is complete. Otherwise, the sequence_of_statements is conditionally executed, and then the Next operation of the iterator type that has a Chunk parameter is called with the loop iterator, the current value of the loop parameter, and the corresponding chunk index, 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 associated parallel construct is left as a consequence of a transfer of control.

10.4/5

In the absence of a transfer of control, the associated parallel construct of a parallel generalized iterator is complete when all of its logical threads of control are complete.

11/5

For an array component iterator, the chunk_specification of the associated parallel construct, if any, is first elaborated to determine the maximum number of chunks (see 5.5), and then the iterable_name is evaluated and the denoted array object 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, the sequence_of_statements is conditionally executed with the loop parameter denoting each component of the array for the loop, using a 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 chunk_specification to the determined maximum. The loop iteration proceeds until the 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.

11.a/5
implementation defined

The number of chunks for an array component iterator.

11.1/5

If a chunk_specification with a discrete_subtype_definition is present in the associated parallel construct, then the logical thread of control associated with a given chunk has a chunk parameter initialized with a distinct value from the discrete subtype defined by the discrete_subtype_definition. The values of the chunk parameters are assigned such that they increase in the canonical order of the starting array components for the chunks.

12/5

For a container element iterator, the chunk_specification of the associated parallel construct, if any, is first elaborated 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, an object of the Iterator_View type is created with the discriminant referencing the iterable container object denoted by the iterable_name. This is the iterable container object for the loop. Otherwise, the iterable container object denoted by the iterable_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 loop iterator. For a sequential container element iterator, an object of the default cursor subtype is created (the loop cursor). For a parallel container element iterator, each chunk of iterations will have its own loop cursor, again of the default cursor subtype.

12.a/5
reason

If Iterator_View is specified, we add an extra object and use that object for this iteration. This allows these iterators to automatically use the stable view (defined in each of the language-defined containers) to do the iteration. That eliminates the need to set and clear the tampering with elements indication each time Reference is called; that eliminates substantial overhead as finalization is typically used to implement the tampering reset.

13/5

A container element iterator then proceeds as described above for a generalized iterator, except that each reference to a loop parameter is replaced by a reference to the corresponding loop cursor. For a container element iterator, the loop parameter for each iteration instead denotes an indexing (see 4.1.6) into the iterable container object for the loop, with the only parameter to the indexing being the value of the loop cursor for the given iteration. If the loop parameter is a constant (see above), then the indexing uses the default constant indexing function for the type of the iterable container object for the loop; otherwise it uses the default variable indexing function.

14/4

Any exception propagated by the execution of a generalized iterator or container element iterator is propagated by the immediately enclosing loop statement.

14.a/4
ramification

This text covers exceptions raised by called functions that make up the execution of the iterator as well as exceptions raised by the assignment to the loop parameter or cursor.

Examples

15/5

Example of a parallel generalized loop over an array:

16/5

parallel for Element of Board loop -- See 3.6.1. Element := Element * 2.0; -- Double each element of Board, a two-dimensional array. end loop;

17/5

For examples of use of generalized iterators, see A.18.33 and the corresponding container packages in A.18.2 and A.18.3.

Extensions to Ada 2005

17.a/3

Generalized forms of loop iteration are new.

Incompatibilities With Ada 2012

17.b/4

Corrigendum: Added a rule to ensure that the object being iterated cannot be a component that could disappear before the loop completes. This could be incompatible by making a loop that was legal (and worked correctly, so long as the enclosing object is not modified during the loop) from the original Ada 2012 illegal in corrected Ada 2012. Such loops should be pretty rare, especially as these iterator forms are new to Ada 2012.

17.c/4

Corrigendum: Added rules to reject loops if the call to the default iterator function for a container element iterator is illegal, or if the cursor type of an iterator is limited. These are formally incompatible with original Ada 2012, but as it's unlikely that any Ada 2012 compiler ever allowed the illegal usages in an expansion of a loop (it's much more likely that they would have just caused an internal error in the compiler), this should have no effect in practice.

17.d/4

Corrigendum: Added a requirement that the given subtype statically match the subtype of the element or component for a component element iterator or array component iterator. Original Ada 2012 text allowed any type that covered the subtype of the element or component, but that led to questions of what the meaning was if they are different. In this case, the element is essentially a renaming of the container element, and it doesn't make sense for the constraints to be different. Ignoring explicitly defined constraints in renames is a mistake that we don't want to continue, thus we require static matching. This means that some programs might be illegal, but those programs were misleading at best, and potentially would raise unexpected exceptions because the element values might have been invalid or abnormal with respect to the declared constraint.

Extensions to Ada 2012

17.e/5

For consistency, we now allow a subtype_indication on a generalized iterator, and anonymous access types on all forms of iterator. We introduced a new syntax non-terminal, loop_parameter_subtype_indication to simplfy the wording.

17.f/5

An iterator_filter is now allowed on iterator_specifications. This is mainly for consistency with aggregate and reduction iterators, where it eliminates the need for temporary objects.

Wording Changes from Ada 2012

17.g/4

Corrigendum: Added wording to specify that a loop propagates any exceptions propagated by the execution of an iterator. Since that's what naturally would happen from a macro-style expansion of the parts of an iterator, and no other interpretation makes sense given the way the rest of Ada works, we consider it so unlikely that any Ada 2012 implementation ever did anything else that we don't document this as a possible inconsistency.

17.h/5

Added wording to include the use of the iterator view in a container element iterator.

17.i/5

Added wording to describe the execution of parallel iterators.

5.5.3 Procedural Iterators

1/5

A procedural_iterator invokes a user-defined procedure, passing in the body of the enclosing loop_statement as a parameter of an anonymous access-to-procedure type, to allow the loop body to be executed repeatedly as part of the invocation of the user-defined procedure.

Syntax

2/5

procedural_iterator ::=
iterator_parameter_specification of iterator_procedure_call
[iterator_filter]

3/5

iterator_parameter_specification ::=
formal_part
| (defining_identifier{, defining_identifier})

4/5

iterator_procedure_call ::=
procedure_name
| procedure_prefix iterator_actual_parameter_part

5/5

iterator_actual_parameter_part ::=
(iterator_parameter_association {, iterator_parameter_association})

6/5

iterator_parameter_association ::=
parameter_association
| parameter_association_with_box

7/5

parameter_association_with_box ::=
[ formal_parameter_selector_name => ] <>

8/5

At most one iterator_parameter_association within an iterator_actual_parameter_part shall be a parameter_association_with_box.

Name Resolution Rules

9/5

The name or prefix given in an iterator_procedure_call shall resolve to denote a callable entity C (the iterating procedure) that is a procedure, or an entry renamed as (viewed as) a procedure. [When there is an iterator_actual_parameter_part, the prefix can be an implicit_dereference of an access-to-subprogram value.]

10/5

An iterator_procedure_call without a parameter_association_with_box is equivalent to one with an iterator_actual_parameter_part with an additional parameter_association_with_box at the end, with the formal_parameter_selector_name identifying the last formal parameter of the callable entity denoted by the name or prefix.

11/5

An iterator_procedure_call shall contain at most one iterator_parameter_association for each formal parameter of the callable entity C. Each formal parameter without an iterator_parameter_association shall have a default_expression (in the profile of the view of C denoted by the name or prefix).

12/5

The formal parameter of the callable entity C associated with the parameter_association_with_box shall be of an anonymous access-to-procedure type A.

Legality Rules

13/5

The anonymous access-to-procedure type A shall have at least one formal parameter in its parameter profile. If the iterator_parameter_specification is a formal_part, then this formal_part shall be mode conformant with that of A. If the iterator_parameter_specification is a list of defining_identifiers, the number of formal parameters of A shall be the same as the length of this list.

14/5

[If the name or prefix given in an iterator_procedure_call denotes an abstract subprogram, the subprogram shall be a dispatching subprogram.]

14.a/5
proof

This is stated normatively in 3.9.3.

Static Semantics

15/5

A loop_statement with an iteration_scheme that has a procedural_iterator is equivalent to a local declaration of a procedure P followed by a procedure_call_statement that is formed from the iterator_procedure_call by replacing the <> of the parameter_association_with_box with P'Access. The formal_part of the locally declared procedure P is formed from the formal_part of the anonymous access-to-procedure type A, by replacing the identifier of each formal parameter of this formal_part with the identifier of the corresponding formal parameter or element of the list of defining_identifiers given in the iterator_parameter_specification. The body of P consists of the conditionally executed sequence_of_statements. The procedure P is called the loop body procedure.

15.a/5
implementation note

For a procedural_iterator with an iterator_filter, the body of the routine would be something like:

15.b/5

procedure P ... is begin if iterator_filter then sequence_of_statements end if; end P;

16/5

In a procedural iterator, the Parallel_Calls aspect (see 9.10.1) of the loop body procedure is True if the reserved word parallel occurs in the corresponding loop statement, and False otherwise.

17/5

The following aspects may be specified for a callable entity S that has exactly one formal parameter of an anonymous access-to-subprogram type:

18/5

Allows_Exit
The Allows_Exit aspect is of type Boolean. The specified value shall be static. The Allows_Exit aspect of an inherited primitive subprogram is True if Allows_Exit is True either for the corresponding subprogram of the progenitor type or for any other inherited subprogram that it overrides. If not specified or inherited as True, the Allows_Exit aspect of a callable entity is False. For an entry, only a confirming specification of False is permitted for the Allows_Exit aspect.
18.a/5
reason

An entry does not allow exit, because implementing a transfer of control out of a task or protected entry creates unnecessarily complex dynamic semantics.

19/5
Specifying the Allows_Exit aspect to be True for a subprogram indicates that the subprogram allows exit, meaning that it is prepared to be completed by arbitrary transfers of control from the loop body procedure[, including propagation of exceptions. A subprogram for which Allows_Exit is True should use finalization as appropriate rather than exception handling to recover resources and make any necessary final updates to data structures].
19.a/5

Aspect Description for Allows_Exit: An indication of whether a subprogram will operate correctly for arbitrary transfers of control.

19.b/5
ramification

A subprogram that does not need cleanup satisfies the requirements, and thus can specify Allows_Exit as True. If a subprogram S allows exit, it cannot expect to get control other than via finalization if the loop body procedure initiates a transfer of control as part of a procedural_iterator. In particular, exception handlers in S, even when others handlers, will not be executed when a transfer of control occurs. The mechanism that the implementation uses to implement such transfers of control needs to avoid triggering exception handlers.

20/5

Parallel_Iterator
The Parallel_Iterator aspect is of type Boolean. The specified value shall be static. The Parallel_Iterator aspect of an inherited primitive subprogram is True if Parallel_Iterator is True either for the corresponding subprogram of the progenitor type or for any other inherited subprogram that it overrides. If not specified or inherited as True, the Parallel_Iterator aspect of a callable entity is False.
21/5
Specifying the Parallel_Iterator aspect to be True for a callable entity indicates that the entity is allowed to invoke the loop body procedure from multiple distinct logical threads of control. The Parallel_Iterator aspect for a subprogram shall be statically False if the subprogram allows exit.
21.a/5

Aspect Description for Parallel_Iterator: An indication of whether a subprogram may use multiple threads of control to invoke a loop body procedure.

21.b/5
reason

Permitting exit from a parallel procedural iterator introduces additional semantic and implementation complexity.

Legality Rules

22/5

If a callable entity overrides an inherited dispatching subprogram that allows exit, the overriding callable entity also shall allow exit. If a callable entity overrides an inherited dispatching subprogram that has a True Parallel_Iterator aspect, the overriding callable entity also shall have a True Parallel_Iterator aspect.

22.a/5
ramification

Since an entry never allows exit, attempting to implement an allows exit subprogram with a task or protected entry is always illegal. However, the Parallel_Iterator aspect can be applied to an entry, so a subprogram with the Parallel_Iterator aspect True can be implemented by an entry.

23/5

A loop_statement with a procedural_iterator as its iteration_scheme shall begin with the reserved word parallel if and only if the callable entity identified in the iterator_procedure_call has a Parallel_iterator aspect of True.

24/5

If the actual parameter of an anonymous access-to-subprogram type, passed in an explicit call of a subprogram for which the Parallel_Iterator aspect is True, is of the form P'Access, the designated subprogram P shall have a Parallel_Calls aspect True (see 9.10.1).

25/5

The sequence_of_statements of a loop_statement with a procedural_iterator as its iteration_scheme shall contain an exit_statement, return statement, goto_statement, or requeue_statement that leaves the loop only if the callable entity associated with the procedural_iterator allows exit.

26/5

The sequence_of_statements of a loop_statement with a procedural_iterator as its iteration_scheme shall not contain an accept_statement whose entry_declaration occurs outside the loop_statement.

26.a/5
reason

An accept_statement is not allowed in a procedure (see 9.5.2), it has to be directly in a task_body. Since the loop body here is implemented as a procedure, we can't allow accept_statements there, either, even if the loop itself is directly in a task_body.

26.b/5
ramification

This includes cases where the accept_statement is part of another construct, for instance, a select_statement.

Dynamic Semantics

27/5

[For the execution of a loop_statement with an iteration_scheme that has a procedural_iterator, the procedure denoted by the name or prefix of the iterator_procedure_call (the iterating procedure) is invoked, passing an access value designating the loop body procedure as a parameter. The iterating procedure then calls the loop body procedure zero or more times and returns, whereupon the loop_statement is complete. If the parallel reserved word is present, the iterating procedure is allowed to invoke the loop body procedure from multiple distinct logical threads of control.] The aspect_specification, if any, is elaborated prior to the invocation of the iterating procedure.

27.a/5
proof

The stated dynamic semantics are implied by the static semantics given above and the bounded errors given below.

Bounded (Run-Time) Errors

28/5

If the callable entity identified in the iterator_procedure_call allows exit, then it is a bounded error for a call of the loop body procedure to be performed from within an abort-deferred operation (see 9.8), unless the entire loop_statement was within the same abort-deferred operation. If detected, Program_Error is raised at the point of the call; otherwise, a transfer of control from the sequence_of_statements of the loop_statement will not necessarily terminate the loop_statement, and the loop body procedure can be called again.

29/5

If a loop_statement with the procedural_iterator as its iteration_scheme (see 5.5) does not begin with the reserved word parallel, it is a bounded error if the loop body procedure is invoked from a different logical thread of control than the one that initiates the loop_statement. If detected, Program_Error is raised; otherwise, conflicts associated with concurrent executions of the loop body procedure can occur without being detected by the applicable conflict check policy (see 9.10.1). Furthermore, propagating an exception or making an attempt to exit in the presence of multiple threads of control will not necessarily terminate the loop_statement, deadlock can occur, or the loop body procedure can be called again.

29.a/5
discussion

Other Ada rules are still in effect for the allows exit subprogram A, of course. For instance, if a transfer of control causes finalization which raises an exception, Program_Error will be propagated by A (rather than the transfer of control). In such a case, the bounded error above would still apply. Another example is the case where an unrelated task is waiting on the normal completion of the loop body procedure call in A. Such a task might end up waiting forever if a transfer of control happens (this is a deadlock situation). This case does not require additional wording, as the same thing would happen if an exception is propagated from the loop body procedure or if A executed a transfer of control (such as a return statement).

Examples

30/5

Example of iterating over a map from My_Key_Type to My_Element_Type (see A.18.4):

31/5

for (C : Cursor) of My_Map.Iterate loop Put_Line (My_Key_Type'Image (Key (C)) & " => " & My_Element_Type'Image (Element (C))); end loop; 32/5 -- The above is equivalent to: 33/5 declare procedure P (C : Cursor) is begin Put_Line (My_Key_Type'Image (Key (c)) & " => " & My_Element_Type'Image (Element (C))); end P; begin My_Map.Iterate (P'Access); end;

34/5

Example of iterating over the environment variables (see A.17):

35/5

for (Name, Val) of Ada.Environment_Variables.Iterate(<>) loop -- "(<>)" is optional because it is the last parameter Put_Line (Name & " => " & Val); end loop; 36/5 -- The above is equivalent to: 37/5 declare procedure P (Name : String; Val : String) is begin Put_Line (Name & " => " & Val); end P; begin Ada.Environment_Variables.Iterate (P'Access); end;

Extensions to Ada 2012

37.a/5

Procedural iterators, and the Allows_Exit and Parallel_Iterator aspects are new in Ada 2022.