John Barnes

# 4.4 Aggregates

There are important changes to aggregates in Ada 2005 which are very useful in a number of contexts. These were triggered by the changes to the rules for limited types which are described in the next section, but it is convenient to first consider aggregates separately.
The main change is that the box notation <> is now permitted as the value in a named aggregate. The meaning is that the component of the aggregate takes the default value if there is one.
So if we have a record type such as
type RT is
record
A: Integer := 7;
B: access Integer;
C: Float;
end record;
then if we write
X: RT := (A => <>, B => <>, C => <>);
then X.A has the value 7, X.B has the value null and X.C is undefined. So the default value is that given in the record type declaration or, in the absence of such an explicit default value, it is the default value for the type. If there is no explicit default value and the type does not have one either then the value is simply undefined as usual.
The above example could be abbreviated to
X: RT := (others => <>);
The obvious combinations are allowed
(A => <>, B => An_Integer'Access, C => 2.5)
(A => 3, others => <>)
(A => 3, B | C => <>)
The last two are the same. There is a rule in Ada 95 that if several record components in an aggregate are given the same expression using a | then they have to be of the same type. This does not apply in the case of <> because no typed expression is involved.
The <> notation is not permitted with positional notation. So we cannot write
(3, <>, 2.5)    -- illegal
But we can mix named and positional notations in a record aggregate as usual provided the named components follow the positional ones, so the following are permitted
(3, B => <>, C => 2.5)
(3, others => <>)
A minor but important rule is that we cannot use <> for a component of an aggregate that is a discriminant if it does not have a default. Otherwise we could end up with an undefined discriminant.
The <> notation is also allowed with array aggregates. But in this case the situation is much simpler because it is not possible to give a default value for array components. Thus we might have
P: array (1.. 1000) of Integer := (1 => 2, others => <>);
The array P has its first component set to 2 and the rest undefined. (Maybe P is going to be used to hold the first 1000 prime numbers and we have a simple algorithm to generate them which requires the first prime to be provided.) The aggregate could also be written as
(2, others => <>)
Remember that others is permitted with a positional array aggregate provided it is at the end. But otherwise <> is not allowed with a positional array aggregate.
We can add others => <> even when there are no components left. This applies to both arrays and records.
The box notation is also useful with tasks and protected objects used as components. Consider
protected type Semaphore is ... ;
type PT is
record
Guard: Semaphore;
Count: Integer;
Finished: Boolean := False;
end record;
As explained in the next section, we can now use an aggregate to initialize an object of a limited type. Although we cannot give an explicit initial value for a Semaphore we would still like to use an aggregate to get a coverage check as mentioned in Section 1.3.3. So we can write
X: PT := (Guard => <>, Count => 0, Finished => <>);
Note that although we can use <> to stand for the value of a component of a protected type in a record we cannot use it for a protected object standing alone.
Sema: Semaphore := <>;    -- illegal
The reason is that there is no need since we have no coverage check to concern us and there could be no other reason for doing it anyway.
Similarly we can use <> with a component of a private type as in
type Secret is private;
type Visible is
record
A: Integer;
S: Secret;
end record;
X: Visible := (A => 77; S => <>);
but not when standing alone
S: Secret := <>;    -- illegal
It would not have any purpose because such a variable will take any default value anyway.
We conclude by mentioning a small point for the language lawyer. Consider
function F return Integer;
type T is
record
A: Integer := F;
B: Integer := 3;
end record;
Writing
X: T := (A => 5, others => <>);    -- does not call F
is not quite the same as
X: T;    -- calls F
...
X.A := 5;  X.B := 3;
In the first case the function F is not called whereas in the second case it is called when X is declared in order to default initialize X.A. If it had a nasty side effect then this could matter. But then programmers should not use nasty side effects anyway.

© 2005, 2006, 2007 John Barnes Informatics.