Rationale for Ada 2005
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. 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 John Barnes Informatics.
Sponsored in part by: