4.3.5 Container Aggregates
Term entry: container aggregate
— construct used to define a value of a type that represents a
collection of elements, by explicitly specifying the elements in the
collection
{
AI12-0212-1}
For a type other than an array type, the following type-related operational
aspect may be specified:
Aggregate
This aspect is an
aggregate
of the form:
Aspect Description for Aggregate:
Mechanism to define user-defined aggregates.
(Empty =>
name[,
Add_Named =>
procedure_name][,
Add_Unnamed =>
procedure_name][,
New_Indexed =>
function_name,
Assign_Indexed =>
procedure_name])
Ramification: {
AI22-0020-1}
As this aspect is described with syntax, it has to be given exactly as
specified here. That means it cannot be given as a positional aggregate,
nor can the order of the elements be changed. For instance, New_Indexed
always has to occur before Assign_Indexed, and Empty always has to be
first.
{
AI12-0212-1}
The type for which this aspect is specified is known as the
container
type of the Aggregate aspect.
A
procedure_name
shall be specified for at least one of Add_Named, Add_Unnamed, or Assign_Indexed.
If Add_Named is specified, neither Add_Unnamed nor Assign_Indexed shall
be specified. Either both or neither of New_Indexed and Assign_Indexed
shall be specified.
Name Resolution Rules
{
AI12-0212-1}
{
AI22-0002-1}
The
name specified
for Empty for an Aggregate aspect shall denote
a
constant of the container type, or denote exactly one function
with a result type of the container type that has no parameters, or that
has one
in parameter of a signed integer type.
Reason: The parameter
of In the function case, the parameter, if present,
may be used to specify an initial size for the container, in anticipation
of adding elements to it. For a positional aggregate, or a named aggregate
that doesn't use an iterator, it will be initialized with the number
of elements. For a named aggregate that uses an iterator, the implementation
is permitted to estimate the number of elements that the iterator will
produce, but it is not required to do so.
{
AI12-0212-1}
{
AI12-0437-1}
{
AI22-0021-1}
The
procedure_name
specified for Add_Unnamed for an Aggregate aspect shall denote exactly
one procedure that has two parameters, the first an
in out parameter
of the container type, and the second an
in parameter
whose
type is of some nonlimited type,
called the
element type of the container type.
{
AI12-0212-1}
{
AI12-0437-1}
{
AI22-0021-1}
The
function_name
specified for New_Indexed for an Aggregate aspect shall denote exactly
one function with a result type of the container type, and two parameters
of the same
discrete type,
called with
that type being the
key type of the container type.
Reason: The New_Indexed function is used
instead of Empty as the first step of creating an aggregate that is initialized
using the Assign_Indexed procedure.
{
AI12-0212-1}
{
AI12-0437-1}
{
AI22-0021-1}
The
procedure_name
specified for Add_Named or Assign_Indexed for an Aggregate aspect shall
denote exactly one procedure that has three parameters, the first an
in out parameter of the container type, the second an
in
parameter
whose of
a nonlimited type
is called (the
key type of the container type
),
and the third, an
in parameter
whose of
a nonlimited type
that is called
the
element type of the container type.
Legality Rules
{
AI12-0212-1}
{
AI22-0002-1}
If the container type of an Aggregate aspect is a private type, the full
type of the container type shall not be an array type.
If the container type is limited, the name specified for Empty shall
denote a function rather than a constant object.
{
AI12-0212-1}
{
AI22-0021-1}
For an Aggregate aspect,
neither the element type
nor the key type (if any) of the container type shall be a limited type.
Additionally, the key type of Assign_Indexed shall be the same
type as that of the parameters of New_Indexed
,
and that type shall be a discrete type.
If Additionally,
if both Add_Unnamed and Assign_Indexed are specified, the final
parameters shall be of the same type — the element type of the
container type.
{
AI22-0021-1}
None of the subprograms specified for an Aggregate
aspect shall have a formal access parameter, nor an explicitly aliased
formal parameter.
Reason: Both access
parameters and explicitly aliased parameters add additional requirements
on the corresponding actual parameter. These complicate the description
and implementation of container aggregates with little benefit. Thus
we make them illegal. This means neither the element type nor the key
type (if any) of the container type can be an anonymous access type.
{
AI22-0032-1}
If the container type T is not abstract,
then none of the subprograms specified in the Aggregate aspect for T
shall be abstract.
Ramification: Since
the Aggregate aspect is nonoverridable, this rule is rechecked on any
derived type that inherits an Aggregate aspect.
{
AI22-0032-1}
In addition to the places where
Legality Rules normally apply (see 12.3),
these rules apply also in the private part of an instance of a generic
unit.
Ramification: The
first Legality Rule has to be rechecked in instances, if a type with
an Aggregate aspect is derived from a formal private type, in case the
actual type for the private type is an array type. The last Legality
Rule has to be rechecked in instances, in case an actual type for an
abstract formal type is not abstract.
Static Semantics
Syntax
Reason: {
AI12-0212-1}
Unlike other
aggregates,
container aggregates have to be surrounded with brackets rather than
parentheses. Using brackets allows writing zero- and one-element positional
aggregates.
(Were we to use parentheses, a one-element positional aggregate would
look the same as and would always be interpreted as a parenthesized expression.)
Unlike the case for arrays, where it is always possible to give bounds
to work around this gap, such an
aggregate
could not be written at all for a sequence type (such as a list) where
there is no index or key to use.
Name Resolution Rules
Legality Rules
Reason: Only an element of an indexed
aggregate can be left uninitialized, because the compiler can simply
omit producing an Assign_Indexed operation for the given index. For other
kinds of aggregates, there are no operations that add an element without
giving it a value. We could have defined such (optional) operations,
but they would have added significant complication to an already complex
feature without adding much additional expressive power. Note that, to
be consistent with array aggregates, we do not support specifying <>
in an
iterated_element_association.
Ramification: If there is a
key_expression
in an
iterated_element_association,
it determines the key of each added key/value pair, rather than the loop
parameter. But if there is no
key_expression,
the loop parameter itself is used as the key.
Dynamic Semantics
if the
aggregate
is an indexed aggregate, from the result of a call on the New_Indexed
function; the actual parameters in this call represent the lower and
upper bound of the
aggregate,
and are determined as follows:
if the
aggregate
is a
positional_container_aggregate,
the lower bound is the low bound of the subtype of the key parameter
of the Add_Indexed procedure, and the upper bound has a position number
that is the sum of the position number of the lower bound and one less
than the number of
expressions
in the
aggregate;
{
AI22-0002-1}
if the
aggregate
is not an indexed aggregate,
by assignment from
the Empty constant, or from a call on the Empty function specified
in the Aggregate aspect. In the case of an Empty function with a formal
parameter, the actual parameter has the following value:
otherwise, to an implementation-defined
value.
Implementation Note: This value ought
to be an estimate for the number of elements in the
aggregate,
if one is available. If not, it is suggested to use the default value
for the parameter if one exists, and zero otherwise.
for a
positional_container_aggregate
of a type with a specified Add_Unnamed procedure, each
expression
is evaluated in an arbitrary order,
and the Add_Unnamed
procedure is invoked in sequence with the anonymous object
A as
the first parameter and the result of evaluating each
expression
as the second parameter, in the order of the
expressions;
{
AI22-0031-1}
for a
positional_container_aggregate
that is an indexed aggregate, each
expression
is evaluated in an arbitrary order,
and the Assign_Indexed
procedure is invoked
once for each expression of
the aggregate in
sequence with the anonymous object
A as the first parameter,
the key value as the second parameter, computed by starting with the
low bound of the subtype of the key formal parameter of the Assign_Indexed
procedure and taking the successor of this value for each successive
expression of the aggregate,
and the result of evaluating each
expression
as the third parameter;
otherwise, with the loop parameter as
the second parameter;
for a
named_container_aggregate
that is an indexed aggregate, the evaluation proceeds as above for the
case of Add_Named, but with the Assign_Indexed procedure being invoked
instead of Add_Named; in the case of a
container_element_association
with a <> rather than an
expression,
the corresponding call on Assign_Indexed is not performed, leaving the
component as it was upon return from the New_Indexed function;
1.
2.
{
AI12-0212-1}
{
AI12-0327-1}
an iteration is performed, and for each value conditionally produced
by the iteration (see
5.5 and
5.5.2)
the Add_Unnamed procedure is invoked, with the anonymous object
A
as the first parameter and the result of evaluating the
expression
as the second parameter.
{
AI22-0031-1}
When the above wording says that a subprogram is
invoked or called, this is a subprogram call as defined in 6.4,
with parameter associations as specified in the wording evaluated as
defined 6.4.1. [In particular, this means
that the parameters are converted to the subtype of the formal parameter
(which can raise an exception — see 4.6).]
Examples
{
AI12-0212-1}
{
AI12-0429-1}
Examples of specifying the Aggregate aspect for a Set_Type, a Map_Type,
and a Vector_Type:
-- Set_Type is a set-like container type.
type Set_Type is private
with Aggregate => (Empty => Empty_Set,
Add_Unnamed => Include);
function Empty_Set return Set_Type;
subtype Small_Int is Integer range -1000..1000;
procedure Include (S : in out Set_Type; N : in Small_Int);
-- Map_Type is a map-like container type.
type Map_Type is private
with Aggregate => (Empty => Empty_Map,
Add_Named => Add_To_Map);
procedure Add_To_Map (M : in out Map_Type;
Key : in Integer;
Value : in String);
Empty_Map : constant Map_Type;
-- Vector_Type is an extensible array-like container type.
type Vector_Type is private
with Aggregate => (Empty => Empty_Vector,
Add_Unnamed => Append_One,
New_Indexed => New_Vector,
Assign_Indexed => Assign_Element);
function Empty_Vector (Capacity : Integer := 0) return Vector_Type;
procedure Append_One (V : in out Vector_Type; New_Item : in String);
procedure Assign_Element (V : in out Vector_Type;
Index : in Positive;
Item : in String);
function New_Vector (First, Last : Positive) return Vector_Type
with Pre => First = Positive'First;
-- Vectors are always indexed starting at the
-- lower bound of their index subtype.
-- Private part not shown.
-- Example aggregates using Set_Type.
S : Set_Type;
-- Assign the empty set to S:
S := [];
-- Is equivalent to:
S := Empty_Set;
-- A positional set aggregate:
S := [1, 2];
-- Is equivalent to:
S := Empty_Set;
Include (S, 1);
Include (S, 2);
-- Is equivalent to:
S := Empty_Set;
for Item in 1 .. 5 loop
Include (S, Item * 2);
end loop;
{
AI12-0379-1}
--
Is equivalent (assuming set semantics) to:
S := Empty_Set;
for Item
in 1 .. 5
loop
Include (S, Item);
end loop;
for Item
in -5 .. -1
loop
Include (S, Item);
end loop;
-- Example aggregates using Map_Type.
M : Map_Type;
-- A simple named map aggregate:
M := [12 => "house", 14 => "beige"];
-- Is equivalent to:
M := Empty_Map;
Add_To_Map (M, 12, "house");
Add_To_Map (M, 14, "beige");
-- Define a table of pairs:
type Pair is record
Key : Integer;
Value : access constant String;
end record;
Table : constant array(Positive range <>) of Pair :=
[(Key => 33, Value => new String'("a nice string")),
(Key => 44, Value => new String'("an even better string"))];
-- Is equivalent to:
M := Empty_Map;
for P of Table loop
Add_To_Map (M, P.Key, P.Value.all);
end loop;
-- Create an image table for an array of integers:
Keys : constant array(Positive range <>) of Integer := [2, 3, 5, 7, 11];
--
A map aggregate where the values produced by the
--
iterated_element_association are of the same type as the key
--
(hence a separate key_expression is unnecessary):
M := [
for Key
of Keys => Integer'Image (Key)];
-- Is equivalent to:
M := Empty_Map;
for Key of Keys loop
Add_To_Map (M, Key, Integer'Image (Key));
end loop;
-- Example aggregates using Vector_Type.
V : Vector_Type;
-- A positional vector aggregate:
V := ["abc", "def"];
-- Is equivalent to:
V := Empty_Vector (2);
Append_One (V, "abc");
Append_One (V, "def");
-- An indexed vector aggregate:
V := [1 => "this", 2 => "is", 3 => "a", 4 => "test"];
-- Is equivalent to:
V := New_Vector (1, 4);
Assign_Element (V, 1, "this");
Assign_Element (V, 2, "is");
Assign_Element (V, 3, "a");
Assign_Element (V, 4, "test");
Extensions to Ada 2012
Inconsistencies With Ada 2022
{
AI22-0031-1}
Corrigendum: Expressions
in a named_container_aggregate
are evaluated once for each key value. This is consistent with other
kinds of aggregate.
Original Ada 2022 evaluated the expressions once per association. If
a program depends on the number of times the expressions are evaluated,
the behavior could change. This sort of program is unusual, and most
depend on each component having a separate evaluation, so this change
is more likely to fix a bug than cause one.
Incompatibilities With Ada 2022
{
AI22-0002-1}
Corrigendum: Unlike
original Ada 2022, Empty can only be given by a function, not by a constant,
as a constant does not make sense when the associated type is derived.
As a new Ada 2022 feature, the specification of container aggregates
should be rare, and the fix is easy: just replace the constant by an
expression function returning the constant.
{
AI22-0021-1}
Corrigendum:Adjusted the rules about resolution
of subprograms to avoid depending upon properties like limitedness of
the parameter types (we do not do this elsewhere in the language). Also,
some rules were added to make some unusual cases illegal that could have
caused the aggregate implementation to be illegal. In both of these cases,
unusual programs that were legal in original Ada 2022 are illegal in
Ada 202y. As this is a new Ada 2022 feature, and the situations involved
are unlikely, it is not expected that this will affect any existing code.
{
AI22-0032-1}
Corrigendum:The subprograms specified for
an Aggregate aspect cannot be abstract unless the associated type is
abstract; abstract subprograms were allowed in original Ada 2022. It
is highly unlikely that any code using abstract subprograms in an Aggregate
aspect exists, as such subprograms have no body but will be called when
an aggregate is used for the type (usually causing internal errors in
compilers).
Extensions to Ada 2022
Wording Changes from Ada 2022
{
AI22-0031-1}
Corrigendum: Added wording to clarify that
calls defined in this subclause are evaluated as calls are defined in
6.4. While this could cause a program to raise
an exception where it previously did not, this is just specifying otherwise
unspecified behavior and does so as as Ada programmer would expect.
{
AI22-0031-1}
Corrigendum: Increased flexibility by removing
an order requirement on the calls to Add_Indexed. This does not require
any change to an existing implementation or program.
Ada 2005 and 2012 Editions sponsored in part by Ada-Europe