Version 1.3 of ais/ai-00287.txt

Unformatted version of ais/ai-00287.txt version 1.3
Other versions for file ais/ai-00287.txt

!standard 7.5 (00)          02-02-06 AI95-00287/00
!class amendment 02-02-06
!status received 02-02-06
!priority Medium
!difficulty Medium
!subject Limited types considered limited
!summary
!problem
!proposal
!wording
!discussion
!example
!ACATS test
!appendix

From: Robert Duff
Sent: Wednesday, February 6, 2002,  6:05 PM

                    Limited Types Considered Limited

One of my homework assignments was to propose changes to "fix" limited
types.  This e-mail outlines the problems, and proposed solutions.
I haven't written down every detail -- I first want to find out if there
is any interest in going forward with these changes.  Some of these
ideas came from Tucker.

I apologize for doing my homework at the last minute.  I hope folks will
have a chance to read this before the meeting, and I hope Pascal is
willing to put it on the agenda.

Ada's limited types allow programmers to express the idea that "copying
values of this type does not make sense".  This is a very useful
capability; after all, the whole point of a compile-time type system is
to allow programmers to formally express which operations do and do not
make sense for each type.

Unfortunately, Ada places certain limitations on limited types that have
nothing to do with the prevention of copying.  The primary example is
aggregates: the programmer is forced to choose between the benefits of
aggregates (full coverage checking) and the benefits of limited types.
Forcing programmers to choose between two features that ought to be
orthogonal is one of the most frustrating aspects of Ada.

I consider the full coverage rules (for aggregates and case statements)
to be one of the primary benefits of Ada over many other languages,
especially with type extensions, where some components are inherited
from elsewhere.  I will refrain from further singing the praises of full
coverage; I assume I'm preaching to the choir.

My goals are:

    - Allow aggregates of limited types.

    - Allow constructor functions that return limited types.

    - Allow initialization of limited objects.

    - Allow limited constants.

    - Allow subtype_marks in aggregates more generally.
      (They are currently allowed only for the parent part in an
      extension aggregate.)

The basic idea is that there is nothing wrong with constructing a
limited object; *copying* is the evil thing.  One should be allowed to
create a new object (whether it be a standalone object, or a formal
parameter in a call, or whatever), and initialize that object with a
function call or an aggregate.  In implementation terms, the result
object of the function call, or the aggregate, is built in place in its
final destination -- no copying is necessary, or allowed.

All of the above goals except constructor functions are fairly trivial
to achieve, both in terms of language design and in terms of
implementation effort.  Constructor functions are somewhat more
involved.  However, I am against any language design that allows
aggregates where function calls are not allowed; subprogram calls are
perhaps the single most important tool of abstraction ever invented!
(There is at least one other such case in Ada, and I hate it.)

By "constructor function", I mean a function that returns an object
created local to the function, as opposed to an object that already
existed before the function was called.

Ada currently allows functions to return limited types in two cases,
neither of which achieves the goal here:

    If the limited type "becomes nonlimited" (for example, a limited
    private type whose full type is integer), then constructor functions
    are allowed, but the return involves a copy, thus defeating the
    purpose of limited types.  Anyway, this feature is not allowed for
    various types, such as tagged types.

    If the limited type does not become nonlimited, then it is returned
    by reference, and the returned object must exist prior to the
    function call; it cannot be created by the function.  In essense,
    these functions don't return limited objects at all; they simply
    return a pointer to a preexisting limited object (or perhaps
    a heap object).

We need a new kind of function that constructs a new limited object
inside of itself, and returns that object to be used in the
initialization of some outer object.  The run-time model is that the
caller allocates the object, and passes in a pointer to that object.
The function builds its result in that place; thus, no copying is done
on return.

Because the run-time model for calls to these constructor functions is
different from that of existing functions that return a limited type, we
need to indicate this syntactically on the spec of the function.  In
particular, change the syntax of functions so that "return" can be
replaced by "out", indicating a constructor function.  In addition,
change the syntax of object declarations to allow "out", as in
"X: out T;"; this marks the object as "the result object" of a limited
constructor function.  The reason for "out" is that these things behave
much like parameters of mode 'out'.

Examples:

    type T is tagged limited
        record
            X: ...;
            Y: ...;
            Z: ...;
        end record;

    type Ptr is access T'Class;

    Object_1: constant T := (X => ..., Y => ..., Z => ...);

    function F(X: ...; Y: ...) out T;

    function F(X: ...; Y: ...) out T is
        Result: out T := (X => X, Y => Y, Z => ...);
    begin
        ... -- possible modifications of Result.
        return Result;
    end F;

    Object_2: Ptr := new T'(F(X => ..., Y => ...));
        -- Build a limited object in the heap.

