AI22-0056-1
!standard 3.2.3(8) 23-08-24 AI22-0056-1/05
!standard 3.4(27/6)
!standard 3.9.1(4.2/5)
!standard 3.9.3(4/3)
!standard 3.9.3(6/5)
!class Amendment 23-01-11
!status Amendment 1-2022 23-06-27
!status WG9 Approved 23-10-12
!status ARG Approved 6-0-0 23-06-13
!status work item 23-01-11
!status received 23-01-11
!submitter Randy Brukardt
!priority Medium
!difficulty Hard
!subject Automatic creation of constructor functions
We define "automatic constructor extension"; types that have this property will automatically create constructor functions upon derivation, just as they are created for null extensions in Ada 2022.
Imagine that you have a classic mix-in generic – that is, a generic package that defines a type that is an extension of a generic formal type:
generic
type T is tagged private; -- or type T is
new TTT with private;
package Generic_Package is
type Derived is new T with ...
end Generic_Package;
The issue comes up when you try to instantiate the package, and the actual type has functions that must be overridden (functions with controlling results). Now the instantiation will fail, because no overriding subprogram is declared in the generic package. But of course it's impossible for an overriding subprogram to be put in the generic, because the generic specification cannot know what operations may exist that need to be overridden (and shouldn't know, in order to be as general as possible).
Note that this problem doesn't occur if one replaces with ... by with null record. We therefore have a disincentive to use private extensions or extension components with mix-ins. And clearly many mix-ins need extension components as part of their implementation.
We propose extending the existing "null extension" rules to allow extension components if the type has a new aspect ("Constructor_Extension") specified for the type.
Modify 3.2.3(8):
A primitive subprogram whose designator is an operator_symbol is called a primitive operator. {A primitive function of T whose result type is T is called a constructor function of T.}
Modify 3.4(27/2):
For the execution of a call on an inherited subprogram, a call on the corresponding primitive subprogram of the parent or progenitor type is performed; the normal conversion of each actual parameter to the subtype of the corresponding formal parameter (see 6.4.1) performs any necessary type conversion as well. If the result type of the inherited subprogram is the derived type, the result of calling the subprogram of the parent or progenitor is converted to the derived type, or in the case of a {type that has automatic constructor extension (see 3.9.1)}[null extension], extended to the derived type using the equivalent of an extension_aggregate with the original result as the ancestor_part and {others => <>}[null record] as the record_component_association_list.
Add after 3.9.1(4.2/5) [in Static Semantics]:
For a record extension or private extension that does not have a known_discriminant_part, the following type-related aspect can be specified:
Constructor_Extension
A type extension for which aspect Constructor_Extension is True is said to have automatic constructor extension. As defined in 3.4, for such a type, an inherited constructor function whose corresponding constructor function of the parent of the full type is not abstract, is defined to return an object given by an extension aggregate.
Aspect Constructor_Extension is never inherited; it is True if the type extension is a null extension or if the aspect is specified as True for the type extension; it is False otherwise. If a type has a partial view that is a private extension, the Constructor_Extension aspect shall be specified (if at all) only on the partial view. If specified True on the partial view, the full type shall not have a known_discriminant_part.
Modify 3.9.3(4/3):
If a type has an implicitly declared primitive subprogram that is inherited or is a predefined operator, and the corresponding primitive subprogram of the parent or ancestor type is abstract or is a function with a controlling access result, or if a {nonabstract} type [other than a nonabstract null extension]{that does not have automatic constructor extension} inherits a {constructor} function [with a controlling result], then:
Modify 3.9.3(6/5):
Otherwise, the subprogram shall be overridden with a nonabstract subprogram or, in the case of a private extension inheriting a nonabstract {constructor} function [with a controlling result], have a full type that [is a null extension]{has automatic constructor extension}[Redundant:; for a type declared in the visible part of a package, the overriding may be either in the visible or the private part]. Such a subprogram is said to require overriding. However, if the type is a generic formal type, the subprogram is allowed to be inherited as is, without being overridden for the formal type itself;[Redundant: a nonabstract version will necessarily be provided by the actual type.]
Modify AARM 3.9.3(6.a/2):
A{ constructor} function[ that returns the parent type] requires overriding for a type extension (or becomes abstract for an abstract type){, if the type does not have automatic constructor extension,} because conversion from a parent type to a type extension is not defined, and function return semantics{ without automatic constructor extension} is defined in terms of conversion{ (see 3.4)} [(other than for a null extension; see below)]. (Note that parameters of mode in out or out do not have this problem, because the tag of the actual is not changed.)
Modify AARM 3.9.3(6.g/2):
For a {type that has automatic constructor extension}[null extension], the result of a {constructor} function[ with a controlling result] is defined in terms of an extension_aggregate with {an} [a null record] extension part{ with each component defaulted} (see 3.4). This means that these restrictions on{ constructor} functions[ with a controlling result] do not have to apply to {such types}[null extensions].
[Note: It appears that 4.2.1(14/5) needs a similar change, but that rule was deleted by AI22-0003-1, so changing it is not necessary.]
Besides the case of generic dispatching constructor, Ada 2022 also requires the use of functions to get user-defined literals, making them even more common for tagged types.
This solution is just a generalization of the existing rules for null extensions. We keep compatibility by making all null extensions have this new aspect with value True.
Note that this solution mitigates the maintenance hazard posed by the existing null extension rule (if a component needs to be added by maintenance, suddenly a bunch of routines need to be overridden). With this new aspect, adding the aspect and appropriate defaults will keep the existing behavior and prevent the discontinuity. (The first author of this AI finds this property more appealing than the original problem itself; he has always been against the null extension rules because of this maintenance hazard.)
Also note that this leaves the trade-off between compile-time checking and automatic creation in the hands of the programmer. It might have been preferred for “automatic creation” to be the default, and the "shall be overridden" case the one selected by the aspect, but it's too late for that as it would reduce safety of existing code.
We do not allow specifying automatic constructor extension with a configuration pragma as that would increase the likelihood of inherited constructor functions returning uninitialized extension-part components, without any aspect directly specified on the type extension.
An alternative solution would be to make a special case for generics, such that Program_Error is raised from subprograms that require overriding in a generic rather than rejecting the instantiation.
This idea was rejected for two reasons: First, it essentially changes a compile-time check into a runtime check. That's especially bad if some client wants to use a dispatching constructor – the mix-in generic cannot know how the resulting type might be used and it's bad to restrict that for clients.
Secondly, this would give an unusual incentive to use generics even when that is not needed, as derivations within a generic would be "easier" than the same derivation in non-generic code. We try to avoid such incentives by making the capabilities of generic and non-generic code equivalent.
The implementation of this extension is somewhat more complex than the original mechanism. The size of the resulting object is different (not just the tag), and initial values may need to be assigned to the object. Of course, these operations already are done when implementing extension aggregates, so these are not new problems.
This can always be implemented with a wrapper function that returns the appropriate extension aggregate (which is typically what the human programmer ends up writing without this feature). That should not cause any problems, as it is the same as existing constructs. However, existing non-wrapper implementations would be complicated by this extension.
On the other hand, implementations are probably already using a wrapper to implement the automatic generation of functions for null extensions, at least for dispatching calls. That is needed because dispatching directly to the original routine would return an object with the wrong tag. There has to be code to change the tag, and most likely that is placed in a wrapper so that all dispatching calls are handled the same. Since a wrapper is needed for dispatching, it exists and could be called for statically bound calls in more complex cases.
This solution has not been checked for problems related to the evaluation of default expressions. As such expressions are arbitrary, they could require finalization of objects or even task waiting. At a first look, it would seem these problems are no different than returning an arbitrary aggregate from a function. Since this can always be implemented with a wrapper, it seems unlikely that any new problems would be introduced.
However, it may be better to restrict the default expressions allowed in this case to static expressions and aggregates with all of the components being static expressions, as such a restriction would eliminate any finalization or task waiting requirements. This would be similar to the restrictions on aspects like Default_Value.
The original Ada 2012 version of this AI required that all of the extension components be initialized. Besides being ill-defined in the case of components of a private type (which may or may not be initialized), it also was overly restrictive. The requirement for a user of this feature is that the object is properly initialized by default. But that may not require all of the components to be initialized. For instance, if an extension includes a buffer and a length, setting the length to zero is sufficient to properly initialize the extension. No one cares what is in the (unused) buffer.
Thus, it seems better to simplify the definition of the feature and leave it up to the user to determine if the initialization is sufficient. A compiler could warn if an extension has nothing that is initialized by default and the aspect is given, if that is considered likely enough to be a problem.
It has been suggested that the original problem can be solved by making the mix-in type
abstract and then using an "extracting" derivation to make the type concrete and to provide
the needed constructor functions.
This would look something like:
package P is
type Root is tagged private;
function Make_One return Root;
-- Other operations defined here.
private
...
end P;
generic
type T is abstract tagged private;
package Mix_In is
type New_Type is abstract new T with
private;
-- Mix-in operations defined here.
private
...
end Mix_In;
with P, Mix_In;
package Q is
package Blended is new Mix_In (P.Root);
type Mixed is new Blended.New_Type with null
record;
function Make_One return Mixed;
end Q;
So far, this is legal. However, we now are faced with the task of creating a body for the function Make_One.
package body Q is
function Make_One return Mixed is (P.Make_One with
???);
end Q;
We want to call the parent operation to provide the parent part of the object. But, since the extension components of New_Type are private, we don't know what to write for the extension part. (Indeed, this extension aggregate is not legal for this reason.)
We could imagine allowing an aggregate like (P.Make_One with <>) to allow all of the components to be set to their default values. (Indeed, this was proposed for Ada 2005, but eventually the idea was dropped due to problems with task and protected types.) But that would also require a new Ada feature; if we're going to do one of those, we might as well fix the problem more directly.
We could imagine adding a function to package Mix_In to do the job, perhaps
function Make_From_T (A : in T) return New_Type;
But that doesn't work since New_Type is an abstract type and such a function has to be abstract.
So one would have to add a procedure:
procedure Make_From_T (A : in T; B : out New_Type);
and the body would have to do assignments to the parts:
procedure Make_From_T (A : in T; B : out
New_Type) is
begin
T(B) := A;
B.Ext_Comp := ...;
...
end Make_From_T;
For this to be legal, T cannot be abstract. Moreover, we probably don't want this to be inherited along with New_Type, so we have to put it into a nested package to make it not primitive. This makes our Mix_In package look like:
generic
type T is tagged private;
package Mix_In is
type New_Type is abstract new T with
private;
-- Mix-in operations defined here.
package Inner is
procedure Make_From_T (A : in T; B : out
New_Type);
-- Don't inherit this routine.
end Inner;
private
...
end Mix_In;
Now we can implement Make_One as:
package body Q is
function Make_One return Mixed is
B : Mixed;
begin
Blended.Inner.Make_From_T (P.Make_One, Blended.New_Type(B));
-- No additional extension components here to
worry about.
end Make_One;
end Q;
OK, so we've found a way in current Ada to do this. But mix-ins are themselves a work-around for the lack of multiple inheritance in Ada. To make them work in the common case where constructor functions are in use, one has to use the additional work-around of declaring them abstract and then using a concrete derivation from the abstract version. And to actually be able to implement such a concrete derivation, one needs additional work-arounds in order to be able to initialize the extension components of the mix-in type. And those additional work-arounds require restricting the applicability of the mix-in along with requiring a number of extra steps even when deriving the parent type does not actually require any overriding. A proper solution seems better than all of this.
@drepl
A primitive subprogram whose designator is an @fa{operator_symbol} is called a @i{primitive operator}.
@dby
A primitive subprogram whose designator is an @fa{operator_symbol} is called a @i{primitive operator}. A primitive function of @i{T} whose result type is @i{T} is called a @i{constructor function} of @i{T}.
@drepl
For the execution of a call on an inherited subprogram, a call on the corresponding primitive subprogram of the parent or progenitor type is performed; the normal conversion of each actual parameter to the subtype of the corresponding formal parameter (see @ref{6.4.1}) performs any necessary type conversion as well. If the result type of the inherited subprogram is the derived type, the result of calling the subprogram of the parent or progenitor is converted to the derived type, or in the case of a null extension, extended to the derived type using the equivalent of an @fa{extension_aggregate} with the original result as the @fa{ancestor_part} and @b{null record} as the @fa{record_component_association_list}. In either case, the result is then converted to the corresponding subtype (as defined above) of the derived type.
@dby
For the execution of a call on an inherited subprogram, a call on the corresponding primitive subprogram of the parent or progenitor type is performed; the normal conversion of each actual parameter to the subtype of the corresponding formal parameter (see @ref{6.4.1}) performs any necessary type conversion as well. If the result type of the inherited subprogram is the derived type, the result of calling the subprogram of the parent or progenitor is converted to the derived type, or in the case of a type that has automatic constructor extension (see @ref{3.9.1}), extended to the derived type using the equivalent of an @fa{extension_aggregate} with the original result as the @fa{ancestor_part} and @b{others} => <> as the @fa{record_component_association_list}. In either case, the result is then converted to the corresponding subtype (as defined above) of the derived type.
@dinsa
In the case where the (compile-time) view of an object @i{X} is of a tagged type T1 or T1'Class and the (run-time) tag of @i{X} is T2'Tag, only the components (if any) of @i{X} that are components of T1 (or that are discriminants which correspond to a discriminant of T1) are said to be @i{components of the nominal type} of @i{X}. Similarly, only parts (respectively, subcomponents) of T1 are parts (respectively, subcomponents) of the nominal type of @i{X}.
@dinss
For a record extension or private extension that does not have a @fa{known_discriminant_part}, the following type-related aspect can be specified:
@xhang{@xterm{Constructor_Extension}
A type extension for which aspect Constructor_Extension is True is said to have @i{automatic constructor extension}. As defined in @ref{3.4}, for such a type, an inherited constructor function whose corresponding constructor function of the parent of the full type is not abstract, is defined to return an object given by an extension aggregate.}
@xindent{Aspect Constructor_Extension is never inherited; it is True if the type extension is a null extension or if the aspect is specified as True for the type extension; it is False otherwise. If a type has a partial view that is a private extension, the Constructor_Extension aspect shall be specified (if at all) only on the partial view. If specified True on the partial view, the full type shall not have a @fa{known_discriminant_part}.}
@drepl
If a type has an implicitly declared primitive subprogram that is inherited or is a predefined operator, and the corresponding primitive subprogram of the parent or ancestor type is abstract or is a function with a controlling access result, or if a type other than a nonabstract null extension inherits a function with a controlling result, then:
@dby
If a type has an implicitly declared primitive subprogram that is inherited or is a predefined operator, and the corresponding primitive subprogram of the parent or ancestor type is abstract or is a function with a controlling access result, or if a nonabstract type that does not have automatic constructor extension inherits a constructor function, then:
@drepl
Otherwise, the subprogram shall be overridden with a nonabstract subprogram or, in the case of a private extension inheriting a nonabstract function with a controlling result, have a full type that is a null extension; for a type declared in the visible part of a package, the overriding may be either in the visible or the private part. Such a subprogram is said to @i{require overriding}. However, if the type is a generic formal type, the subprogram is allowed to be inherited as is, without being overridden for the formal type itself; a nonabstract version will necessarily be provided by the actual type.
@dby
Otherwise, the subprogram shall be overridden with a nonabstract subprogram or, in the case of a private extension inheriting a nonabstract constructor function, have a full type that has automatic constructor extension; for a type declared in the visible part of a package, the overriding may be either in the visible or the private part. Such a subprogram is said to @i{require overriding}. However, if the type is a generic formal type, the subprogram is allowed to be inherited as is, without being overridden for the formal type itself; a nonabstract version will necessarily be provided by the actual type.
package P is
type Root is tagged private;
function Make_One return Root;
-- Other operations defined here.
private
...
end P;
generic
type T is tagged private;
package Mix_In is
type New_Type is new T with private
with Constructor_Extension;
-- Mix-in operations defined here.
private
...
end Mix_In;
with P, Mix_In;
package Q is
package Mixed is new Mix_In (P.Root); -- Now legal.
end Q;
ACATS tests should check that the aspect is implemented, and that functions do not need to be overridden for extensions when the aspect is given.
This AI was promoted from AI12-0083-1 to be reconsidered for post-Ada 2022 work. The !appendix of the original AI has additional motivation and discussion.