Version 1.7 of ais/ai-00404.txt
!standard 3.10(6) 05-08-15 AI95-00404/06
!standard 3.9.2(11)
!class amendment 05-01-28
!status Amendment 200Y 05-03-14
!status ARG Approved 6-0-2 05-02-14
!status work item 05-01-28
!status received 05-01-28
!status received
!priority High
!difficulty Easy
!subject Not null and all in access parameters and types
!summary
Not null is implicit, but may be given explicitly, for controlling
access parameters. When defined by a renaming or
a generic instantiation, the controlling access parameters
must be null excluding. Note that controlling access results
are not automatically null excluding.
All is not permitted as a general access modifier for anonymous access
types.
!problem
The current proposal for Ada 2005 permits the specification of not null
for controlling access parameters even though they have to be not null
anyway. Similarly it permits the specification of all in anonymous
access types even though they never are pool specific and so all is
superfluous. This provides the user with several different ways of
saying the same thing which is confusing.
However, specifying not null for controlling access parameters provides
helpful documentation, and which parameters are controlling might
change, or might not be obvious, so this sort of specification should
be permitted, despite being redundant.
!proposal
(See summary.)
!wording
Modify 3.10(6) [as previously modified by AI-231 and AI-254] to
...
access_definition ::=
[null exclusion] access [constant] subtype_mark |
| [null exclusion] access [protected] procedure parameter_profile
| [null exclusion] access [protected] function parameter_and_result_profile
Add after 3.9.2(11):
If a dispatching operation is defined by a subprogram_renaming_declaration
or the instantiation of a generic subprogram, any access parameter of the
renamed subprogram or the generic subprogram that corresponds to a
controlling access parameter of the dispatching operation, shall have
a subtype that excludes null.
AARM Note on incompatibility:
This rule will require the addition of an explicit "not null" on
non-dispatching operations that are later renamed to be dispatching, or
on a generic that is used to define a dispatching operation.
!discussion
The draft rationale says:
For uniformity, Ada 2005 permits all three forms with anonymous access types.
And we can also add not null so we can write all the following in Ada 2005:
procedure P1(X: access T)
procedure P2(X: access constant T);
procedure P3(X: access all T);
procedure P4(X: not null access T);
procedure P5(X: not null access constant T);
procedure P6(X: not null access all T);
Note that two anomalies remain. One is that access T is deemed to be
short for access all T when it occurs in an anonymous access type in
order to permit compatibility between Ada 95 and Ada 2005. And so P1
and P3 are the same.
To have required all existing users to insert all in their access
parameters would have been too much of a burden. Similarly P4 and P6
are the same as well.
Moreover, as mentioned above, there is also the anomaly that controlling
parameters are still always null excluding and so in that case P1, P3,
P4, and P6 are all the same.
It is very confusing to have unnecessary ways of saying the same thing.
We are stuck with access T as a parameter as being general because of
backwards compatibility with Ada 95.
It is therefore proposed that anonymous access types should just take the
form
AV: access T;
AC: access constant T;
and that
AV: access all T;
should not be permitted.
This makes them behave like normal object declarations where we either
have constant or nothing.
The anomaly is with named access types having to distinguish between
pool-specific and general access types. There is no reason why this should
be extended to anonymous access types.
Similarly, controlling access parameters have to be null excluding but
for compatibility with Ada 95 we cannot require the programmer to insert
not null everywhere. We considered disallowing an explicit not null
where they are implicit, but it was anticipated that after a transition
period, explicit not null would be used everywhere it applies, and disallowing
its use for controlling access parameters would defeat this.
For a dispatching operation defined by renaming or instantiation, we require
that the renamed subprogram or generic subprogram be null excluding for each
access parameter or access result that ends up controlling in the renaming or
the instance. For example:
generic
type GT is private;
procedure Gen_Subp_1(Y : access GT);
generic
type GT is private;
procedure Gen_Subp_2(Y : not null access GT);
with Gen_Subp_1; with Gen_Subp_2;
package P is
type T is tagged ....
package Inner is
procedure Not_Disp_1(X : access T);
procedure Not_Disp_2(X : not null access T);
end Inner;
procedure Ren1(X : access T) renames Inner.Not_Disp_1; --
procedure Ren2(X : access T) renames Inner.Not_Disp_2; --
procedure Inst1 is new Gen_Subp_1(T); --
procedure Inst2 is new Gen_Subp_2(T); --
end P;
Note that we considered imposing a similar implicit null exclusion for
controlling access results, but chose not to do that, because there is
no Ada95 compatibility issue, and there is no automatic null check
inherent in the use of a controlling access result. If a null check is
necessary, it is because there is a dereference of the result. If there
is no dereference of the result, a null return value is perfectly
acceptable, and can be a useful indication of a particular status of the
call.
!example
(See discussion.)
!corrigendum 3.9.2(11)
Insert after the paragraph:
The default_expression for a controlling formal parameter of a dispatching
operation shall be tag indeterminate. A controlling formal parameter that is an
access parameter shall not have a default_expression.
the new paragraph:
If a dispatching operation is defined by a subprogram_renaming_declaration
or the instantiation of a generic subprogram, any access parameter of the
renamed subprogram or the generic subprogram that corresponds to a
controlling access parameter of the dispatching operation, shall have
a subtype that excludes null.
!corrigendum 3.10(6)
Replace the paragraph:
access_definition ::= access subtype_mark
by:
null_exclusion ::= not null
access_definition ::=
[null_exclusion] access [constant] subtype_mark
| [null_exclusion] access [protected] procedure parameter_profile
| [null_exclusion] access [protected] function parameter_and_result_profile
!ACATS test
Create B-Tests to check that the renames and instance rule is checked.
Create a B-Test to insure that "all" is not allowed in anonymous access types.
!appendix
From: Tucker Taft
Sent: Friday, February 18, 2005 6:18 AM
The other "morning after" issue that I remember has
to do with access results. Erhard made the point that
by associating non-nullness with controlling access results,
we will make it that much harder to use them as a replacement
for named access types. Although I argued against his
point in the meeting, afterward I realized there really
isn't much justification for requiring that controlling access
results be non-null.
There are several ways in which controlling access results
are different from controlling access parameters:
1) We don't have any "legacy"/"compatibility" to worry
about here. Access results didn't exist in Ada 95,
so we don't have to inherit their rules. We can
easily say that with controlling access results, you
get what you ask for, either null-excluding or
null-allowing, whichever is more appropriate.
2) With controlling access parameters, there is a necessary
dereference on every dispatching call to find out which
body to execute, or in the case of multiple parameters,
to make sure they all agree on which body. With controlling
access results, there is no such requirement, except when
the controlling access result is passed to a null-excluding
parameter or assigned to a null-excluding target, and then
the check is really associated with the target, not the
result.
3) Given "function Empty_Acc return access Set:" and
"X : access Set'Class;" I had leapt to the conclusion
that "X := Empty_Acc;" was dispatching on result.
But that's wrong. We are doing an access value
assignment, and there is certainly no requirement that
X be non-null to begin with. A dispatching on result
would only occur with: "X.all := Empty_Acc.all;" in
which case we have the ".all" on Empty_Acc which clearly
inserts the appropriate null check, so again, there is
no need to associate it with Empty_Acc itself.
4) Unlike with normal results, with access results, "null"
is a well-defined value that could be returned from
a primitive function. For example, consider the
C++ streaming approach, where the streaming "get"
returns the input stream:
function Get(S : access Stream; Val : access Integer)
return access Stream;
function Get(S : access Stream; Val : access Float)
return access Stream;
We could also have other operations that produce streams
or add characters into a stream for later "Get"s,
which would look more like constructors:
function Open(Name : String) return access Stream;
function Init_Contents(Contents : String)
return access Stream;
function Unget_String(S : access Stream; Put_Back : String)
return access Stream; -- "Put_Back" inserted
-- back into stream for Get
-- to re-read
A "null" value could be used as an indication of an empty stream.
One could then write:
Str : access Stream := Open("My_File");
A, B : aliased Integer;
C : aliased Float;
begin
while Str /= null loop
Str := Str.Get(A'Access).Get(B'Access).Get(C'Access);
-- Do something with A, B, C
end loop;
Of course some people don't like using "null" as an indicator,
but it is pretty common (certainly when iterating over a
linked list, for example). But in any case, it seems
a conceivable thing to do, and there seems no reason
why we need to disallow it or make it more difficult
than necessary.
----------------------
So for all the above reasons, and perhaps more, I would
suggest we drop the connection between non-nullness and
controlling access results, and adopt a "what you see
is what you get" model for them. I believe it might have
been an overactive attempt to unify, otherwise known as "confuse,"
controlling access parameters and controlling access
results, which led us to believe they were necessarily
non-null. In other words, I think Erhard had the right
instinct here...
*************************************************************
From: Pascal Leroy
Sent: Tuesday, February 22, 2005 4:11 AM
I agree with your arguments: there doesn't seem to be any good reason to
force access results to be null-excluding.
*************************************************************
!topic Bad example in AI-404
!reference AI95-00404
!from Adam Beneschan 05-04-25
!discussion
The example in AI-404 to demonstrate the new rule about dispatching
operations created by renaming and generic instantiations has a
problem.
generic
type GT is private;
procedure Gen_Subp_1(Y : access GT);
generic
type GT is private;
procedure Gen_Subp_2(Y : not null access GT);
with Gen_Subp_1; with Gen_Subp_2;
package P is
type T is tagged ....
package Inner is
procedure Not_Disp_1(X : access T);
procedure Not_Disp_2(X : not null access T);
end Inner;
procedure Ren1(X : access T) renames Not_Disp_1; -- illegal
procedure Ren2(X : access T) renames Not_Disp_2; -- legal
procedure Inst1 is new Gen_Subp_1(T); -- illegal
procedure Inst2 is new Gen_Subp_2(T); -- legal
end P;
Besides the obvious problem that Not_Disp_1 and Not_Disp_2 are not
visible at the point of the renaming declarations, the more subtle
problem is that the generic instantiations cannot be legal, because
the occurrence of the instantiation causes T to be frozen (13.14(11)),
and thus no new dispatching operations on T could be defined by the
instantiation, with or without the new rule. (Or have I read the
rules wrong?)
This is really a minor nitpick, since this example hasn't found its
way into the Ada 2006 RM.
*************************************************************
Questions? Ask the ACAA Technical Agent