Rules:

Change the rules in 4.3 to allow limited aggregates.  This basically
means erasing the word "nonlimited" in a few places.

Change the rule in 3.3.1(5) about initializing objects to allow limited
types.  But require the expression to be an aggregate or a constructor
function.  ("X: T := Y;", where Y is a limited object, remains illegal,
because that would necessarily involve a copy.)  There are various
analogous rules (initialized allocators, subexpressions of aggregates,
&c) that need analogous changes.

Assignment statements remain illegal for limited types, even if the
right-hand side is an aggregate or limited constructor function.

Allowing constants falls out from the other rules.

Allow a component expression in an aggregate to be a subtype_mark.  This
means that the component is created as a default-initialized object.
It's essentially the same thing we already allow in an extension
aggregate; we're simply generalizing it to all components of all
aggregates.  This is important, in case some part of the type is
private.  There is no reason to limit this capability to limited types.

Specify that limited aggregates are built "in place"; there is always a
newly-created object provided by the context.  Note that we already have
one case where aggregates are built in place: (nonlimited) controlled
aggregates.  Similarly, the result of a limited constructor function is
built in place; the context of the call provides a newly-created object.
(In the case of "X: T := F(...);", where F says "return G(...);", F will
receive the address of X, and simply pass it on to G.)

If the result object of a limited constructor function contains tasks,
the master is the caller.

For a function whose result is declared "out T", T must be a limited
type; such a function is defined to be a "limited constructor function".

Subtype T must be definite.  This rule is not semantically necessary.
However, the run-time model calls for the caller to allocate the result
object, and this rule allows the caller to know its size before the
call.  Without this rule, a different run-time model would be required
for indefinite subtypes: the called function would have to allocate the
result in the heap, and return a pointer to it.  A design principle of
Ada is to avoid language rules that require implicit heap allocation;
hence this rule.  (An alternative rule would be that T must be
constrained if composite, thus eliminating defaulted discriminants.)

A limited constructor function must have exactly one return statement.
The expression must be one of the following:

    - An object local to the function (possibly nested in block
      statements), declared with "out".

    - A function call to a limited constructor function.

    - An aggregate.

    - A parenthesized or qualified expression of one of these.

An object declared "out" must be local to a limited constructor
function.

A constraint check is needed on creation of a local "out" object.
We have to do the check early (as opposed to the usual check on the
return statement), because we need to make sure the object fits in the
place where it belongs (at the call site).  If the return expression is
an aggregate, that needs a constraint check, as usual.  If the return
expression is a function call, then that function will do whatever
checking is necessary.

Is there an issue with dispatching-on-result functions?
I don't think so.


Compatibility:

This change is not upward compatible.  Consider:

    type Lim is limited
        record
            Comp: Integer;
        end record;

    type Not_Lim is
        record
            Comp: Integer;
        end record;

    procedure P(X: Lim);
    procedure P(X: Not_Lim);

    P((Comp => 123));

The call to P is currently legal, and calls P(Not_Lim).  In the new
language, this call will be ambiguous.

This seems like a tolerable incompatibility.  It is always caught at
compile time, and cases where nonlimitedness is used to resolve
overloading have got to be vanishingly rare.  The above program, though
legal, is highly confusing, and I can't imagine anybody wanting to do
that.  The current rule was a mistake in the first place: even if
limited aggregates *should* be illegal, that should not be a Name
Resolution Rule.


Other advantages:

One advantage of this change is that it makes the usage style of limited
types more uniform with nonlimited types, thus making the language more
accessible to beginners.

How do you construct an object in Ada?  You call a function.  Cool -- no
need for the kludginess of C++ constructors.  But if it's limited, you
have to fool about with discriminants -- not something that would
naturally occur to a beginner.  And discriminants have various annoying
restrictions when used for this purpose.

How do you capture the result of a function call?  You put it in a
constant: "X: constant T := F(...);".  But if it's limited, you have to
*rename* it: "X: T renames F(...);".  Again, that's not something that
would naturally occur to a beginner -- and the beginner would rightly
look upon it as a "trick" or a "workaround".

Another point is that the current rules force you into the heap,
unnecessarily.  You end up passing around pointers to limited objects,
either explicitly or implicitly, which tends to add complexity to one's
programs.

