Version 1.1 of ais/ai-20359.txt
!standard 12.03 (12) 04-07-29 AI95-00359-03/01
!standard 12.03 (20)
!standard 13.14 (05)
!class amendment 04-07-29
!status work item 04-07-29
!status received 04-07-29
!subject Delayed completion of a generic instantation
To better support generic signature packages, and mutually dependent types that
involve generic instantiations, syntax is defined to allow a generic
instantiation can be split into two parts.
For such an instantiation, no freezing or elaboration occurs at the
point of the instantiation, rather it is deferred until a completion point.
Between the instantiation and the completion, only a limited view of the
instance is available.
Ada 95 provides formal package parameters. One way of using these parameters is
to define a "signature" for a class of abstractions, such as all set
abstractions, or all physical unit abstractions, and then build a new generic
abstraction on top of the original class of abstractions using this signature as
the template for a formal package parameter.
Unfortunately, it is difficult to use signatures because of the fact that an
instantiation freezes all of its actual parameters.
Given the signature for a set abstraction:
type Element is private;
type Set is private;
with function Size(Of_Set : Set) return Natural is <>;
with function Union(Left, Right : Set) return Set is <>;
with function Intersection(Left, Right : Set) return Set is <>;
with function Empty return Set is <>;
with function Unit_Set(With_Item : Element) return Set is <>;
with function Nth_Element(Of_Set : Set) return Element is <>;
package Set_Signature is end;
... we could define a generic that required some set
abstraction, but it didn't care which one so long
as it implemented the above signature:
with package Base_Set is new Set_Signature(<>);
package Layered_Abstraction is
type Cool_Type(Set : access Base_Set.Set) is limited_private;
procedure Massage(CT : in out Cool_Type; New_Set : Base_Set.Set);
Now if we want to define a set type and provide the pre-instantiated
signature for it, we run into trouble:
type Elem is private;
with function Hash(El : Elem) return Integer;
package Hashed_Sets is
type Set is private;
function Union(Left, Right : Set) return Set;
package Signature is new Set_Signature(Elem, Set);
type Set is record
The problem is that we can't do the instantiation of Set_Signature where we
would want to do so, because the instantiation freezes the type "Set"
A similar problem occurs when a type wants to include a pointer to a container
based on the type being defined. For example:
package Expressions is
type Expr_Ref is private;
package Expr_Sequences is new Sequences(Expr_Ref);
type Seq_Of_Expr is access Expr_Sequences.Sequence;
function Operands(Expr : Expr_Ref) return Seq_Of_Expr;
type Expression; --
type Expr_Ref is access Expression;
Here we have a case where we want to instantiate a generic using a private type,
and use the results of the instantiation as the designated type of an access
type, which is in turn used as a parameter or result type of an operation on the
Unfortunately, we can't instantiate the "Sequences" generic with Expr_Ref, since
it is private.
Add "deferred" to the list of keywords in 2.9.
package defining_program_unit_name is
[deferred] new generic_package_name [generic_actual_part];
Add after 12.3(7):
A generic_instantiation which contains the reserved word deferred is
called a deferred instantiation.
The form of a pragma Complete is as follows:
Add after 12.3(10):
A deferred instantiation shall not be a library_unit_declaration.
AARM Note: This is not strictly necessary, but the completion of such
a unit would have to implicitly follow it, so it would be identical to
not using the deferred. That just makes the use confusing.
The generic_instantiation_local_name of a pragma Complete shall be the name
of a deferred instantiation, and shall not occur in any other pragma
Complete immediately in the same declarative region.
AARM Note: "Local_Name" takes care of the check that the instantiation
is in the same declarative region, and insures that the instantiation
comes first. See 13.1(5/1).
A deferred instantiation is said to be completed after the occurrence of a
pragma Complete referencing the name of the instance; if no pragma exists,
the deferred instantiation is completed at the end of the nearest enclosing
declarative_part or package_specification. If multiple deferred
instantations are completed at the end of a declarative_part or
package_specification (because pragma Complete was not specified for them),
they are completed in order of their declaration.
A name other than a package name or subtype name declared by a deferred
instantiation shall be referenced only if the deferred instantiation is
completed. A package name declared by a deferred instantiation shall
occur in a use_package_clause only if the deferred instantiation is
AARM Notes: These rules (plus the static semantic rules below) are
intended to prevent any access that would require freezing or elaboration
of the generic instance. Thus they can be thought of as being compile-time
Since we know that the elaboration of the instance body has completed
before we finish elaborating the immediate declarative region, references
to the instance from outside of that region do not need these rules.
That's different than the usual case in Ada; usually there is increasing
capability for local scopes. Here we have restrictions that apply only
locally, but not globally.
These rules are intentionally similar to the limited view rules.
End AARM Notes.
Add after paragraph 12.3(19):
If a deferred instantiation is not completed, each type_declaration in the
visible part denotes an incomplete view of the type. If the
type_declaration is tagged, then the view is a tagged incomplete view.
Add before paragraph 12.3(20):
A non-deferred instantiation is elaborated at the place of the
generic_instantiation. A deferred instantiation is elaborated at
at the place where it is completed.
[Editor's note: Do we need to change the wording of 12.3(20)? I think we don't
because the semantics of elaboration are the same, only the location is
different. But the use of the syntactic term might be confusing. A way around
would be to use the long form "generic_instantation including the reserved
word deferred" rather than the shorthand as above.]
Replace paragraph 13.14(5) by:
o The occurrence of a pragma Complete causes freezing of the deferred
instantiation that it references.
AARM Note: We don't need to specifically mention default completions,
because the end of the declarative_part or package_specification will
cause freezing, as noted in 13.14(3/1). Implementers can imagine that
the freezing occurs in the same order as elaboration of the instances
(that is, the order of declaration).
o The occurrence of a generic_instantiation that does not contain the
reserved word deferred causes freezing.
o If an instantiation causes freezing, if a parameter of that instantiation
is defaulted, the default_expression or default_name for that parameter
Add after paragraph 13.14(19/1):
A deferred instantiation shall be completed before it is frozen (see 12.3).
AARM Note: This rule is needed in case a non-instance body occurs before the
implicit or explicit completion. Other references that might freeze are already
illegal by the rules given uses of deferred instantiations that are not
Signature packages can be a very useful feature, but if they can't be
instantiated in the generic that defines the abstraction, they become of much
Generics in general can be very useful, but the freezing rules mean that they
are not as powerful as cut-and-paste, since they can't be applied to private
types before the private type is completely defined.
This proposal makes it possible for a generic instantiation to be used in more
places than before, since it can be used with a private type before the type is
completely defined. As things stand now, it is often necessary to completely
restructure things to be able to use a generic container as part of defining a
This proposal allows spliting an instantiation into two parts, delaying the
freezing and all of the run-time effects (and thus full definition
requirements) to the second part. Note that the actuals are not evaluated
until the completion is elaborated. That's necessary because evaluation of
the actuals would necessarily require them to be frozen. But freezing of
actual types, subprograms, and expressions would simply prevent the use of
partial views -- which of course is what we're trying to support. (We could
revise the freezing rules to work around this by saying that actual types and
subprograms, but that would still prevent the use of deferred constants as
actual parameters, which would be a significant restriction.)
The restrictions on the use of the generic unit before the completion are
similar to those of a limited view. We didn't use limited views directly,
because the restrictions only apply between the deferred instance declaration
and the completion, and thus are best described as legality and static
These restrictions can be thought of as static access-before-elaboration
checks. This has the nice property of maintaining the invariant that there is
never any runtime Elaboration_Check for entities exported by an instance.
Of course, it is possible for the instance body to be (implicitly) located at a
place where some of the actual types are not completely defined, and that's
problematic. We solve this by changing the freezing rules so that the instance
body freezes the actual parameters. This may lead to compile-time errors with
rather mysterious error messages, but hopefully these errors will be infrequent.
We only support deferred package instantiations, as subprograms do not export
anything interesting other than the subprogram itself. If it is desired to use
such a subprogram instantiation with partial views, that can easily be
accomplished with a renames-as-body. So we don't need a special construct for
that. (Supporting it would complicate some of the wording, but otherwise is not
With this proposal, the following example extracted from the email discussion
works as expected:
type Elem is private;
with function Code (I : Elem) return Natural;
type Elem_Array is array (Positive range <>) of Elem;
package Array_Hashing is
function Hash (Arr : Elem_Array) return Natural;
package body Array_Hashing is
type Nat_Mod is mod Natural'Last;
function Hash (Arr : Elem_Array) return Natural is
Res : Nat_Mod := 1;
for I in Arr'range loop
Res := Res*3 + Nat_Mod(Code(Arr(I)));
package User is
type Something is private;
function Count (A : Something) return Natural;
type Array_of_Something is array (Positive range <>) of Something;
package My_Hash is deferred new Array_Hashing (Something,
NULL_HASH : constant Natural;
type Something is range 0 .. 200;
pragma Complete (My_Hash);
NULL_HASH : constant Natural := My_Hash.Hash ((1..0 => 0));
The instance body for My_Hash is located immediately before the call to
My_Hash.Hash, so this call is legal. The instance body freezes Something,
but this type is already completely defined so there is no illegality.
NOTES ON THE DESIGN
The use of a pragma for completion is somewhat uncomfortable. This pragma
changes the visibility of items, which generally not allowed for pragmas.
A pragma was used because it didn't require the introduction of yet another
keyword or odd conformance rules. It also seems more in keeping with something
optional; syntax would seem more appropriate if the completion was required to
A syntax alternative would be:
package deferred_generic_instantiation_local_name is complete;
with the same semantics as the pragma. If syntax is used, it probably ought to
be required, so that there is no implicit completion. That would simplify
some of the rules.
Other syntax alternative considered were:
package deferred_generic_instantiation_local_name is elaborated;
which also requires a second keyword; and
package deferred_generic_instantiation_local_name is new generic_name (<>);
which requires a conformance rule for the generic_name.
Another alternative would be to repeat the entire instantiation at the point of
the completion. But that would look like a full instantiation, which is
confusing both readers and the compiler. It also would require conformance
rules for the formal parameters, which just isn't worth the effort.
A number of alternative syntaxes were considered for the instantiation.
We could use limited instead of deferred to avoid using a new keyword:
package My_Hash is limited new Array_Hashing (Something);
pragma Complete (My_Hash);
but this seems confusing, since the view is limited only within the package.
Another option avoiding a new keyword is:
delay package My_Hash is new Array_Hashing (Something);
pragma Complete (My_Hash);
but this is pretty weird.
An ACATS test should be created for this feature.
Questions? Ask the ACAA Technical Agent