!standard 3.4(18/2) 09-11-30 AI05-0164-1/02
!standard 6.1(27.1/2)
!standard 6.1(28.2/2)
!class binding interpretation 09-10-21
!status Amendment 201Z 09-11-30
!status ARG Approved 10-0-1 09-11-08
!status work item 09-10-21
!status received 09-06-23
!priority Low
!difficulty Medium
!qualifier Omission
!subject Parameters of access-to-subprogram parameters and derivation
!summary
The types of parameters of access-to-subprogram parameters are not modified
by derivation.
!question
When a derived type is declared, 3.4(18) says that the profiles of inherited
subprograms are obtained by systematically replacing each "subtype of its
profile" that is of the parent type, with the derived type (actually the
"corresponding subtype").
6.1(27.1/2) says that for access parameters of an access-subprogram type, the
subtypes of the profile include the subtypes of the profile of the parameter
type.
So this means that if Typ2 is derived from Typ1, if any primitive operations of
Typ1 have access-subprogram parameters (or results) that themselves have
parameters of type Typ1 (or have access-subprogram parameters that have
parameters of type Typ1, and so on recursively), those occurrences of Typ1 are
changed to Typ2 in the inherited subprogram declaration. I.e.:
type Typ1 is record ... end record;
procedure Primitive_Op (X : in out Typ1;
Func : access function (Z : Typ1)
return Typ1);
type Typ2 is new Typ1;
-- Implicit inherited subporgram:
--procedure Primitive_Op (X : in out Typ2;
-- Func : access function (Z : Typ2)
-- return Typ2);
But this causes a number of problems. For instance, if Typ2 is constrained and
Typ1 is not, the body of Primitive_Op won't have code to check the
constraint before the value is returned. It's also possible that Typ1 and
Typ2 generate different code for parameter passing (for instance, if
Typ1 is an unconstrained discriminanted record with defaults, it may have a
'Constrained value passed with the parameter, while a constrained Typ2 may
not have such a value passed.
Finally, if a dispatching call is made to Primitive_Op, a non-dispatching
'Access value will be passed, which very easily could have the wrong types
for the actual body called.
Thus, it seems that derivation should not change the profile of
access-to-subprogram parameters. Is this the intent? (Yes.)
!recommendation
(See Summary.)
!wording
Modify 3.4(18/2):
The profile of an inherited subprogram (including an inherited enumeration literal)
is obtained from the profile of the corresponding (user-defined) primitive subprogram
of the parent or progenitor type, after systematic replacement of each subtype of its
profile (see 6.1){, other than those found in the designated profile of an access_definition,}
that is of the parent or progenitor type with a *corresponding subtype* of the derived type.
For a given subtype of the parent or progenitor type, the corresponding subtype of the derived
type is defined as follows:
Modify 6.1(27.1/2):
* For any access parameters of an access-to-subprogram type, the subtypes of
the {designated} profile of the parameter type.
Modify 6.1(28.2/2):
* For any access result type of an access-to-subprogram type, the subtypes of
the {designated} profile of the result type.
!discussion
Changing the definition of the profile of a subprogram is not possible; it's
used in the definition of conformance and specifically by the definition of
subtype conformance. The subtypes of access-to-subprogram parameters have to
participate in conformance.
Thus we add an exception to 3.4(18).
---
The changes to 6.1(27.1/2) and 6.1(28.2/2) are needed as access-to-subprogram types have
designated profiles, but don't directly have a profile.
!corrigendum 3.4(18/2)
@drepl
The profile of an inherited subprogram (including an inherited enumeration literal)
is obtained from the profile of the corresponding (user-defined) primitive subprogram
of the parent or progenitor type, after systematic replacement of each subtype of its
profile (see 6.1) that is of the parent or progenitor type with a @i
of the derived type. For a given subtype of the parent or progenitor type, the
corresponding subtype of the derived type is defined as follows:
@dby
The profile of an inherited subprogram (including an inherited enumeration literal)
is obtained from the profile of the corresponding (user-defined) primitive subprogram
of the parent or progenitor type, after systematic replacement of each subtype of its
profile (see 6.1), other than those found in the designated profile of an @fa,
that is of the parent or progenitor type with a @i of the derived type.
For a given subtype of the parent or progenitor type, the corresponding subtype of the derived
type is defined as follows:
!corrigendum 6.1(27.1/2)
@drepl
@xbullet
@dby
@xbullet
!corrigendum 6.1(28.2/2)
@drepl
@xbullet
@dby
@xbullet
!ACATS Test
Add a B-Test (using overriding indicators) to ensure that routines are inherited
properly.
!appendix
!topic Dispatching operations and access-subprogram parameters
!reference 3.9.2(16), 6.1(27.1/2), 3.4(18/2)
!from Adam Beneschan 09-06-23
!discussion
When a derived type is declared, 3.4(18) says that the profiles of inherited
subprograms are obtained by systematically replacing each "subtype of its
profile" that is of the parent type, with the derived type (actually the
"corresponding subtype").
6.1(27) says that for access parameters of an access-subprogram type, the
subtypes of the profile include the subtypes of the profile of the parameter
type. (I think that this clause should read "subtypes of the designated profile
of the parameter type", since access types don't themselves have profiles by
6.1(22); I'm assuming that's what is meant.)
So this appears to mean that if Typ2 is derived from Typ1, if any primitive
operations of Typ1 have access-subprogram parameters (or result) that themselves
have parameters of type Typ1 (or have access-subprogram parameters that have
parameters of type Typ1, and so on recursively), those occurrences of Typ1 are
changed to Typ2 in the inherited subprogram declaration. I.e.:
type Typ1 is record ... end record;
procedure Primitive_Op (X : in out Typ1;
Func : access function (Z : Typ1)
return Typ1);
type Typ2 is new Typ1;
-- Implicit inherited subporgram:
--procedure Primitive_Op (X : in out Typ2;
-- Func : access function (Z : Typ2)
-- return Typ2);
This would seem to cause a problem with dispatching operations, though.
3.9.2(16) says that when a call on a dispatching operation has a dynamically
tagged controlling tag, and there is more than one controlling operand, they
must all have the same tag or else Constraint_Error is raised. But an
access-subprogram parameter is not a controlling operand, so there appears to be
no check at all to make sure it's a subprogram with the right type.
So what does this do?
package Pack1 is
type Root is tagged null record;
procedure Op (Obj : in out Root;
Count : in Integer;
Func : access function (Obj : in Root) return Root);
end Pack1;
package body Pack1 is
procedure Op (Obj : in out Root;
Count : in Integer;
Func : access function (Obj : in Root) return Root) is
begin
null;
end Op;
end Pack1;
with Pack1;
package Pack2 is
type T1 is new Pack1.Root with record
F1 : Integer := 0;
end record;
overriding
procedure Op (Obj : in out T1;
Count : in Integer;
Func : access function (Obj : in T1) return T1);
end Pack2;
with Text_IO;
package body Pack2 is
procedure Op (Obj : in out T1;
Count : in Integer;
Func : access function (Obj : in T1) return T1) is
begin
for I in 1 .. Count loop
Obj := Func(Obj);
Text_IO.Put_Line (Integer'Image (Obj.F1));
end loop;
end Op;
end Pack2;
with Pack1, Pack2;
procedure Test1 is
function Func1 (Obj : in Pack1.Root) return Pack1.Root is
begin
return Obj;
end Func1;
procedure Dispatch (Obj : in out Pack1.Root'Class;
Count : in Integer) is
begin
Pack1.Op (Obj, Count, Func1'access); --dispatching call
end Dispatch;
X2 : Pack2.T1;
begin
Dispatch (X2, 5);
end Test1;
Since the dispatching call doesn't make sure (at runtime) that Func1 has a
parameter type that matches Obj's tag, it looks like the call is executed, and
the overriding procedure Pack2.Op is called, and that procedure thinks it's
calling a function that returns something of type T1 when it isn't. This cannot
be good.
****************************************************************
From: Christoph Grein
Sent: Wednesday, June 24, 2009 2:37 AM
overriding
procedure Op (Obj : in out T1;
Count : in Integer;
Func : access function (Obj : in T1) return T1);
GNAT Pro 6.2.1 says: subprogram Op is not overriding.
This is accepted as overriding, but it leads of course to problems in the body:
procedure Op (Obj : in out T1;
Count: in Integer;
Func : access function (Obj: in Pack_0.Root) return Pack_0.Root);
****************************************************************
From: Adam Beneschan
Sent: Wednesday, June 24, 2009 9:40 AM
> overriding
> procedure Op (Obj : in out T1;
> Count : in Integer;
> Func : access function (Obj : in T1) return T1);
>
> GNAT Pro 6.2.1 says: subprogram Op is not overriding.
I'm not surprised. I'm guessing that T1 is derived from Root, and GNAT
implicitly declares the subprogram inherited from this one:
procedure Op (Obj : in out Root;
Count : in Integer;
Func : access function (Obj : in Root) return Root);
it replaces "Root" by "T1" in the profile, but when doing so, it fails to go
into the profile of the access-subprogram parameter, resulting in this inherited
subprogram:
procedure Op (Obj : in out T1;
Count : in Integer;
Func : access function (Obj : in Root) return Root);
So then of course it would reject the above declaration as "overriding" (and
accept this one:)
> This is accepted as overriding, but it leads of course to problems in
> the body:
>
> procedure Op (Obj : in out T1;
> Count: in Integer;
> Func : access function (Obj: in Pack_0.Root) return
> Pack_0.Root);
Our compiler has the same behavior.
However, I believe GNAT (like our compiler) is doing the wrong thing, based on the
wording of the RM. It could be that the wording of the RM doesn't reflect what is
intended, and that "systematically replacing subtypes of the profile" isn't supposed
to include subtypes in an access-subprogram-parameter profile. If that's the case,
the RM needs to be changed, and then GNAT will be right without making any changes.
But as it currently stands, unless there is some other applicable rule I missed, the
compilers are not following the RM.
****************************************************************