Limited types offer other advantages in addition to lack of copying:
access discriminants, and the ability to take 'Access of the "current
instance".  It seems a shame to require the programmer to choose between
these and aggregates.


Alternatives:

It is not strictly necessary to mark the result object with "out"; the
compiler could deduce this information by looking at the return
statement(s).  However, marking the object simplifies the compiler -- it
needs to treat this object specially by using the space allocated by the
caller.

It is not necessary to limit the number of return statements to 1.
However, it seems simplest.  We need to prevent things like this:

    function F(...) out T is
        Result_1: out T;
        Result_2: out T;
    begin
        Result_1.X := 123;
        Result_2.X := 456;
        if ... then
            return Result_1;
        else
            return Result_2;
        end if;
    end F;

because we can't allocate Result_1 and Result_2 in the same place!
On the other hand, the following could work:

    function F(...) out T is
        Result: out T;
    begin
        if ... then
            return Result;
        else
            return Result;
        end if;
    end F;

suggesting a rule that all return statements must refer to the *same*
object.  But this could work, too:

    function F(...) out T is
    begin
        if ... then
            return (...); -- aggregate
        elsif ... then
            return G(...); -- function call
        elsif ... then
            declare
                Result_1: out T;
            begin
                Result_1.X := 123;
                return Result_1; -- local
            end;
        else
            declare
                Result_2: out T;
            begin
                Result_2.X := 456;
                return Result_2; -- different local
            end;
        end if;
    end F;

because only one of the four different result objects exists at any
given time.  I'm not sure how much to relax this rule.  Perhaps some
rule about declaring only one of these special result objects in a given
region?

****************************************************************

From: Tucker Taft
Sent: Thursday, February 7, 2002,  6:15 AM

I would allow these "constructor" functions for any kind of
type.  I would require that at most one OUT local variable
be declared.  It must be in the outermost declarative part
(to avoid it going out of scope before it was returned), and
that all return statements must identify the OUT local variable
if present (or perhaps, all return statements must omit the
return expression completely if present).

Allowing the declaration of an "OUT" local variable might
be generalized to normal functions (with the same restrictions
as above).  For a "normal" function, the OUT local variable
would represent the returned object, but with the difference that
the space for it is allocated by the called routine, rather than
the caller.  This allows the Pascal "style" of assigning to
a return object, when it is appropriate.

****************************************************************

From: Robert Duff
Sent: Thursday, February 7, 2002,  7:52 AM

> I would allow these "constructor" functions for any kind of
> type.

You mean for nonlimited as well as limited?  Sounds OK.

I assume you do not mean to allow them for unknown-size subtypes.

>...  I would require that at most one OUT local variable
> be declared.  It must be in the outermost declarative part
> (to avoid it going out of scope before it was returned),

I don't see why that's necessary.  The OUT variable doesn't *really* go
away -- it's really just a view of the object created by the caller.
No action is required to return it -- it's already in the right spot,
so a return of it is simply a "goto end of function".

>... and
> that all return statements must identify the OUT local variable
> if present (or perhaps, all return statements must omit the
> return expression completely if present).

A less restrictive rule is that all return statements that refer to a
variable must refer to the OUT variable.  Does that not work?

> Allowing the declaration of an "OUT" local variable might
> be generalized to normal functions (with the same restrictions
> as above).

OK.

>...  For a "normal" function, the OUT local variable
> would represent the returned object, but with the difference that
> the space for it is allocated by the called routine, rather than
> the caller.  This allows the Pascal "style" of assigning to
> a return object, when it is appropriate.

One question is: what if the function doesn't execute a return
statement?  That's currently erroneous.  But if there's an OUT variable,
it would seem sensible to just let the function fall off the end, and
have the OUT variable define the result.

Which implies that we should eliminate the rule requiring at least one
return statement, in the case where there's an OUT variable.

I like it!

I presume the OUT object can actually be a constant or variable, by the
way.

****************************************************************

From: Tucker Taft
Sent: Thursday, February 7, 2002,  8:28 AM

> > I would allow these "constructor" functions for any kind of
> > type.
>
> You mean for nonlimited as well as limited?  Sounds OK.

Yes, that's what I meant.  And since some limited types
become non-limited, I think this may be necessary.

> I assume you do not mean to allow them for unknown-size subtypes.

Right.  They should have exactly the same restrictions,
independent of whether the type is limited or nonlimited,
and the caller should always allocate space for the
returned object.  That way for limited types that
become non-limited, we don't run into any weirdness.

