Version 1.5 of ai05s/ai05-0173-1.txt
!standard 3.9(7.4/2) 10-10-16 AI05-0173-1/03
!standard 3.9(12.4/2)
!class Amendment 09-10-22
!status Amendment 2012 10-06-03
!status WG9 Approved 10-10-28
!status ARG Approved 6-0-1 10-06-20
!status work item 09-10-22
!status received 09-08-10
!priority Low
!difficulty Easy
!subject Testing if tags represent abstract types
!summary
The function Is_Abstract is added to Ada.Tags.
!problem
3.9 (25.2/2) says 'An instance of Tags.Generic_Dispatching_Constructor
raises Tag_Error if The_Tag does not represent a concrete descendant of T or if
the innermost master (see 7.6.1) of this descendant is not also a master of the
instance....', but there appears to be no way of determining if a given tag
represents a non-concrete (i.e., abstract) type until an attempt is made to
create an object from it. There should be a way to test this.
!proposal
Add a function Is_Abstract to Ada.Tags.
!wording
Add after 3.9(7.4/2):
function Is_Abstract (T : Tag) return Boolean;
Add after 3.9(12.4/2):
The function Is_Abstract returns True if the type whose tag is T is
abstract, and False otherwise.
!discussion
The name of the function Is_Abstract is a bit dubious: the parameter is a tag,
but it is not the tag that is abstract; it is the type that the tag represents.
However, existing routines like Is_Descendant have the same problem, so we use
the simplest name.
Since other rules require raising of Tag_Error for tags that represent abstract
types in Generic_Dispatching_Constructor, it must be the case that this
information is present in some form in the tag, so implementing the function
should be trivial.
Note that the only properties that could be queried on a tag are those that
are invariant for the type. Specifically, privacy cannot be queried, as it depends
on the view of the type.
!example
If one implements a registration function for a factory, it makes sense for that
registration to test whether the tags would legitimately be able to create
an object. That would put the error much closer to the cause compared to waiting
until the factory is used to create an object.
procedure Register
(Factory : in out Factory_Type;
Selector : in Selector_Type;
Type_Tag : in Ada.Tags.Tag) is
begin
--
if not Ada.Tags.Is_Descendant_At_Same_Level (Type_Tag, Root_Type'Tag) then
raise Type_Error;
elsif not Ada.Tags.Is_Abstract (Type_Tag) then
raise Type_Error;
end if;
--
if Factory.Map.Contains (Selector) then
raise Selector_Error;
end if;
Factory.Map.Insert (Selector, Type_Tag);
end Register;
!corrigendum 3.9(7.4/2)
Insert after the paragraph:
function Interface_Ancestor_Tags (T : Tag) return Tag_Array;
the new paragraph:
function Is_Abstract (T : Tag) return Boolean;
!corrigendum 3.9(12.4/2)
Insert after the paragraph:
The function Interface_Ancestor_Tags returns an
array containing the tag of each interface ancestor type of the type whose tag
is T, other than T itself. The lower bound of the returned array is 1, and the
order of the returned tags is unspecified. Each tag appears in the result
exactly once. If the type whose tag is T has no interface ancestors,
a null array is returned.
the new paragraph:
The function Is_Abstract returns True if the type whose tag is T is
abstract, and False otherwise.
!ACATS test
Add an ACATS C-Test to test the new function.
!appendix
!topic Generic_Dispatching_Constructor and abstract derived types.
!reference Ada 2005 RM3.9
!from Lee Clements 09-08-09
!keywords Generic_Dispatching_Constructor abstract Tag
!discussion
Should Ada.Tags define an Is_Abstract function?
AARM 3.9 (25.2/2) says 'An instance of Tags.Generic_Dispatching_Constructor
raises Tag_Error if The_Tag does not represent a concrete descendant of T or if
the innermost master (see 7.6.1) of this descendant is not also a master of the
instance....', but there appears to be no way of determining if a given tag
represents a non-concrete (i.e., abstract) type until an attempt is made to
create an object from it.
I propose that a new function is required in Ada.Tags to allow better
determination of the allowability of a tag in a call to an instance of
generic_dispatching_constructor:
function Is_Abstract (T: in Ada.Tags.Tag) return Boolean;
Is_Abstract has the obvious sematics: returns True if the type represented by T
is abstract, False otherwise.
What is necessary to determine whether the type's master is compatible, I leave
to others to determine.
Also, is there any particular reason why the root type T is explicitly exluded
from the types allowed to be created by Generic_Dispatching_Constructor? As I
read AARM 12.5.1 (17) the actual type corresponding to a formal abstract tagged
type 'can' (I read - 'may, but doesn't have to') be abstract? Constructor is an
abstract subprogram and thus always dispatches to a concrete type (in this case
through the 'magic' of Generic_Dispatching_Construtor, which raises Tag_Error is
an invalid tag is specified). Excluding T seems to force users in some
circumstances to create a root type below that strictly needed to model the
problem, just to allow use with this facility.
Furthermore, without having thought about it enough to come up with specific
motivating examples, it would seem to me that there may be benefits to providing
additional interrogative functions (based on the definition of a tagged type in
3.9 (2/2)), e.g.:
function Is_Private (T: in Ada.Tags.Tag) return Boolean; --???
function Is_Limited (T: in Ada.Tags.Tag) return Boolean;
function Is_Interface (T: in Ada.Tags.Tag) return Boolean;
function Is_Task_Interface (T: in Ada.Tags.Tag) return Boolean;
function Is_Protected_Interface (T: in Ada.Tags.Tag) return Boolean;
function Is_Synchronized_Interface (T: in Ada.Tags.Tag) return Boolean;
function Is_Protected_Derived_From_Interface (T: in Ada.Tags.Tag) return Boolean;
function Is_Task_Derived_From_Interface (T: in Ada.Tags.Tag) return Boolean;
... again, with the 'obvious' semantics.
I'd be interested to know whether the ARG considers these worth adding to the
language specification.
Motivating example:
Inspired by the examples given in the Ada 2005 Rationale for the use of
Ada.Tags.Generic_Dispatching_Constructor (2.6 Object factory functions), I
thought I'd implement a generic factory:
generic
type Root_Type (<>) is abstract tagged limited private;
type Selector_Type (<>) is private;
type Parameters_Type (<>) is limited private;
with function "<" (Left, Right : in Selector_Type) return Boolean is <>;
with function Construct (Params : not null access Parameters_Type) return Root_Type is abstract;
package Generic_Factory is
Type_Error : exception;
Selector_Error : exception;
type Factory_Type is tagged private;
procedure Register
(Factory : in out Factory_Type;
Selector : in Selector_Type;
Type_Tag : in Ada.Tags.Tag);
function Is_Creatable
(Factory : in Factory_Type;
Type_Tag : in Ada.Tags.Tag)
return Boolean;
function Is_Registered
(Factory : in Factory_Type;
Selector : in Selector_Type)
return Boolean;
procedure Deregister
(Factory : in out Factory_Type;
Selector : in Selector_Type);
function Produce
(Factory : in Factory_Type;
Selector : in Selector_Type;
Params_Ptr : not null access Parameters_Type)
return Root_Type'Class;
function Create
(Factory : in Factory_Type;
Type_Tag : in Ada.Tags.Tag;
Params_Ptr : not null access Parameters_Type)
return Root_Type'Class;
private
package Selector_Maps
is new Ada.Containers.Indefinite_Ordered_Maps
(Selector_Type, Ada.Tags.Tag, "<", Ada.Tags."=");
type Factory_Type
is new Ada.Finalization.Controlled
with record
Map : Selector_Maps.Map;
end record;
end Generic_Factory;
package body Generic_Factory is
function Dispatching_Constructor
is new Ada.Tags.Generic_Dispatching_Constructor
(Root_Type, Parameters_Type, Construct);
procedure Register
(Factory : in out Factory_Type;
Selector : in Selector_Type;
Type_Tag : in Ada.Tags.Tag) is
begin
if (not Ada.Tags.Is_Descendant_At_Same_Level (Type_Tag, Root_Type'Tag)) then
raise Type_Error;
end if;
if (Factory.Map.Contains (Selector)) then
raise Selector_Error;
end if;
Factory.Map.Insert (Selector, Type_Tag);
end Register;
function Is_Creatable
(Factory : in Factory_Type;
Type_Tag : in Ada.Tags.Tag)
return Boolean is
begin
if (Ada.Tags.Is_Descendant_At_Same_Level (Type_Tag, Root_Type'Tag)) then
return True;
else
return False;
end if;
end Is_Creatable;
function Is_Registered
(Factory : in Factory_Type;
Selector : in Selector_Type)
return Boolean is
begin
return Factory.Map.Contains (Selector);
end Is_Registered;
procedure Deregister
(Factory : in out Factory_Type;
Selector : in Selector_Type) is
begin
if (Factory.Map.Contains (Selector)) then
Factory.Map.Delete (Selector);
end if;
end Deregister;
function Produce
(Factory : in Factory_Type;
Selector : in Selector_Type;
Params_Ptr : not null access Parameters_Type)
return Root_Type'Class is
begin
if (Factory.Map.Contains (Selector)) then
return Dispatching_Constructor (Factory.Map.Element (Selector), Params_Ptr);
else
raise Selector_Error;
end if;
end Produce;
function Create
(Factory : in Factory_Type;
Type_Tag : in Ada.Tags.Tag;
Params_Ptr : not null access Parameters_Type)
return Root_Type'Class is
begin
if (Ada.Tags.Is_Descendant_At_Same_Level (Type_Tag, Root_Type'Tag)) then
return Dispatching_Constructor (Type_Tag, Params_Ptr);
else
raise Type_Error;
end if;
end Create;
end Generic_Factory;
Unfortunately the use of Is_Descendant_At_Same_Level in Register, Is_Creatable
and Create does not cover all issues. Consider:
type Params is null record;
type Root is tagged null record;
function Construct (Params_Ptr : not null access Params) return Root;
type Abstract_Derived is abstract new Root with null record;
function Construct (Params_Ptr : not null access Params) return Abstract_Dervied;
package Test_Factory is new Generic_Factory
(Root, Integer, Params, Standard."<", Construct);
Dummy: aliased Params;
F : Test_Factory.Factory_Type;
F.Register (1, Abstract_Derived'Tag); -- Allowed;
F.Is_Creatable (Abstract_Derived'Tag); -- returns True;
F.Produce(1, Dummy'access); -- Tag_Error raised by Generic_Dispatching_Constructor.
F.Create (Abstract_Derived'Tag, Dummy'access); -- Tag_Error raised by Generic_Dispatching_Constructor.
Attempting to use an abstract type in the way shown in the code above is
obviously wrong and the program is in error, but It would seem to me that it
would be better to trap the error at the earliest opportunity, hence the
attempts at checks in Register and Is_Creatable. By allowing the registration
of the type, a latent failure is introduced that could be encountered at less
well defined times (compared to registration) in program execution.
I would think that the Generic_Factory package, or something like it, is likely
to find itself implemented many times by different people, especially as the
Rationale seems to suggest that this is the correct type of solution to certain
classes of problems.
****************************************************************
From: Adam Beneschan
Sent: Tuesday, August 11, 2009 12:09 PM
> function Is_Abstract (T: in Ada.Tags.Tag) return Boolean;
>
> Is_Abstract has the obvious sematics: returns True if the type
> represented by T is abstract, False otherwise.
Seems simple enough to me. Since no object can have a 'Tag whose type is
abstract, in practice the only way you'd get such a tag would be from a call to
Internal_Tag or Descendant_Tag (which take string parameters), but I can
envision how that might be possible (due to an input error, perhaps) and how it
might be desirable to provide a way to avoid an exception. Or by explicitly
taking the 'Tag of an abstract type, as you did in your example, but this is a
clear (and silly) programming error. If that were the only motivation for
adding this check, it probably wouldn't be worth changing the language (even a
seemingly simple change like this, which might not be all that simple for some
implementations). But to me, the possibility of errors in tag strings in input
files is a better motivation.
> What is necessary to determine whether the type's master is
> compatible, I leave to others to determine.
Me too. :-)
> Also, is there any particular reason why the root type T is explicitly
> exluded from the types allowed to be created by
> Generic_Dispatching_Constructor?
It isn't, at least as far as I can tell. Where does it appear to you that T is
explicitly excluded? (If you're confused by the term "descendant", note that T
is a descendant of T: see 3.4.1(10). If there's something else in 3.9 that says
that the root type is excluded, I sure can't find it.)
> As I read AARM 12.5.1 (17) the actual
> type corresponding to a formal abstract tagged type 'can' (I read -
> 'may, but doesn't have to') be abstract? Constructor is an abstract
> subprogram and thus always dispatches to a concrete type (in this case
> through the 'magic' of Generic_Dispatching_Construtor, which raises
> Tag_Error is an invalid tag is specified). Excluding T seems to force
> users in some circumstances to create a root type below that strictly
> needed to model the problem, just to allow use with this facility.
>
> Furthermore, without having thought about it enough to come up with
> specific motivating examples, it would seem to me that there may be
> benefits to providing additional interrogative functions (based on the
> definition of a tagged type in 3.9 (2/2)), e.g.:
>
> function Is_Private (T: in Ada.Tags.Tag) return Boolean; --???
> function Is_Limited (T: in Ada.Tags.Tag) return Boolean; function
> Is_Interface (T: in Ada.Tags.Tag) return Boolean; function
> Is_Task_Interface (T: in Ada.Tags.Tag) return Boolean; function
> Is_Protected_Interface (T: in Ada.Tags.Tag) return Boolean; function
> Is_Synchronized_Interface (T: in Ada.Tags.Tag) return Boolean;
> function Is_Protected_Derived_From_Interface (T: in Ada.Tags.Tag)
> return Boolean; function Is_Task_Derived_From_Interface (T: in
> Ada.Tags.Tag) return Boolean; ... again, with the 'obvious' semantics.
I'd definitely be opposed to Is_Private. Whether a type is private or not (more
accurately, whether the full or partial view of a type is visible) is more a
property of where you are in the program in relation to the type's declaration,
rather than a property of the type itself. I cannot see any legitimate use of a
function like this.
I can't see any legitimate use for Is_Limited, either; whether a type is limited
or not *statically* affects what operations can be performed on objects of the
type (or its associated class-wide type), particularly assignment, but I don't
think there's any case where this is checked at runtime. The only case where it
could possibly matter is if you have a limited interface type Lim_Int;
descendants of that type are nonlimited. But supposing you had an object X of
type Lim_Int'Class, and you could determine from X'Tag that the concrete type is
nonlimited. What would that gain you? You still wouldn't be able to do an
assignment, or do anything else that you could do with nonlimited types.
For Is_Interface: if Is_Abstract is implemented, I don't see any particular use
for Is_Interface. If a Tag is the tag of an abstract type, you can't use it for
much; in fact, it probably indicates an input error of some sort (like a bad
name passed to Descendant_Tag). Given that, I don't see any advantage in
distinguishing useless tags of interface types from useless tags of
non-interface abstract types.
I don't know much about task/protected interfaces so I won't say anything about
the other suggested functions.
****************************************************************
Questions? Ask the ACAA Technical Agent