Version 1.8 of ais/ai-10318.txt
!standard 03.10.02(13) 04-10-05 AI95-00318-02/06
!standard 03.09(14)
!standard 06.01(13)
!standard 06.03.01(16)
!standard 06.05(02)
!standard 06.05(03)
!standard 06.05(04)
!standard 06.05(05)
!standard 06.05(06)
!standard 06.05(07)
!standard 06.05(08)
!standard 06.05(09)
!standard 06.05(10)
!standard 06.05(11)
!standard 06.05(12)
!standard 06.05(13)
!standard 06.05(14)
!standard 06.05(15)
!standard 06.05(16)
!standard 06.05(17)
!standard 06.05(18)
!standard 06.05(19)
!standard 06.05(20)
!standard 06.05(21)
!standard 06.05(22)
!standard 06.05(24)
!standard 08.01(4)
!class amendment 02-10-09
!status Amendment 200Y 04-09-23
!status ARG Approved 5-0-4 04-09-23
!status work item 04-07-28
!status ARG Approved 9-0-2 04-06-17
!status work item 03-05-23
!status received 02-10-09
!priority Medium
!difficulty Medium
!subject Limited and anonymous access return types
!summary
A new extended syntax is proposed for the return statement,
providing a name for the new object being created as a result
of a call on the function.
This new syntax can be used to support returning limited objects from a
function and more generally to reduce the copying that might be required
when a function returns a complex object, a controlled object, etc.
The existing ability to return by reference is replaced by an
ability to have an anonymous access type as a return type.
!problem
We already have a proposal (AI-287) for allowing aggregates of a limited type,
by requiring that the aggregate be built directly in the target object rather
than being copied into the target.
But aggregates can only be used with non-private types. Limited
private types could not be initializable at their declaration point.
It would be natural to allow functions to return limited objects,
so long as the object could be built directly in the "target"
of the function call, which could be a newly created object being
initialized, or simply a parameter to another subprogram call.
When returning a limited type it may be desirable to perform some other
initialization to the object after it has been created, but before returning
from the function. This is difficult to do while still creating the object
directly in its "final" location.
Currently functions that return a limited private type may have an
accessibility check performed on the object returned, depending on a
property ("return-by-reference-ness") which is not generally visible
based on the partial view of the type. This means that a function
that works initially may stop working if the full type of the result
type is changed to include, say, a limited tagged component, or some
other component that is return-by-reference.
A function whose result type turns out to be return-by-reference
cannot be allowed where a new object is required. However, there
is nothing in the declaration of such a function that indicates it
returns by reference.
The capability to return-by-reference could be useful for non-limited types,
but it becomes even more useful if a call on such a function could be treated
as a variable, so it could be used on the left-hand side of an assignment.
These capabilities exist in the language without introducing the
conceptual oddity of return-by-reference. A function returning
an access value allows the effect of return-by-reference, and doesn't
require changing the language, at the cost of a bit of extra verbosity in
some cases (the need to dereference the result).
!proposal
Anonymous access types are permitted for a function result type:
parameter_and_result_profile ::=
[formal_part] return subtype_mark
| [formal_part] return access_definition
An anonymous access type used as the result type of a function is called an
access result type. The accessibility level of an access result type is that
of the declaration containing the parameter_and_result_profile.
-------------
An extended syntax for the return statement is proposed:
RETURN identifier : [ALIASED] return_subtype_indication [:= expression] [DO
handled_sequence_of_statements
end return];
Such an extended return statement is permitted only immediately within a
function. The specified identifier names the object that is the result of
a call on the function. If the expression is present, it provides
the initial value for the result object. If not, the result object
is default initialized. If the handled_sequence_of_statements is
present, it is executed after initializing the result object. Within
the handled_sequence_of_statements, the identifier denotes a variable
view of the result object with nominal subtype given by the subtype_indication.
When the handled_sequence_of_statements completes, the function is complete.
Note: An expression-less return statement is permitted
within the handled_sequence_of_statements, similar to the way
that accept statements work.
A call of a function with a limited result type may
be used in the same contexts where we have proposed to allow aggregates of a
limited type, namely contexts where a new object is being created (or can be).
1) Initializing a newly declared object (including a result object identified
in an extended return statement)
2) Default initialization of a record component
3) Initialized allocator
4) Component of an aggregate
5) IN formal object in a generic instantiation (including as a default)
6) Expression of a return statement
7) IN parameter in a function call (including as a default expression)
In addition, since the result of a function call is a name in Ada 95,
the following contexts would be permitted, with the same semantics
as creating a new temporary constant object, and then creating a
reference to it:
8) Declaring an object that is the renaming of a function call.
9) Use of the function call as a prefix to 'Address
In other words, it would be permitted in any context where limited types
are permitted. With the new proposals, that is pretty much any context
where a "name" that denotes an object or value is permitted, except as the
right hand side of an assignment statement.
This proposal assumes that AI-287 is adopted; it does not repeat the changes
needed to allow function_calls in the contexts listed above.
!wording
Add before 3.9(14):
If a record_type_declaration includes the reserved word limited, it is called
a limited record.
Add after 3.10.2(13):
* The accessibility level of the anonymous access type of an access result
type (see 6.5) is the same as that of the associated function or
access-to-subprogram type.
Change 6.1(13) to:
parameter_and_result_profile ::=
[formal_part] return subtype_mark
| [formal_part] return access_definition
Modify 6.3.1(16) as follows:
Two profiles are mode conformant if they are type-conformant, corresponding
parameters have identical modes, and, for access parameters {or access result
types}, the designated subtypes statically match.
Replace clause 6.5 with the following:
6.5 Return Statements
A return_statement is used to complete the execution of the innermost
enclosing subprogram_body, entry_body, or accept_statement.
Syntax
return_statement ::= simple_return_statement | extended_return_statement
simple_return_statement ::= return [expression];
extended_return_statement ::=
return identifier : [aliased] return_subtype_indication [:= expression] [do
handled_sequence_of_statements
end return];
return_subtype_indication ::= subtype_indication | access_definition
Name Resolution Rules
The result subtype of a function is the subtype denoted by the
subtype_mark, or defined by the access_definition, after the reserved word
RETURN in the profile of the function. The expression, if any, of a
return_statement is called the return expression. The expected type for a
return expression is the result type of the corresponding function.
Legality Rules
A return_statement shall be within a callable construct, and it applies to
the innermost callable construct or extended_return_statement that contains
it. A return_statement shall not be within a body that is within the
construct to which the return_statement applies.
A function body shall contain at least one return_statement that applies
to the function body, unless the function contains code_statements. A
simple_return_statement shall include a return expression if and only if
it applies to a function body. An extended_return_statement shall apply to
a function body.
If the result subtype of a function is defined by a subtype_mark, the
return_subtype_indication of an extended_return_statement that applies to
the function body shall be a subtype_indication. The type of the
subtype_indication shall be the result type of the function. If the result
subtype of the function is constrained, then the subtype defined by the
subtype_indication shall also be constrained and shall statically match
this result subtype. If the result subtype of the function is
unconstrained, then the subtype defined by the subtype_indication shall be
a definite subtype, or there shall be a return expression.
If the result subtype of the function is defined by an access_definition,
the return_subtype_indication shall be an access_definition. The subtype
defined by the access_definition shall statically match the result subtype
of the function. The accessibility level of this anonymous access subtype
is that of the result subtype.
If the type of the return expression is limited, then the return expression
shall be an aggregate, a function call (or equivalent use of an operator),
or a qualified_expression or parenthesized expression whose operand is one
of these.
AARM Note:
In other words, if limited, the return expression must produce a
"new" object, rather than being the name of a preexisting object
(which would imply copying).
Static Semantics
Within an extended_return_statement, the return object is declared with
the given identifier, with nominal subtype defined by the
return_subtype_indication.
Dynamic Semantics
For the execution of an extended_return_statement, the subtype_indication
is elaborated. This creates the nominal subtype of the return object. If
there is a return expression, it is evaluated and converted to the nominal
subtype (which might raise Constraint_Error -- see 4.6) and becomes the
initial value of the return object; otherwise, the return object is
initialized by default as for a stand-alone object of its nominal subtype
(see 3.3.1). If the nominal subtype is indefinite, the return object is
constrained by its initial value. The handled sequence of statements, if
any, is then executed.
For the execution of a simple_return_statement, the expression (if any) is
first evaluated and converted to the result subtype to become the value of
the anonymous return object.
If the result type of a function is a specific tagged type, the
tag of the return object is that of the result type.
AARM Ramification:
This is true even if the tag of the return expression is different, which
could happen if the return expression were a view conversion or a
dereference of an access value. Note that for a limited type, because
of the restriction to aggregates and function calls (and no conversions),
the tag will already match.
AARM Reason:
This rule ensures that a function whose result type is a specific tagged
type always returns an object whose tag is that of the result type. This
is important for dispatching on controlling result, and allows the caller
to allocate the appropriate amount of space to hold the value being
returned (assuming there are no discriminants).
Finally, a transfer of control is performed which completes the
execution of the construct to which the return_statement applies,
and returns to the caller. In the case of a function, the
function_call denotes a constant view of the return object.
Examples
Examples of return statements:
return; --
--
return Key_Value(Last_Index); --
return Node : Cell do --
Node.Value := Result;
Node.Succ := Next_Mode;
end return;
The new rule added before 7.5(2) by AI-287 should say:
...unless it is an aggregate, a function_call, or a parenthesized...
A new bullet should be added to the list here:
* the expression of a return_statement (see 6.5)
The following should be added to the new rule added after 7.5(8) by AI-287:
For a function_call of a type with a part that is of a task, protected, or
limited record type that is used to initialize an object as allowed above, the
implementation shall not create a separate return object (see 6.5) for the
function_call. The function_call shall be constructed directly in the
new object.
Similarly, the replacement note of 7.5(9)
of AI-287 should say "aggregate or function_call" in each occurrence.
Add after 8.1(4):
* an extended_return_statement;
!example
Here is an example of a function with a limited result type
using an extended return statement:
function Make_Obj(Param : Natural) return Lim_Type is
begin
return Result : Lim_Type do --
--
Further_Processing(Result, Param);
end return;
end Make_Obj;
Here is a similar function that returns an access-to-limited type:
function Make_Obj(Param : Natural) return access Lim_Type is
begin
return Result : access Lim_Type do --
Result := new Lim_Type; --
--
Further_Processing(Result.all, Param);
end return;
end Make_Obj;
Here is an abstraction which uses functions with access result types, to
support an extensible array abstraction (aka vector):
generic
type Element is private;
type Index is (<>);
package Extensible_Arrays is
pragma Assert(Index'First > Index'Base'First);
--
type Ext_Array is private;
--
procedure Set_Elem(EA : in out Ext_Array; I : Index; Elem : Element);
--
--
function Last(EA : Ext_Array) return Index'Base;
--
function Elem(EA : Ext_Array; I : Index) return access Element;
--
--
--
procedure Set_Empty(EA : in out Ext_Array);
--
--
private
type Elem_Array is array(Index range <>) of aliased Element;
--
type Elem_Array_Ptr is access Elem_Array;
--
type Ext_Array is record
Last : Index'Base := Index'First - 1;
Data : Elem_Array_Ptr;
--
--
end record;
end Extensible_Arrays;
procedure Ext_Array_Test(Max : Positive) is
package Ext_Int_Arrays is
new Extensible_Arrays(Element => Integer; Index => Positive);
type Ext_Int_Array is new Ext_Int_Arrays.Ext_Array;
X : Ext_Int_Array; --
begin
--
for I in 1..Max loop
Set_Elem(X, I, Elem => I*2);
end loop;
--
for I in 1..Max/2 loop
Elem(X, I).all := Elem(X, I).all + 1;
end loop;
--
for I in 1..Last(X) loop
Ada.Text_IO.Put_Line(Integer'Image(I) & " => " &
Integer'Image(Elem(X, I).all));
end loop;
Set_Empty(X); --
end Ext_Array_Test;
!discussion
In meetings with Ada users, there has been a general sense
that if limited aggregates are provided in Ada 200Y, it would be desirable
to also provide limited function returns which could act
as "constructor" functions.
Just allowing a function whose whole body is a return statement
returning an aggregate (or another function call) does not give the
programmer much flexibility. What they would like is to be able
to create the object being returned and then initialize it further somehow,
perhaps by calling a procedure, doing a loop (as in the examples above),
etc. This requires a named object. However, to avoid copying,
we need this object to be created in its final "resting place,"
i.e. in the target of the function call. This might be in the
"middle" of some enclosing composite object the caller is initializing,
or it might be in the heap, or it might be a stand-alone local
object.
Because the implementation needs to create the result object in a place
determined by the caller, it is important that the declaration of the
object be distinguished in some way. By declaring it as part of an
extended return statement, we have a way for the programmer to indicate
that this is the object to be returned. Clearly we don't want to allow
extended return statements to be nested.
Because it may be necessary to do some computing before deciding
exactly how the result object should be declared, we permit
the extended return statement to occur any place a normal return
statement is permitted. So different branches of an if or case statement
could have their own extended return statements, each with its own named
result object.
Note that we have allowed the user to declare the result object
as "aliased." This seems like a natural thing which might be
wanted, so you could initialize a circularly-linked list header
to point at itself, etc.
Note that we had discussed various mechanisms where information
from the calling context would be available inside the function
at the language level. In particular, it would be possible to refer
to the values of the discriminants or bounds of the object being
initialized, presuming it was constrained, within the subtype
indication and initializing expression, if any.
Ultimately this capability was not included in this proposal, as it
created a series of somewhat complicated restrictions on usage and made the
implementation that much more difficult. Note that the implementation
may still need to pass in information from the calling context, depending
on the run-time model, because if the type is "really" limited (e.g.
it is limited tagged, or contains a task or a protected object), then
the new object must be built in its final resting place. In many run-time
models, that means the storage needs to be allocated at the call-site if the
object being initialized is a component of some larger object.
However, by not allowing the programmer to refer to this contextual
information at the langauge level, we give the implementation more
flexibility in how it solves the build-in-place requirement for
"really" limited objects. See the discussion below about implementation
approaches.
The syntax for extended return statements was initially proposed early on,
but when this AI was first written up, we proposed instead a revised
object declaration syntax where the word "return" was used almost like the word
"constant," as a qualifier. This was somewhat more economical in terms of
syntax and indenting, but was not felt to be as clear semantically as this
current syntax.
We have eliminated the capability for returning by reference, in favor
of returning a value of an anonymous access type. An alternative proposal
(AI-318-1) proposed to make return-by-reference a separate capability,
triggered by the presence of the reserved word "ALIASED" in the function
profile. This was felt by some reviewers to be enshrining the confusing notion
of return-by-reference, which earlier had been buried in a discussion
of certain limited types. Furthermore, the implementation model of
return by reference was clearly to return a "reference" (effectively an access
value) to the result object. Making this explicit presumably makes
the feature easier to understand, and we can also piggy back on the
usual accessibility checks, rather than have to invent special ones
associated with a return by reference.
The capability to return an anonymous access type goes well with
the other changes allowing anonymous access types in more contexts.
We have kept the implementation simple by making the accessibility
level of the result type the same as that of the associated function
(or access-to-subprogram type).
POSSIBLE IMPLEMENTATION APPROACHES
The implementation of the extended return statement for non-limited
types should minimize the number of copies, but may still require a copy
in some implementation models and in some calling contexts.
The implementation of the extended return statement for limited result
types is straightforward if the result subtype is constrained. It is
essentially equivalent to a procedure with an OUT parameter -- the
caller allocates space for the target object, perhaps does some of the
"implicit" initialization for tags, discriminants, tasks, or protected
components, etc., and passes its address to the called routine, which
uses it for the "return" object. Nonlimited controlled components can
still require some fancy footwork, since they can be explicitly
initialized, so default initializing them would be inappropriate. But
compilers already have to deal with returning non-limited controlled
objects, so presumably this won't create an insurmountable burden.
If the result subtype is unconstrained, then there are two basic possibilities:
1) The target object's (nominal) subtype is definite, and either constrained
or the size of the object is independent of the constraints (e.g.
allocate-the-max is used for the object); the target object might be a
component of a larger object.
2) The target object's nominal subtype is unconstrained, and its size
is to be determined by the result returned from the function;
the target object must be a stand-alone object, or an "entire"
heap object.
In the first case, the caller determines the size of the target object and
can allocate space for it; in the second, the caller cannot
preallocate space for the target object, and must rely on the called
routine allocating space for it in an "appropriate" place.
The code for the called routine must handle both of these cases.
One reasonable way to do so is for the caller to provide a
"storage pool" for the result. In the first case, this storage
"pool" has space for exactly one object of a given maximum size.
It's Allocate routine is trivial. It just checks to see if the
size is no greater than the space available, and then returns the
preallocated (target) address.
In the second case, the storage pool is either the storage pool
associated with the initialized allocator at the call site,
or a storage pool that represents a secondary stack, or equivalent,
used for returning objects of unknown size from a function.
In either case, the function would return the address of the new
object.
A "bare" storage pool may not be enough in general. If the type
has any task parts, then these tasks must be placed on an activation
list determined by the calling context. They may also be linked onto a
master record of some sort, unless this is deferred until
activation occurs. Note that the tasks cannot be activated
until after returning from the call, since they may have
to be activated in conjunction with other tasks having the
same master.
If the type has any controlled or protected parts, then the object
as a whole, or the individual parts, may need to be added to
a cleanup list determined by the calling context.
If the type has any access discriminants, then some kind of
accessibility level will need to be provided, since the access
discriminant may only be initialized to point to an object
whose accessibility level is no deeper than that of the
storage pool where the new object is being allocated.
What this means is that rather than passing just a reference
to a storage pool, it is more likely the caller will pass
a reference to a structure which in turn refers to:
- a storage pool,
- an accessibility level,
- an activation list,
- the associated master,
- a cleanup list
Supporting a function result of an anonymous access type presents no
special challenges since we have defined the accessibility level of
the result type to be the same as that as the associated function
or access-to-subprogram declaration. Hence, it is as though a named
access type were declared and then used as the result type, from
a run-time model point of view. There is no need for any (new) run-time
accessibility checking.
DEALING WITH EXCEPTIONS
There was some concern about what would happen if an exception were
propagated by an extended return statement, and then the same or some
other extended return statement were reentered. There doesn't seem to be
a real problem. The return object doesn't really exist outside the
function until the function returns, so it can be restored to its
initial state on call of the function if an exception is propagated from
an extended return statement. Once restored to its initial state, there
seems no harm in starting over in another extended_return_statement.
THE BUILD-IN-PLACE IMPLEMENTATION REQUIREMENT
The intent of this feature is that there never is copying of a "really" limited
object. We have added an Implementation Requirement to insure that that is
really the case.
It has been argued that this requirement is not needed because any such copies
are semantically neutral. But no copies of a self-referencing object could
ever really be semantically neutral. Moreover, the definition of object
creation in 3.3(19) says that the subcomponents are assigned from the
expression already evaluated. This clearly must be superceded.
In addition, we want to tell the reader (Ada user and implementers alike) that
function calls have changed. In Ada up to this point, function calls were
always about copying (at least logically, 7.6(21) allows omitting the copy).
That is emphatically not the case in the Amendment; indeed in a similar case
for aggregates, we included such a requirement in the Corrigendum.
!corrigendum 3.9(14)
Insert before the paragraph:
The component_definition of a component_declaration defines the
(nominal) subtype of the component. If the reserved word aliased appears
in the component_definition, then the component is aliased (see 3.10).
the new paragraph:
If a record_type_declaration includes the reserved word limited, it
is called a limited record.
!corrigendum 3.10.2(13)
Insert after the paragraph:
- The accessibility level of the anonymous access type of an access
parameter is the same as that of the view designated by the actual. If the
actual is an allocator, this is the accessibility level of the execution
of the called subprogram.
the new paragraph:
- The accessibility level of the anonymous access type of an access
result type (see 6.5) is the same as that of the associated function or
access-to-subprogram type.
!corrigendum 6.1(13)
Replace the paragraph:
parameter_and_result_profile ::= [formal_part] return subtype_mark
by:
parameter_and_result_profile ::=
[formal_part] return subtype_mark
| [formal_part] return access_definition
!corrigendum 6.3.1(16)
Replace the paragraph:
Two profiles are mode conformant if they are type-conformant, and
corresponding parameters have identical modes, and, for access parameters, the
designated subtypes statically match.
by:
Two profiles are mode conformant if they are type-conformant,
corresponding parameters have identical modes, and, for access parameters or
access result types, the designated subtypes statically match.
!corrigendum 6.5(2)
Replace the paragraph:
return_statement ::= return [expression];
by:
return_statement ::= simple_return_statement | extended_return_statement
simple_return_statement ::= return [expression];
extended_return_statement ::=
return identifier : [aliased] return_subtype_indication [:= expression] [do
handled_sequence_of_statements
end return];
return_subtype_indication ::= subtype_indication | access_definition
!corrigendum 6.5(03)
Replace the paragraph:
The expression, if any, of a return_statement is called the return
expression. The result subtype of a function is the subtype denoted by the
subtype_mark after the reserved word return in the profile of the
function. The expected type for a return expression is the result type of the
corresponding function.
by:
The result subtype of a function is the subtype denoted by the
subtype_mark, or defined by the access_definition, after the reserved
word return in the profile of the function. The expression, if any, of
a return_statement is called the return expression. The expected type
for a return expression is the result type of the corresponding function.
!corrigendum 6.5(04)
Replace the paragraph:
A return_statement shall be within a callable construct, and it applies
to the innermost one. A return_statement shall not be within a body that
is within the construct to which the return_statement applies.
by:
A return_statement shall be within a callable construct, and it applies to
the innermost callable construct or extended_return_statement that
contains it. A return_statement shall not be within a body that is within
the construct to which the return_statement applies.
!corrigendum 6.5(05)
Replace the paragraph:
A function body shall contain at least one return_statement that applies
to the function body, unless the function contains code_statements. A
return_statement shall include a return expression if and only if
it applies to a function body.
by:
A function body shall contain at least one return_statement that applies
to the function body, unless the function contains code_statements. A
simple_return_statement shall include a return expression if and only if
it applies to a function body. An extended_return_statement shall apply to
a function body.
If the result subtype of a function is defined by a subtype_mark, the
return_subtype_indication of an extended_return_statement that
applies to the function body shall be a subtype_indication. The type of
the subtype_indication shall be the result type of the function. If the
result subtype of the function is constrained, then the subtype defined by the
subtype_indication shall also be constrained and shall statically match
this result subtype. If the result subtype of the function is unconstrained,
then the subtype defined by the subtype_indication shall be a definite
subtype, or there shall be a return expression.
If the result subtype of the function is defined by an access_definition,
the return_subtype_indication shall be an access_definition. The
subtype defined by the access_definition shall statically match the result
subtype of the function. The accessibility level of this anonymous access
subtype is that of the result subtype.
If the type of the return expression is limited, then the return expression
shall be an aggregate, a function call (or equivalent use of an operator),
or a qualified_expression or parenthesized expression whose operand is one
of these.
Static Semantics
Within an extended_return_statement, the return object is declared
with the given identifier, with nominal subtype defined by the
return_subtype_indication.
!corrigendum 6.5(06)
Replace the paragraph:
For the execution of a return_statement, the expression (if any) is
first evaluated and converted to the result subtype.
by:
For the execution of an extended_return_statement, the
subtype_indication is elaborated. This creates the nominal subtype of the
return object. If there is an expression, it is evaluated and converted
to the nominal subtype (which might raise Constraint_Error -- see 4.6) and
becomes the initial value of the return object; otherwise, the return object is
initialized by default as for a stand-alone object of its nominal subtype
(see 3.3.1). If the nominal subtype is indefinite, the return object is
constrained by its initial value. The handled sequence of statements, if
any, is then executed.
For the execution of a simple_return_statement, the expression (if any) is
first evaluated and converted to the result subtype to become the value of
the anonymous return object.
!corrigendum 6.5(07)
Delete the paragraph:
If the result type is class-wide, then the tag of the result is the tag of the
value of the expression.
!corrigendum 6.5(08)
Replace the paragraph:
If the result type is a specific tagged type:
by:
If the result type of a function is a specific tagged type, the
tag of the return object is that of the result type.
!corrigendum 6.5(09)
Delete the paragraph:
- If it is limited, then a check is made that the tag of the value of
the return expression identifies the result type. Constraint_Error is raised
if this check fails.
!corrigendum 6.5(10)
Delete the paragraph:
- If it is nonlimited, then the tag of the result is that of the result
type.
!corrigendum 6.5(11)
Delete the paragraph:
A type is a return-by-reference type if it is a descendant of one of the
following:
!corrigendum 6.5(12)
Delete the paragraph:
!corrigendum 6.5(13)
Delete the paragraph:
- a task or protected type;
!corrigendum 6.5(14)
Delete the paragraph:
- a nonprivate type with the reserved word limited in its
declaration;
!corrigendum 6.5(15)
Delete the paragraph:
- a composite type with a subcomponent of a return-by-reference type;
!corrigendum 6.5(16)
Delete the paragraph:
- a private type whose full type is a return-by-reference type.
!corrigendum 6.5(17)
Delete the paragraph:
If the result type is a return-by-reference type, then a check is made that the
return expression is one of the following:
!corrigendum 6.5(18)
Delete the paragraph:
- a name that denotes an object view whose accessibility level is
not deeper than that of the master that elaborated the function body; or
!corrigendum 6.5(19)
Delete the paragraph:
- a parenthesized expression or qualified_expression whose operand
is one of these kinds of expressions.
!corrigendum 6.5(20)
Delete the paragraph:
The exception Program_Error is raised if this check fails.
!corrigendum 6.5(21)
Delete the paragraph:
For a function with a return-by-reference result type the result is returned by
reference; that is, the function call denotes a constant view of the object
associated with the value of the return expression. For any other function, the
result is returned by copy; that is, the converted value is assigned into an
anonymous constant created at the point of the return_statement, and the
function call denotes that object.
!corrigendum 6.5(22)
Replace the paragraph:
Finally, a transfer of control is performed which completes the
execution of the construct to which the return_statement applies,
and returns to the caller.
by:
Finally, a transfer of control is performed which completes the
execution of the construct to which the return_statement applies,
and returns to the caller. In the case of a function, the
function_call denotes a constant view of the return object.
!corrigendum 6.5(24)
Replace the paragraph:
return; -- in a procedure body, entry_body, or accept_statement
return Key_Value(Last_Index); -- in a function body
by:
return; -- in a procedure body, entry_body,
-- accept_statement, or extended_return_statement
return Key_Value(Last_Index); -- in a function body
return Node : Cell do -- in a function body, see 3.10.1 for Cell
Node.Value := Result;
Node.Succ := Next_Mode;
end return;
!corrigendum 7.5(2)
!comment This rule only talks about function_calls, because those are only
!comment appropriate here. The conflict text handles the combination of
!comment function_calls and aggregates.
@dinsb
If a tagged record type has any limited components, then the reserved word
@b<limited> shall appear in its @fa<record_type_definition>.
@dinst
In the following contexts, an @fa<expression> of a limited
type is not permitted unless it is a @fa<function_call>
or a parenthesized @fa<expression> or @fa<qualified_expression> whose operand
is permitted by this rule:
@xbullet<the initialization @fa<expression> of an @fa<object_declaration> (see 3.3.1)>
@xbullet<the @fa<default_expression> of a component_declaration (see 3.8)>
@xbullet<the @fa<expression> of a @fa<record_component_association> (see 4.3.1)>
@xbullet<the @fa<expression> for an @fa<ancestor_part> of an @fa<extension_aggregate> (see 4.3.2)>
@xbullet<an @fa<expression> of a @fa<positional_array_aggregate> or the
@fa<expression> of an @fa<array_component_association> (see 4.3.3)>
@xbullet<the @fa<qualified_expression> of an initialized @fa<allocator> (see 4.8)>
@xbullet<the @fa<expression> of a @fa<return_statement> (see 6.5)>
@xbullet<the @fa<default_expression> or actual parameter for a formal object
of mode @b<in> (see 12.4)>
!corrigendum 7.5(8)
Insert after the paragraph:
There are no predefined equality operators for a limited type.
the new paragraph:
Implementation Requirements
For a function_call of a type with a part that is of a task, protected, or
limited record type that is used to initialize an object as allowed above, the
implementation shall not create a separate return object (see 6.5) for the
function_call. The function_call shall be constructed directly in the
new object.
!corrigendum 7.5(9)
Replace the paragraph:
13 The following are consequences of the rules for limited types:
by:
13 While it is allowed to write initializations of limited objects,
such initializations never copy a limited object. The source of such an
assignment operation must be a function_call, and such function_calls
must be built directly in the target object.
!corrigendum 8.1(4)
Insert after the paragraph:
the new paragraph:
- an extended_return_statement;
!ACATS test
ACATS(s) tests need to be created for these features.
!appendix
From: Tucker Taft
Sent: Thursday, April 1, 2004, 6:13 AM
I have been asked to prepare an alternative to AI-318
which drops the notion of "aliased" return-by-reference
functions, and replaces it with a simplified version
of anonymous access type return. One thing that is
being lost in this process is that return-by-reference
eliminates the need for ".all" at the call site.
However, it struck me that we already allow implicit
dereference in a number of contexts, and since
anonymous access types as return types is a new
feature, it would be feasible to allow implicit
dereference of calls of such functions in *any* context.
Allowing implicit dereference has some advantages:
1) It provides better compatibility with the existing
(albeit limited) return-by-reference capability,
because call sites would not have to change, only
the function would change to return X'access rather
than X (or Y rather than Y.all). Implicit dereference
would eliminate the need for a .all at the call sites.
2) C++ has a return-by-reference capability ("&" return type)
which allows a natural way to use a call on a function as
the left hand side of an assignment, allowing the implementation
of "abstract" arrays, e.g.:
Arr(X) := Arr(X) + 1;
where "Arr" is actually a function that implements an array-like
data structure.
We could get much of this same capability by allowing
functions declared to return an anonymous access type to
be implicitly dereferenced in any context. Furthermore,
since Ada uses "()" for both array indexing and function
calling, this would actually get some value out of that
syntactic unification (or as Robert might call it,
"confusion" ;-).
This is actually better than the "aliased" return-by-ref
capability, since in that case the returned object was
necessarily considered a constant. Of course if the
writer of the function wanted the result to be access-to-constant,
they could declare it that way.
3) Similar to above, but relevant to me because Bob Duff and I
have recently been sparring over an issue that would be nicely
solved by implicit dereference: As in many text- and language-
processing tools, we convert all strings into unique IDs as soon
as we read the source file. We call these unique IDs "spellings,"
LISP used to call them "symbols," and I have seen them called
String-IDs and a number of other similar things. They
significantly simplify further processing because string equality
involves a simple ID equality comparison, and these IDs can
be efficiently passed and returned from subprograms without
any of the issues associated with passing and returning
unconstrained arrays.
*However*, when it comes to passing these IDs to
subprograms that expect Strings, we have to convert
the ID back to a String. The simplest way to do this is to
write a function, say To_String, which takes an ID and returns
a String. Unfortunately, that immediately gets you back into
the inefficiencies of returning unconstrained arrays. An
alternative is to expose the representation of the IDs, and
allow the caller to explicitly use ".all" or a component selection
to retrieve the String at the call site, but that clearly makes
the "abstraction" a bit less abstract.
By allowing implicit dereference of functions returning
anonymous access types, we could have the best of both worlds.
The To_String function could actually return "access constant
String" instead of String, but it could still be used in
any context that required a String without the overhead of
returning unconstrained arrays. This would preserve both
abstraction and performance.
So, barring major objection, I am going to propose that calls
on functions returning anonymous access types will permit
implicit dereference in any context (instead of only in front
of ".", "(", and "'").
Comments welcomed...
****************************************************************
From: Pascal Leroy
Sent: Monday, April 5, 2004, 10:47 AM
Tuck wrote:
> Here is an alternative proposal, which drops
> "aliased return blah" (return-by-reference) in
> favor of "return access blah." It still includes
> functions returning limited types.
It took me a while to realize that this AI really has two proposals:
1 - Functions returning anonymous access types. That includes implicit
dereferencing, but as I see it the extended_return_statement is not
necessary for this part.
2 - Improvements for functions returning limited types. This is the
part that really needs the extended_return_statement.
The more I look at the AI, the more I like #1 (especially with implicit
dereferencing and the capability to have a function call on the LHS of
an assignment) and the less convinced I am about #2. Yeah, it would be
nice to improve the usability of limited types, but the baggage needed
to do that (and the somewhat arbitrary restrictions that come with it)
sounds clunky to me.
What do others think?
****************************************************************
From: Tucker Taft
Sent: Monday, April 5, 2004, 11:13 AM
Pascal Leroy wrote:
>
> Tuck wrote:
>
> > Here is an alternative proposal, which drops
> > "aliased return blah" (return-by-reference) in
> > favor of "return access blah." It still includes
> > functions returning limited types.
>
> It took me a while to realize that this AI really has two proposals:
>
> 1 - Functions returning anonymous access types. That includes implicit
> dereferencing, but as I see it the extended_return_statement is not
> necessary for this part.
>
> 2 - Improvements for functions returning limited types. This is the
> part that really needs the extended_return_statement.
I believe I was directed to keep these two proposals as part of a single AI.
> The more I look at the AI, the more I like #1 (especially with implicit
> dereferencing and the capability to have a function call on the LHS of
> an assignment) and the less convinced I am about #2. Yeah, it would be
> nice to improve the usability of limited types, but the baggage needed
> to do that (and the somewhat arbitrary restrictions that come with it)
> sounds clunky to me.
>
> What do others think?
I think this is the key thing to make limited types more useful.
With this change, making a type limited allows the implementor to
control all cases of copying, without dramatically undermining
the usability of the type, and with almost no negative performance
impact.
****************************************************************
From: Randy Brukardt
Sent: Tuesday, April 6, 2004, 3:53 PM
The only reason that I would ever vote for (1) would be if it was the only
way to get (2). If we don't want to handle the limited functions, then we
need do nothing for return-by-reference.
Visible access parameters and results in modern programs should be
discouraged; used only when there is absolutely no other choice. (If we'd
have "in out" on functions, there would never be a need for them.)
As far as the implicit dereference goes, I've been waiting for the expected
"April Fool" that goes with it. Since I've been waiting a week, I suppose it
is actually a serious proposal. I find it completely bizarre, because it
ruins the model of implicit dereference (it occurs only before '.' or '()').
Moreover, why
type Int_Access is access all Integer;
function anon return access all Integer;
function IA return Int_Access;
I := Anon; -- Legal.
I := IA; -- Illegal.
should behave differently is going to be just too goofy to explain.
OTOH, (2) will not only eliminate arbitrary restrictions from limited types,
but it also will make code more readable anytime that it takes multiple
steps to create a result. (And should allow the generation of better code as
well by building in place more often.)
****************************************************************
From: Tucker Taft
Sent: Thursday, April 15, 2004, 10:30 AM
In my recent AI-318-2 proposal for functions returning
an anonymous access type, I had specified that
implicit dereference was provided for calls on
such functions, in part to minimize the impact
of eliminating return-by-reference functions.
However, since then I noticed an additional use
of implicit deref of such calls. I mentioned this
in a response to ada-comment, but here I repeat
it for those who don't follow that mailing list.
There are often times where one wants to provide
a read-only view of a "private" global variable.
There are also times that you have a large global
table, but you want to put its initialization in
a package body so you don't suffer recompilation
headaches every time you change the table slightly
(e.g. a large parse table).
Ada doesn't really have any good solution for this.
In C/C++, you can declare a large constant (or
variable) without giving its initialization in a
spec (i.e. the ".h" file), and then in the body
(i.e. the ".c" file) give the full initialization.
With this new implicit deref proposal, it would
make it pretty easy and efficient to solve this
problem:
In the spec:
function Read_Only_View return access constant T;
pragma Inline(Read_Only_View);
In the body:
Var : aliased T := (...);
function Read_Only_View return access constant T is
begin
return Var'Access;
end Read_Only_View;
Similarly, if there were a large constant table that you just
wanted to postpone to the body:
In the spec:
function Parse_Table return access constant Parse_Table_Type;
pragma Inline(Parse_Table);
In the body:
Parse_Table_Obj : aliased constant Parse_Table_Type := (...);
function Parse_Table return access constant Parse_Table_Type is
begin
return Parse_Table_Obj'Access;
end Parse_Table;
Since the existing uses of anonymous access types are
quite limited right now (only parameters and discriminants),
we could consider providing implicit dereference for *all*
expressions of an anonymous access type, since that would
be more uniform. But I also think it is not too bad to
only provide this for calls, since there is already
implicit "deproceduring" (as Algol 68 called it) for parameterless
subprograms in Ada. Providing implicit deref for functions
returning an anonymous access type seems like a natural
progression of that.
If we instead choose to extend implicit deref to all anonymous
access values, then there is more of an upward compatibility
concern, since there could be additional ambiguities created
when an access parameter or discriminant is passed to an
overloaded subprogram with one version having a param
of type "T" and another a param of type "access T".
Given the relatively modest use of access parameters
and access discriminants at this point, this seems relatively
unlikely, and in any case it will not silently change meaning --
you'll get a compile-time error.
I could go either way. I think implicit deref of function
calls at least is pretty important. It is a very nice way
to return a reference to a large object without incurring
significant overhead, and without having to change the syntax
used at the call point (i.e., no need to insert ".all").
****************************************************************
From: Robert Dewar
Sent: Thursday, April 15, 2004, 10:37 AM
> Ada doesn't really have any good solution for this.
I am missing something, it seems quite reasonable to return
an appropriate constant access type. Yes you add some nice
syntactic sugar below, but nothing fundamental.
What am I missing?
****************************************************************
From: Tucker Taft
Sent: Thursday, April 15, 2004, 11:32 AM
We have generalized the availability of
anonymous access types, in part to go with
the "limited with" proposal, since "limited with"
doesn't solve the proliferation of access types
problem. The one significant place where
anonymous access types weren't permitted was
as function result types. AI-318-2 was addressing that
(as well as limited result types). Franco was
extremely keen on getting this, because he felt
that it was a clear hole when trying to explain
the new anonymous access type paradigm.
The implicit deref of calls of such functions may
seem like a small point, but it can have a significant
effect on usability in my experience. Having to add
".all" on the result of a function call is a pain
and changes the perceived nature of the abstraction.
What you really want is return by reference in some
contexts, and having to add an explicit ".all" makes
the abstraction feel less abstract. I gave examples
of an "array" abstraction with the ability to
assign to components of the array.
****************************************************************
From: Robert I. Eachus
Sent: Thursday, April 15, 2004, 4:22 PM
I don't think you are missing anything. But that doesn't mean that what
Tucker is trying to accomplish is not very useful. Right now you can
convert a function to an array in some cases and not have to change the
code that uses the abstraction. This does the same thing for anonymous
access types. (Actually, Tucker goes further, but I think that the
anonymous access cases are the high payoff.) If we can eliminate
gratuitous uses of .all and 'Access, it makes programming in Ada
easier. There is a potential problem in that overloadings will be
possible where supplying the .all (or 'Access) will resolve the
overloading but the direct call will always be ambiguous.
If you really find that a problem, is should be easy enough to say that
if a function call or argument is in parentheses, the .all or 'Access
must be explicit. So if:
X := Foo(Y, Bar); -- is ambiguous
then;
X := Foo(Y, (Bar));
and
X := Foo(Y, Bar.all);
Would resolve to the two different meanings. It might require some
changes to existing code, but not that much, and it would of course, be
caught at compile time.
****************************************************************
From: Randy Brukardt
Sent: Thursday, July 29, 2004, 1:06 AM
AI-10318 includes the following rule:
Legality Rules
If the result subtype of a function is limited at the point where the
function is frozen (see 13.14), the result subtype shall be constrained.
This was intended to make the implementation of limited build-in-place
functions easier.
At the Palma meeting, Pascal pointed out that this is incompatible with
existing generic units that have generic limited private type parameters. He
asked me to add the example of ACATS foundation FDD2A00, which this rule
makes illegal -- and it doesn't raise Program_Error in use. (This example is
not quite as compelling as it could be, as the foundation in question was
created by "PHL". :-)
However, I noticed that this foundation is testing stream attributes. The
problem is caused by 'Input being a function. Indeed, that shows that the
above legality rule has additional compatibility problems and as well needs
help to cover stream attributes.
Let's look at an example.
package Ugh is
type Lim_Tagged is limited tagged private;
function My_Input (Stream : access Root_Stream_Type'Class)
return Lim_Tagged;
-- Legal only if the type doesn't have any discriminants.
for Lim_Tagged'Input use My_Input;
function My_Class_Input (Stream : access Root_Stream_Type'Class)
return Lim_Tagged'Class;
-- Never legal (T'Class is never constrained).
for Lim_Tagged'Class'Input use My_Class_Input;
procedure Do_Something (Object : Lim_Tagged'Class);
task type Tsk is ...
function My_Tsk_Input (Stream : access Root_Stream_Type'Class)
return Tsk;
-- Legal.
for Tsk'Input use My_Tsk_Input;
type Tsk_Array is array (Positive range <>) of Tsk;
-- Tsk_Array'Input is available by the Corrigendum and AI-195.
procedure Do_Something_Else (Object : Tsk_Array);
private
...
end Ugh;
with Ugh;
package Factory is
function Constructor (...) return Ugh.Lim_Tagged'Class;
-- A "factory" constructor.
-- Never legal (T'Class is never constrained)
end Factory;
with Ugh;
procedure Test is
Obj : Ugh.Lim_Tagged'Class := Ugh.Lim_Tagged'Class'Input (A_Stream);
-- This is clearly legal if we don't try to redefine the
-- attribute. But it is returning a clearly unconstrained (new)
-- object, and it will require build-in-place semantics.
TObj : Ugh.Tsk_Array := Ugh.Tsk_Array'Input (A_Stream);
-- This also is clearly legal.
begin
Ugh.Do_Something (Ugh.Lim_Tagged'Class'Input (A_Stream));
-- Legal now in Ada 95+Corr.
Ugh.Do_Something_Else (Ugh.Tsk_Array'Input (A_Stream));
-- Legal now in Ada 95+Corr if Tsk_Array'Input overridden.
end Test;
The first problem to note is incompatibility. With this legality rule, we
aren't allowed to declare a function to be used as a user defined 'Input for
Tsk_Array and Lim_Tagged'Class. Ada 95 certainly allows that. Tucker has
argued privately that it would be nearly impossible to write a useful
user-defined routine in this case, because of the return-by-reference
rules -- returning an existing object is never what you want.
The second problem is the language defines stream attributes for all types -
including unconstrained limited types. Moreover, we've made (limited)
function calls legal in many circumstances with build-in-place semantics.
So, these stream attributes are legal in calls like the above (and the
Do_Something calls are perfectly useful in existing Ada 95+Corr code). That
means that we still have to implement unconstrained function calls, just the
user can't write them. That's obviously silly.
We're also giving much less than meets the eye here. It's not allowed to
write a class-wide constructor (of which T'Class'Input is just a single
example). That's a significant loss. The workaround of using an anonymous
(or named) access type puts the burden of storage management on the user -
reducing a key advantage of Ada. And it means that it won't be possible to
convert non-limited tagged types which were non-limited only to get
functions and constructors to limited tagged types.
---
Anyway, the second problem has to be solved somehow. It's of no help to
implementers to limit users from writing unconstrained functions if the
system still has to do it. There are three basic solutions.
The more ugly rules solution: We could make T'Input "unavailable" if T is
limited and unconstrained. This is messy, though, because we wouldn't want
to prevent the use of T'Input for (untagged) types that aren't really
limited (and thus allow the declaration of functions); that would be a
bigger compatibility problem. Unfortunately, figuring out whether types are
"really" limited breaks privateness, and Mr. Private is sure to object to a
legality rule depending on privateness.
And, of course, this rule would completely undo all of the work which makes
streaming for limited tagged types useful, leaving such types as
second-class citizens.
An alternative is the
New ugly rule solution: The real problem is that we need to prevent making
*calls* to functions that we can't handle, not the functions themselves. So
we could drop the above rule altogether (possibly leaving an implementation
permission for an implementation to reject a function that cannot be
called), and replace it by rule on calls:
If the result subtype of a function_call is limited at the point of the
call, the result subtype shall be constrained.
This solves the problem cleanly, and also reduces the problems with generics
(as now only internal calls would be made illegal; calls from outside the
instance would determine from the actual type if they are legal.
But again this covers too much. And trying to make it tighter again will
break privateness and also be a contract problem in generics.
The final alternative is the
Too much work solution: drop these silly restrictions intended to make the
implementation easier (and which by definition harm the user) and get to
work. Tucker explained how to implement this long ago, and although it looks
painful, it's only a short-term pain -- rather than inflicting this pain on
users forever. It also is much less incompatible, as there are no problems
with existing generics with limited private formal types.
Still, this didn't fly in the past, and I don't expect it to fly now.
---
To me, this looks like a giant tease. We tease programmers by claiming that
you can now do almost everything with limited types that you can do with
non-limited types, but in return some of your generics are now going to be
illegal (with no meaningful workaround), you can't use class-wide functions
(meaning no factories of any kind), and even class-wide streaming isn't
allowed. This, to steal one of Tucker's favorite phrases, is just moving the
bump under the rug. This is a self-inflicted bump; if we were willing to do
the additional work to move the furniture, this bump would be gone. (The
analogy comes true in the carpet here in my office; since we didn't move all
of my furniture in the last flood, the carpet repair guy left a large bump
next to my desk...)
There is also a safety issue here that we haven't really discussed. Objects
returned by return-by-reference functions have a limited lifetime: it's not
possible for the caller to hang onto them forever. That makes it possible to
do storage management and resource locking (although the language doesn't
support this well). However, anonymous access types can be converted to
other types, and via 'Unchecked_Access, held onto forever. (Sure, the
programmer is responsible that the 'Unchecked_Access value is destroyed
before the object is. But if the programmer *knows* [by peeking at the
source] that the actual objects are at library-level or in a heap, the use
is OK vis-a-vis the language -- even though it may not be part of the
"contract" of the function. And 'Unchecked_Access is so common that it isn't
going to be a red flag to anyone - I don't know if I've ever found a useful
case where I could use 'Access on an object -- I don't even try anymore.) So
we've reduced the safety of the language a bit.
Anyway, to draw an actual conclusion. :-)
I don't believe that an AI that purports to give limited types equal footing
with non-limited ones can deny a major benefit of OOP programming to limited
types. Restrictions on class-wide programming are simply unacceptable in my
view -- if limited tagged types are going to remain useless, we might as
well not bother with lots of work and incompatibility.
So I would vote for accepting the short-term pain and making this useful to
all. However, if that is unacceptable to the majority, then (in the absence
of a better idea) I would rather drop the AI completely rather than give
users another (but different) crippled version of limited types.
****************************************************************
Questions? Ask the ACAA Technical Agent