This also means that one can switch between limited and non-limited
with minimal semantic disruption, and you don't have to remember
a lot of special cases for limited vs. non-limited (I presume
that is one of the key goals of this proposal).

> >...  I would require that at most one OUT local variable
> > be declared.  It must be in the outermost declarative part
> > (to avoid it going out of scope before it was returned),
>
> I don't see why that's necessary.  The OUT variable doesn't *really* go
> away -- it's really just a view of the object created by the caller.
> No action is required to return it -- it's already in the right spot,
> so a return of it is simply a "goto end of function".

I don't see how that could work.  Consider the following:

    type Lim_T(B : Boolean := False) is record
        case B is
            when True => X : Task_Type;
            when False => null;
        end case;
    end record;


    function Cons(...) out Lim_T is
    begin
        declare
             Result : out Lim_T(True);
        begin
             null;
        end;
        return Lim_T'(B => False);
    end Cons;

Are you going to require a "return" on all paths out of the declare block?
What if an exception is propagated from the declare block, and then in
the handler there is a return of something with a different discriminant?

> >... and
> > that all return statements must identify the OUT local variable
> > if present (or perhaps, all return statements must omit the
> > return expression completely if present).
>
> A less restrictive rule is that all return statements that refer to a
> variable must refer to the OUT variable.  Does that not work?

No; see above.  I think once you create the OUT local, that
must be the one that gets returned.  Hence, it is probably
simplest if there is an OUT local declared, to eliminate
the use of return expressions, or even the requirement for
an explicit "return" statement, so it would work like
a procedure with one OUT parameter.

> > Allowing the declaration of an "OUT" local variable might
> > be generalized to normal functions (with the same restrictions
> > as above).
>
> OK.
>
> >...  For a "normal" function, the OUT local variable
> > would represent the returned object, but with the difference that
> > the space for it is allocated by the called routine, rather than
> > the caller.  This allows the Pascal "style" of assigning to
> > a return object, when it is appropriate.
>
> One question is: what if the function doesn't execute a return
> statement?  That's currently erroneous.  But if there's an OUT variable,
> it would seem sensible to just let the function fall off the end, and
> have the OUT variable define the result.
>
> Which implies that we should eliminate the rule requiring at least one
> return statement, in the case where there's an OUT variable.
>
> I like it!

In my view, this would be desirable only if we eliminate all return
expressions from return statements for constructor functions with a
local OUT object, making them obey the same rules as procedures
thereafter.

> I presume the OUT object can actually be a constant or variable, by the
> way.

I would not allow it to be a constant.  The word "OUT" would take
the place of the word "CONSTANT" in the syntax, the way I see it.
It seems nearly useless to have it be a constant, and clearly,
the caller could use it to initialize a variable, so calling it a constant
could be misleading.

****************************************************************

From: Robert Duff
Sent: Thursday, February 7, 2002,  8:57 AM

> I don't see how that could work.

Neither do I.  ;-)

> No; see above.  I think once you create the OUT local, that
> must be the one that gets returned.  Hence, it is probably
> simplest if there is an OUT local declared, to eliminate
> the use of return expressions, or even the requirement for
> an explicit "return" statement, so it would work like
> a procedure with one OUT parameter.

Agreed.

> In my view, this would be desirable only if we eliminate all return
> expressions from return statements for constructor functions with a
> local OUT object, making them obey the same rules as procedures
> thereafter.

Yes, that makes sense.

> I would not allow it to be a constant.  The word "OUT" would take
> the place of the word "CONSTANT" in the syntax, the way I see it.
> It seems nearly useless to have it be a constant, and clearly,
> the caller could use it to initialize a variable, so calling it a constant
> could be misleading.

Yes, of course.  I wasn't thinking clearly.

****************************************************************

From: Robert Dewar
Sent: Thursday, February 7, 2002,  9:02 AM

I must say that for me, this entire proposal seems to be insufficiently
grounded in real requirements. I am concerned that the ARG is starting to
wander around in the realm of nice-to-have-neat-language-extensions which
are really rather irrelevant to the future success of Ada. I am not opposed
to a few extensions in areas where a really important marketplace need has
been demonstrated, but the burden for new extensions should be extremely
high in my view, and this extension seems to fall far short of meeting
that burden.

****************************************************************

From: Randy Brukardt
Sent: Thursday, February 7, 2002,  2:19 PM

I hate to be agreeing with Robert here :-), but he's right.

