AI22-0147-1
!standard 3.9(15) 26-04-22 AI22-0147-1/02
!class Amendment 25-11-17
!status No Action 10-0-0 26-05-12
!status work item 25-11-17
!status received 25-11-17
!assigned author Tucker Taft
!submitter Tucker Taft
!priority Low
!difficulty Medium
!subject Controlling what is a controlling parameter
Use the Rooted attribute on a parameter or result type to prevent it from being considered a controlling parameter/result.
There are situations where a particular subprogram should *not* be inherited by derived types, or when derived, one or more parameter or result types should *not* change to match the derived type.
Should we provide a mechanism to indicate that a formal parameter or result of a function is not to be considered a controlling/parameter result? (Yes.)
We recommend providing a new attribute of a type, Rooted, which when applied to the type of a formal parameter or the result of a subprogram, prevents that parameter/result from being considered a "controlling" parameter or result, or in the untagged case, not be the cause of the subprogram becoming a "primitive" operation of the type.
For example, given a case such as the following:
type Holder_Storage_Pool is new
Root_Storage_Pool_With_Subpools ...
type Holder is tagged ...
function Create_Subpool (HSP : Holder_Storage_Pool; H : Holder)
return Subpool_Handle;
we would be violating RM 3.9.2(12) which precludes having a subprogram that is a dispatching operation of two tagged types. To avoid this problem, we could apply the Rooted attribute to one of the formal parameter types to indicate the subprogram is not a primitive (dispatching) operation of that type (in this case, we choose to do that on the storage pool type):
type Holder_Storage_Pool is new
Root_Storage_Pool_With_Subpools ...
type Holder is tagged ...
function Create_Subpool (HSP : Holder_Storage_Pool'Rooted; H : Holder)
return Subpool_Handle;
From a semantic point of view, we treat 'Rooted as producing a new type, called a "rooted" type. It is similar to a class-wide type in that it has no primitive operations of its own. Furthermore, as with a class-wide type, it is implicitly convertible to and from the corresponding unrooted type. The big difference relative to a class-wide type is that a rooted type involves no dispatching upon conversion to the corresponding specific type. Another way to say this is that an object of a rooted type is not considered dynamically tagged; it is statically tagged with the same tag as that of its corresponding unrooted type.
Modify 3.2.3(1/2):
An operation[a][b] operates on a type T if it yields a value of type T, if it has an operand whose expected type (see 8.6) is T, or if it has an access parameter or access result type (see 6.1) designating T. A predefined operator, or other language-defined operation such as assignment or a membership test, that operates on a type, is called a predefined operation of the type. The primitive operations of a {specific }type are the predefined operations of the type, plus any user-defined primitive subprograms.
Modify 3.4.1(3/5):
Every type is one of a specific type, {a rooted type[c][d][e][f][g][h], }a class-wide type, or a universal type. A specific type is one defined by a type_declaration, a formal_type_declaration, or a full type definition embedded in another construct. {A rooted type is a type that has no primitive operations of its own[i][j][k][l], but is implicitly defined when a specific type is defined, and is implicitly convertible to and from the corresponding specific type. }Class-wide and universal types are implicitly defined, to act as representatives for an entire class of types, as follows:
Add after 3.4.1(11):
For every subtype S of a specific type T[m][n], the following attribute is defined:
S'Rooted
S'Rooted denotes a subtype of the rooted type (called T'Rooted in this document) corresponding to the specific type T. If S is constrained, then the values of S'Rooted are constrained to those values that when converted to the type T belong to S. [Redundant: If a formal parameter of a subprogram is of a rooted type, an operand of the corresponding specific type may be passed as the actual, without the subprogram being considered an operation of the specific type. Similarly, if the result of a function is of a rooted type, a value of the corresponding specific type may be returned, without the function being an operation of the specific type.]
Add after 8.6(21):
Add after 8.6(24):
There are (at least) two reasons why one might want to "hide" a subprogram, or at least a particular parameter or result, from the inheritance mechanism:
The standard idiom for solving this problem is moving the problematic subprogram, or the problematic type, into a nested package (or a child package). This can create other problems of its own, and is potentially a heavier-weight solution than seems desirable.
We originally considered having some kind of aspect or reserved word on the subprogram as a whole, but that would only cover case 1. The advantage of making the indication on a particular parameter or the result is that it covers case 2 as well, and it relates nicely to the other way that one can prevent inheritance, by using the Class attribute on a parameter/result type.
We chose the attribute name "Rooted"[r][s][t][u] because it implies that a rooted type is "rooted" to the defining package, and it also relates to the notion of the "root" type of a derivation tree, where this "rooted type" will not follow the derivation tree on inheritance, but will rather be connected solely to the corresponding root type of the hierarchy.
Originally we considered 'Rooted as producing a special kind of rooted subtype, but it was felt to be strange for a subtype to have such different semantics from its associated type. On the other hand, we already have an attribute that produces a class-wide type from a specific type, and so using an aspect to create a rooted type from an unrooted type seems a more consistent approach.
Here are some examples of use of the Rooted aspect:
type Bit_Set is new Set with ...
function Make_Bit_Set (Num_Bits : Positive) return Bit_Set'Rooted;
This constructor function is not to be inherited. At a minimum, it would probably want to have a different name in any derived type.
type A is tagged ...
type B is new Some_Interface with ...
procedure Process (X : in out A; Y : B'Rooted);
In this case, type B is visibly extended from some useful progenitor interface, but other than that, there was no reason for B to be tagged, and making the Process operation dispatching on B and inherited by B's descendants would not make sense.
This test would probably need both B tests and C tests to verify that inheritance does not happen when it is not supposed to happen.
From: Christoph Grein
Sent: November 18, 2025
I do not see the difference to ‘Class.
function Create_Subpool
(SP : My_Storage_Pool'Class; Storage : My_Holder) return Subpool_Handle;
function Create_Subpool
(SP : My_Storage_Pool; Storage : My_Holder'Class) return Subpool_Handle;
versus
function Create_Subpool
(SP : My_Storage_Pool'TBD; Storage : My_Holder) return Subpool_Handle;
function Create_Subpool
(SP : My_Storage_Pool; Storage : My_Holder’TBD) return Subpool_Handle;[v][w][x]
From: Tucker Taft
Sent: December 5, 2025
When a formal parameter is of a class-wide type, it means the actual parameter might be of any descendent of the type. For My_Holder'Rooted, we are restricting it to the type My_Holder, but like for a class-wide type, it does not cause the operation to be a primitive of My_Holder.
[a]If S is a subprogram with some T'Rooted in its profile, and A is an access-to-subprogram type with the same profile except that in some places T is used instead of T'Rooted, or vice versa, do the profiles match? (Perhaps the correct term is some level of conformance.) I assume not. Should they match?
[b]Interesting question. Simplest rule is no. But it might be convenient if it would work. Probably need to define "mode conformance" and "subtype conformance" in the presence of parameters that differ in T vs. T'Rooted (or T vs. "abs T" if we go that route).
[c]The introduction of a whole new category of types seems drastic considering that the aim of this AI, as I understood it, was to somehow "
mark formal parameters as not being controlling parameters. In the last ARG meeting, IIRC there was a suggestion to use the "abs" reserved word as that mark, without creating a new category of types, and just creating a new property of formal parameters. That would be written as P : in abs T, instead of P : in T'Rooted.
If 'Rooted can be used in other contexts, and not just in subprogram profiles, it will be possible to:
- declare stand-alone objects as T'Rooted
- declare components as T'Rooted
- derive new types from T'Rooted.
Is it the intent to allow the above uses, or should they be forbidden in some way?
If derivation from T'Rooted is allowed, is the derived type rooted or specific?
It seems that T'Rooted is tagged if T is tagged, so one could use T'Rooted'Class, which would differ from T'Class. Perhaps this too should be prevented?
[d]T'Rooted and T'Class were intended to be very similar in terms of rules. There seemed no reason to make special restrictions on use of T'Rooted, but I agree at a minimum we have to be sure any legal use needs to be well defined.
The use of "abs" for parameters and results is an interesting alternative. Probably the best approach would be to create a separate "variant" of this AI (i.e. AI22-0147-2) with that approach, so we have something to compare against.
[e]Re the "abs" alternative: I see from my notes that it was Jean-Pierre who suggested that, and Steve liked it, as do I.
I can have a go at writing an "abs" variant of this AI, but I will need some help in starting. I remember seeing somewhere instructions for creating a new AI, but now I can't find them.
[f]Go to the "Active AIs" google folder, make a copy of the "template" AI, move it into the "New AIs" sub-folder, and then start editing it. The template AI includes instructions within it.
[g]Thanks, Tuck. I made a copy in the Active AIs folder, but I could not move it into the New AIs folder -- perhaps that folder is not shared with me.
By the way, about sharing folders, I have two Google identities: niklas.holsti@tidorum.fi, which I like to use for business, including ARG work such AI commenting, and niklasholsti@gmail.com, which I use for personal stuff like Youtube viewing. It seems that the Active AIs folder is now shared (only?) with the latter identity, because I see only a few AIs there if I access it with the former identity. Could you adjust the folder sharing so that (only) the identity niklas.holsti@tidorum.fi is used? And share the New AIs folder with that identity.
[i]If a formal parameter has a rooted type, and a rooted type has no primitive operations of its own, how can the parameter be used in the body of the subprogram? Can a parameter of type T'Rooted be used in exactly the same ways as if it had the type T, relying on the new relaxation of resolution rules in 8.6?
[j]Yes, that is the point of providing implicit conversion both ways.
[k]I wonder if the new rules in 8.6 are enough for prefixed-view calls. The rules in 8.6 apply when there is an "expected type", but the rules 9.1 and 9.2 for prefixed views in 4.1.3 do not define an expected type for the prefix, they only say how the prefix "shall resolve". Perhaps that implies some "expected types"?
[l]Good question. I believe we would want to define T as an ancestor of T'Rooted (in 3.4.1(10/2), implying S'Rooted should probably be defined immediately *before* this paragraph rather than after paragraph 11). We would also want to mention a rooted type in the first sentence of 4.1.3(9.2/7).
[m]I'm wondering if this makes sense when T is an abstract type. Would it be possible to still provide an actual parameter in a call?
[n]You can create a parameter of an abstract type by converting a class-wide object to the abstract type. It will succeed so long as the abstract type is an ancestor of the type identified by the tag. Admittedly not very useful, but you could fetch a component, for example, or pass it to a non-abstract operation of the abstract type.
[o]This is currently illegal, wouldn't it be necessary to change some rule somewhere to allow two tagged types?
[p]We currently allow one of them to be class-wide. This AI is proposing that we allow one of them to be a "rooted" subtype.
[q]A key feature of using 'Rooted rather than 'Class is apparent when it is the result type. Returning a class-wide object is generally more expensive because the object is of unknown size, and might include controlled subcomponents. Returning an object of a specific type can be significantly more efficient. Furthermore, all subsequent uses of the result can avoid dispatching calls, and can make use of inlining when desired.
[r]The name "Rooted" does not suggest these properties to me. I believe that C++ and Java use the word "final" for denying subclassing and inheritance; did you consider "Final" as a possible name for this attribute?
I don't know the full details of the Java/C++ "final" concept, and perhaps it has some features that are undesirable in this context (such as meaning "immutable" or "single assignment" for "final objects"). If so, how about "Fixed" or "Fix"?
[s]I did consider all of these (see the associated ARG GitHub issue where these alternatives are mentioned). Final has many different meanings in Java, and none really match this. Can you explain why "Rooted" does not convey the meaning? It has the proper meaning in my experience.
[t]Such subjective impressions are hard to explain, but "Rooted" makes me think of a tree, here an inheritance tree, so rather than saying "forget or deny inheritance" it makes me think that inheritance /is/ possible.
The form "Root" would more suggest a "fixation", but T'Root could be misunderstood as the original root (ultimate ancestor) of T, so I don't suggest it. RM 3.4.1 (2/2) defines "the class rooted at T", so the term "rooted" is already in the RM.
"Disinherit" would be the legal term: T'Disinherit. Smiley.
[u]In English, "Rooted" generally means unable to be moved elsewhere (because of deep roots), often used as "rooted to a spot".
[v]Please do not directly edit the AI with comments. Instead use the Google Doc "Comment" capability.
[w]If you do (because you want the message to exist for the entire life of the AI - comments tend to be dismissed) - make sure you tell us who made the comment!
[x]I moved the answer from a comment to here so that it is given somewhere permanent. Comments often are removed.