Version 1.1 of acs/ac-00281.txt
!standard 4.6(50) 16-08-18 AC95-00281/00
!class confirmation 16-08-18
!status received no action 16-08-18
!status received 16-06-28
!subject Downward conversions
!summary
!appendix
From: Jeff Cousins
Sent: Tuesday, June 28, 2016 11:27 AM
Using CodePeer has (indirectly) revealed that a few of our programmers seem to
have circumvented the rule against converting tagged objects away from the root
by converting pointers instead. GNAT doesn't give any warning. Is this legal?
Shouldn't it at least require an Unchecked_Conversion of some sort?
package AFU is
type Root_Facade_Type is tagged limited record
Error_Code : Integer;
end record;
end AFU;
with AFU;
package MCI is
type Facade_Handle_Type is limited private;
procedure Define_Construct
(Fhandle : in out Facade_Handle_Type); private
type Facade_Handle_Type is access all AFU.Root_Facade_Type'Class; end MCI;
package body MCI is
type Construct_Message_Data_T is
record
Valid_Operation : Boolean;
end record;
type Constructs_IF_Attr_Type is new AFU.Root_Facade_Type with
record
Message_In_Progress : Construct_Message_Data_T;
end record;
type Constructs_IF_Attr_Ptr_Type is access all Constructs_IF_Attr_Type;
procedure Define_Construct
(Fhandle : in out Facade_Handle_Type) is
-- Downward conversion of tagged object not allowed
Root_Facade : AFU.Root_Facade_Type := (Error_Code => 99);
Constructs_IF_Attr : constant Constructs_IF_Attr_Type :=
Constructs_IF_Attr_Type (Root_Facade);
-- But downward conversion of pointer to tagged object is allowed
Constructs_IF_Attr_Ptr : constant Constructs_IF_Attr_Ptr_Type :=
Constructs_IF_Attr_Ptr_Type (FHandle);
Message_Data : Construct_Message_Data_T
renames Constructs_IF_Attr_Ptr.Message_In_Progress;
begin
if Message_Data.Valid_Operation then
null;
end if;
end Define_Construct;
end MCI;
with MCI;
procedure Downward_Conversion_Test is
MEZ_Construct_IF_Handle : MCI.Facade_Handle_Type;
begin
MCI.Define_Construct (Fhandle => MEZ_Construct_IF_Handle);
end Downward_Conversion_Test;
***************************************************************
From: Tucker Taft
Sent: Tuesday, June 28, 2016 12:01 PM
So long as the access type is access-to-classwide (which is the case for
Facade_Handle_Type), conversion toward the leaves is permitted. See good old RM
4.6, paragraph 24.13 and then 23/2. Conversion toward the leaves is permitted
on tagged class-wide types directly as well.
***************************************************************
From: Steve Baird
Sent: Tuesday, June 28, 2016 1:07 PM
Tuck pointed out that we don't allow a downward tagged-to-tagged conversion
unless the operand's type is class-wide, and that an analogous rule applies to
conversion to a general access-to-tagged type.
I'll add that the consistency in the treatment of the two forms of conversion
extends to the associated runtime checks.
4.6(50) says:
"any checks associated with evaluating a conversion to the target
designated subtype are performed" .
***************************************************************
From: Randy Brukardt
Sent: Tuesday, June 28, 2016 2:18 PM
Right. The case in Jeff's example will always raise Constraint_Error. That is,
it's not unsafe, it's just not going to work as expected.
This sort of class-wide (root) to specific (leaf) conversion is commonly used; I
had to use many of them in the Claw Builder. It's the only way to get at
specific operations of the object if dispatching isn't appropriate. But it's
important to verify that the object is in fact of the right type before doing
such a conversion -- I suspect that is the real problem that Codepeer is
diagnosing.
I.e.
type Any_Window_Access is access all Root_Window_Type'Class;
type Checkbox is new Root_Window_Type with private;
function Is_Checked (C : in Checkbox) return Boolean;
Foo : Any_Window_Access := ...;
In order to find out if Foo is checked, one ought to use code something
like:
if Foo.all in Checkbox then
if Is_Checked (Checkbox (Foo.all)) then
...
-- else not a checkbox
end if;
And for an access-to-checkbox type:
type Checkbox_Access is access all Checkbox;
if Foo.all in Checkbox then
if Is_Checked (Checkbox_Access (Foo).all) then
...
-- else not a checkbox
end if;
(I personally don't see any reason to use access-to-specific in OOP code - I
think it should be discouraged - but of course YMMV.)
***************************************************************
From: Jeff Cousins
Sent: Tuesday, June 28, 2016 4:54 PM
Many thanks for all your replies, I thought some of you clever people would have
answers!
I'm surprised that our code isn't failing due to a Tag_Check - presumably the
Constraint_Error that Randy mentions - as the callers of Define_Construct can't
have any knowledge of the derived type declared within the package body.
***************************************************************
Questions? Ask the ACAA Technical Agent