There is a problem worth solving here (the inability to have constants of
limited types), but that could adequately be solved simply by the 'in-place'
construction of aggregates (which we already require in similar contexts).
[I'll post a real-world example of the problem in my next message.] The problem
is relatively limited, and thus the solution also has to be limited, or it
isn't worth it. This whole business of constructor functions only will sink any
attempt to fix the real problem, because it is just too big of a change at this
point.

Bob's concerns about the purity of the language would make sense in a new
language design, but we're working with limited resources here, and simple
solutions are preferred over perfect ones.

****************************************************************

From: Randy Brukardt
Sent: Thursday, February 7, 2002,  3:05 PM

Here is an example that came up in Claw where we really wanted constants of a
limited type:

The Windows registry contains a bunch of predefined "keys", along with user
defined keys. Our original design for the key type was something like (these
types were all private, and the constants were deferred, but I've left that out
for clarity):

    type Key_Type is new Ada.Finalization.Limited_Controlled with record
      Handle : Claw.Win32.HKey := Claw.Win32.Null_HKey;
      Predefined_Key : Boolean := False; -- Is this a predefined key?
                                         -- (Only valid if Handle is not null)
      -- other components.
    end record;

    Classes_Root : constant Key_Type := (Ada.Finalization.Limited_Controlled with
       Handle => 16#80000000#, -- Windows magic number
       Predefined_Key => True, ...);
    Current_User : constant Key_Type := (Ada.Finalization.Limited_Controlled with
       Handle => 16#80000001#, -- Windows magic number
       Predefined_Key => True, ...);
    -- And several more like this.

    procedure Open (Key    : in out Key_Type;
                    Parent : in Key_Type;
                    Name   : in String);

    procedure Close (Key   : in out Key_Type);

    procedure Put (Root_Key   : in Key_Type;
                   Subkey     : in String;
                   Value_Name : in String;
                   Item       : in String);

    -- and so on..

Of course, our favorite compiler rejected the constants as illegal.

So, they were turned into functions.

   function Classes_Root return Key_Type;
   function Current_User return Key_Type;

However, these have the problem that they have to be overridden for any
extensions of the type (as they are primitive). We could have put them into a
child/nested package (to make them not primitive), but that would bend the
structure of the design even further and add an extra package for no good
reason. We also could have made them class-wide, but that would be a misleading
specification, as they can never return anything other than Key_Type. So we
left them in the main package.

Aside: we originally wanted to use these as default parameters for some of the
various primitive routines. However, that would illegal by 3.9.2(11) unless
they are primitive functions. This rule exists so that the default makes sense
in inherited primitives. But we really would have preferred that the default
expressions weren't inherited; they only make sense on the base routines. That
is a problem that probably isn't worth solving though.

Of course, now that we had functions, we had to implement them. The first try
was:

   function Classes_Root return Key_Type is
   begin
       return (Ada.Finalization.Limited_Controlled with
           Handle => 16#80000000#, -- Windows magic number
           Predefined_Key => True, ...);
   end Classes_Root;

But our friendly compiler told us that THIS was illegal, because this is
return-by-reference type, and the aggregate doesn't have the required
accessibility.

So we had to add a library package-level constant and return that:

   Standard_Classes_Root : constant Key_Type := (Ada.Finalization.Limited_Controlled with
       Handle => 16#80000000#, -- Windows magic number
       Predefined_Key => True, ...);

   function Classes_Root return Key_Type is
   begin
       return Standard_Classes_Root;
   end Classes_Root;

But of course THAT is illegal (its the original problem all over again), so we
had to turn that into a variable and initialize it component-by-component in
the package body's elaboration code:

   Standard_Classes_Root : Key_Type;

   function Classes_Root return Key_Type is
   begin
       return Standard_Classes_Root;
   end Classes_Root;

begin
   Standard_Classes_Root.Handle := 16#80000000#; -- Windows magic number
   Standard_Classes_Root.Predefined_Key => True;
   ...

Which is essentially how it is today.

This turned into such a mess that we gave up deriving from it altogether, and
created an entirely new higher-level abstraction to provide the most commonly
used operations in an easy to use form. Thus, we ended up losing out on the
benefits of O-O programming here.

I certainly hope that newcomers to Ada don't run into a problem like this,
because it is a classic "stupid language" problem.

Simply having a way to initialize a limited constant with an aggregate would be
sufficient to fix this problem. "Constructor functions" might add
orthogonality, but seem unnecessary to solve the problem of being able to have
constants as part of an abstraction's specification.

****************************************************************

From: Robert Duff
Sent: Friday, February 8, 2002, 10:12 AM

> Simply having a way to initialize a limited constant with an aggregate would
> be sufficient to fix this problem. "Constructor functions" might add
> orthogonality, but seem unnecessary to solve the problem of being able to
> have constants as part of an abstraction's specification.

Surely you don't mean that we would allow limited aggregates only for
initializing stand-alone constants?!  Surely, you could use them to
initialize variables.  And if they can be used to initialize variables,
surely initialized allocators should be allowed.  And of course,
parameters.

In *my* programs, much of the data is heap-allocated.  I want to say:

    X: Some_Ptr := new T'(...);

when T is limited.  Allowing only constants would solve about 1% of *my*
problem.

Are you saying that this is illegal:

    P(T'(...));

and I have to instead write:

    Temp: constant T := (...);

    P(Temp);

?!  That sort of arbitrary restriction is what makes people laugh at the
language.

****************************************************************

From: Dan Eilers
Sent: Friday, February 8, 2002, 12:12 PM

Bob Duff wrote:
> My goals are:
>
>     - Allow aggregates of limited types.
>
>     - Allow constructor functions that return limited types.
>
>     - Allow initialization of limited objects.
>
>     - Allow limited constants.
>
>     - Allow subtype_marks in aggregates more generally.
>       (They are currently allowed only for the parent part in an
>       extension aggregate.)

Tuck wrote:
> I would allow these "constructor" functions for any kind of
> type.

I agree that the non-limited case is also important, and should be
listed as an explicit goal of the AI.  The non-limited case is an
efficiency issue, where a programmer wishes to prevent unnecessary
copying of large objects implied by the semantics of aggregates and
function calls.


Tuck wrote:
> ... so it would work like a procedure with one OUT parameter.

The proposal seems to go to a lot of trouble to define a new
kind of function that behaves exactly like a procedure with
one OUT parameter.  I think there may be a simpler solution
involving an extension to function renaming.  The constructor
function would be declared as a procedure with one OUT parameter,
and then renamed (allowing it to be called) as a function, using
the type of the OUT parameter as its return type.

Example:

        procedure p(x: some_type; y: out return_type);

        function f(x: some_type) return return_type renames p;


> > I assume you do not mean to allow them for unknown-size subtypes.
>
> Right.  They should have exactly the same restrictions,
> independent of whether the type is limited or nonlimited,
> and the caller should always allocate space for the
> returned object.  That way for limited types that
> become non-limited, we don't run into any weirdness.

There are many cases, such as the string concat function, where
the return type is declared to be unconstrained (unknown-size),
but really its size is a function of the sizes of the parameters
and therefore computable before the call.  It might facilitate
allocating space in the caller if Ada had a way of expressing the
size of the return type in terms of the parameters, possibly
using the proposed new assertion mechanism (AI-00286).

****************************************************************

From: Tucker Taft
Sent: Friday, February 8, 2002,  5:39 PM

This is an intriguing idea.  Clearly less syntax invention
than the "out" function return idea.  We actually perform the
transformation implied (from a procedure with an OUT parameter to
a function) as an optimization, when the OUT parameter is of an
elementary type.  Also there is a DEC-Ada pragma which imports an
external function as an Ada procedure, because the external function
had OUT parameters in addition to its return value.  I believe GNAT
supports this pragma.

Unfortunately, I'm not sure the above would work for the case when
you want to use an aggregate as the return expression.  Also, a procedure
presumes its OUT parameter is already at least default-initialized.
With the constructor function, the initialization was deferred until
entering the function, where it could be initialization from an aggregate
or from some other function call.

Another approach is to use the proposal for anonymous access
types as return types, which for (access-to) limited types has many of
the same advantages as the constructor function concept (see AI 231 for
details).

****************************************************************

From: Tucker Taft
Sent: Friday, February 8, 2002,  5:48 PM

Given the relative complexity of the constructor function
concept compared to the other de-limiting ideas, I would propose
we split the AI.  The simpler one would allow:

   1) Aggregates of limited objects, with use of a subtype_mark to mean
      default init of a component;
   2) Explicit initialization of limited objects, both in a declaration and
      an allocator, from an aggregate, with the aggregate built "in place" in
      the target.

The more complex one would address functions constructing limited objects
on behalf of the caller.

The aggregate one seems very straightforward.  Almost just eliminate
the existing restriction, presuming that compilers have already learned
how to build aggregates "in place" for controlled types.

The function one looks like a lot of work.

****************************************************************


Questions? Ask the ACAA Technical Agent