Rationale for Ada 2005
3.5 Access types and discriminants
This final topic concerns two matters. The first
is about accessing components of discriminated types that might vanish
or change mysteriously and the second is about type conversions.
Recall that we can
have a mutable variant record such as
type Gender is (Male, Female, Neuter);
type Mutant(Sex: Gender := Neuter) is
record
case Sex is
when Male =>
Bearded: Boolean;
when Female =>
Children: Integer;
when Neuter =>
null;
end case;
end record;
This represents a world
in which there are three sexes, males which can have beards, females
which can bear children, and neuters which are fairly useless. Note the
default value for the discriminant. This means that if we declare an
unconstrained object thus
The_Thing: Mutant;
then The_Thing
is neuter by default but could have its sex changed by a whole record
assignment thus
The_Thing := (Sex => Male, Bearded => True);
It now is Male and has
a beard.
The problem with this sort of object is that components
can disappear. If it were changed to be Female
then the beard would vanish and be replaced by children. Because of this
ghostly behaviour certain operations on mutable objects are forbidden.
One obvious rule is
that it is not permissible to rename components which might vanish. So
Hairy: Boolean renames The_Thing.Bearded; -- illegal
is not permitted. This was an Ada 83 rule. It was
probably the case that the rules were watertight in Ada 83. However,
Ada 95 introduced many more possibilities. Objects and components could
be marked as aliased and the Access
attribute could be applied. Additional rules were then added to prevent
creating references to things that could vanish.
However, it was then discovered that the rules in
Ada 95 regarding access types were not watertight. Accordingly various
attempts were made to fix them in a somewhat piecemeal fashion. The problems
are subtle and do not seem worth describing in their entirety in this
general presentation. We will content ourselves with just a couple of
examples.
In Ada 95 we can declare
types such as
type Mutant_Name is access all Mutant;
type Things_Name is access all Mutant(Neuter);
Naturally enough an object of type Things_Name
can only be permitted to reference a Mutant
whose Sex is Neuter.
Some_Thing: aliased Mutant;
Thing_Ptr: Things_Name := Some_Thing'Access;
Things would now go wrong if we allowed Some_Thing
to have a sex change. Accordingly there is a rule in Ada 95 that says
that an aliased object such as Some_Thing
is considered to be constrained. So that is quite safe.
However, matters get
more difficult when a type such as Mutant
is used for a component of another type such as
type Monster is
record
Head: Mutant(Female);
Tail: aliased Mutant;
end record;
Here we are attempting
to declare a nightmare monster whose head is a female but whose tail
is deceivingly mutable. Those with a decent education might find that
this reminds them of the Sirens who tempted Odysseus by their beautiful
voices on his trip past the monster Scylla and the whirlpool Charybdis.
Those with an indecent education can compare it to a pantomime theatre
horse (or mare, maybe indeed a nightmare). We could then write
M: Monster;
Thing_Ptr := Monster.Tail'Access;
However, there is an Ada 95 rule that says that the
Tail has to be constrained since it is aliased
so the type Monster is not allowed. So far
so good.
But now consider the
following very nasty example
generic
type T is private;
Before, After: T;
type Name is access all T;
A_Name: in out Name;
package Sex_Change is end;
package body Sex_Change is
type Single is array (1..1) of aliased T;
X: Single := (1 => Before);
begin
A_Name := X(1)'Access;
X := (1 => After);
end Sex_Change;
and then
A_Neuter: Mutant_Name(Neuter); -- fixed neuter
package Surgery is new Sex_Change(
T => Mutant,
Before => (Sex => Neuter),
After => (Sex => Male, Bearded => True),
Name => Mutant_Name,
A_Name => A_Neuter);
-- instantiation of Surgery makes A_Neuter hairy
The problem here is that there are loopholes in the
checks when the package Sex_Change is elaborated.
The object A_Name is assigned an access to
the single component of the array X whose
value is Before. When this is done there is
a check that the component of the array has the correct subtype. However
the subsequent assignment to the whole array changes the value of the
component to After and this can change the
subtype of X(1) surreptitiously and there
is no check concerning A_Name. The key point
is that the generic doesn't know that the type T
is mutable; this information is not part of the generic contract.
So when we instantiate Surgery
(at the same level as the type Mutant_Name
so that accessibility succeeds), the object A_Neuter
suddenly finds that it has grown a beard!
A similar difficulty
occurs when private types are involved because the partial view and full
view might disagree about whether the type is constrained or not. Consider
package Beings is
type Mutant is private;
type Mutant_Name is access Mutant;
F, M: constant Mutant;
private
type Mutant(Sex: Gender := Neuter) is
record
... -- as above
end record;
F: constant Mutant := (Female, ... );
M: constant Mutant := (Male, ... );
end Beings;
Now suppose some innocent
user (who has not peeked at the private part) writes
Chris: Mutant_Name := new Mutant'(F); -- OK
...
Chris.all := M; --raises Constraint_Error
This is very surprising. The user cannot see that
the type Mutant is mutable and in particular
cannot see that M and F
are different in some way. From the outside they just look like constants
of the same type. The big trouble is that there is a rule in Ada 95 that
says that an object created by an allocator is constrained. So the new
object referred to by Chris is permanently
Female and therefore the attempt to assign
the value of M with its Bearded
component to her is doomed.
Attempting to fix these and related problems with
a number of minimal rules seemed fated not to succeed. So a different
approach has been taken. Rather than saying that aliased and allocated
objects are always treated as constrained so that accessed components
do not disappear, Ada 2005 takes the approach of preventing the Access
attribute from being applied in certain circumstances by disallowing
certain access subtypes at all. In particular, general access subtypes
which refer to types with defaults for their discriminants are forbidden.
The net outcome is that the declaration of A_Neuter
is illegal because we cannot write Mutant_Name(Neuter)
and so the Surgery cannot be applied to constrained
mutants. On the other hand, Chris is allowed to change sex because the
allocated objects are no longer automatically constrained in the case
of private types whose partial view does not have discriminants.
These changes introduce some minor incompatibilities
which are explained with further examples in the Epilogue.
The other change in
this area concerns type conversions. A variation on the gender theme
is illustrated by the following
type Gender is (Male, Female);
type Person(Sex: Gender) is
record
case Sex is
when Male =>
Bearded: Boolean;
when Female =>
Children: Integer;
end case;
end record;
Note that this type is not mutable so all persons
are stuck with their sex from birth.
We might now declare
some access types
type Person_Name is access all Person;
type Mans_Name is access all Person(Male);
type Womans_Name is access all Person(Female);
so that we can manipulate
various names of people. We would naturally use Person_Name
if we did not know the sex of the person and otherwise use Mans_Name
or Womans_Name as appropriate. We might have
It: Person_Name := Chris'Access;
Him: Mans_Name := Jack'Access;
Her: Womans_Name := Jill'Access;
If we later discover
that Chris is actually Christine then we might like to assign the value
in It to a more appropriate variable such
as Her. So we would like to write
Her := Womans_Name(It);
But curiously enough
this is not permitted in Ada 95 although the reverse conversion
It := Person_Name(Her);
is permitted. The Ada 95 rule is that any constraints
have to statically match or the conversion has to be to an unconstrained
type. Presumably the reason was to avoid checks at run time. But this
lack of symmetry is unpleasant and the rule has been changed in Ada 2005
to allow conversion in both directions with a run time check as necessary.
The above example is actually Exercise 19.8(1) in
the textbook
[6]. The poor student was
invited to solve an impossible problem. But they will be successful in
Ada 2005.
© 2005, 2006, 2007 John Barnes Informatics.
Sponsored in part by: