!standard A.18(0/2) 07-10-24 AI05-0069-1/01 !class Amendment 07-10-24 !status work item 07-10-24 !status received 07-10-02 !priority Medium !difficulty Hard !subject Singleton container !summary (See proposal.) !problem It not possible in Ada to declare an object of an indefinite type that can hold any value of that type -- the language requires an initialization value and for the object to be constrained by that value. Worse, components of indefinite types are illegal. However, it is often useful to have objects and components of indefinite types. One example is the classwide type at the root of a tree of types. It's not uncommon to have operations that produce values of such a type, and to want to store those values as components of a type. This can be done using access types and explicit storage management. However, this is error-prone. Moreover, we've defined containers to eliminate the need for explicit memory management of common data structures. Thus it is appropriate to have have a container to handle this operation. !proposal Define a singleton container as follows: generic type Element_Type (<>) is private; package Ada.Containers.Holders is -- This package provides a "holder" of a definite type that contains -- a single value of an indefinite type. -- This allows one to effectively declare an uninitialized variable -- or component of an indefinite type. type Holder is tagged private; function To_Holder (New_Item : Element_Type) return Holder; -- Returns a non-empty holder containing an element initialized to New_Item. function Is_Empty (Container : Holder) return Boolean; -- Returns True if the holder is empty, and -- False if it contains an element. procedure Clear (Container : Holder); -- Removes the element from Container. function Element (Container : Holder) return Element_Type; -- If Container is empty, Constraint_Error is propagated. -- Otherwise, returns the element stored in Container. procedure Replace_Element (Container : in out Holder; New_Item : in Element_Type); -- Replace_Element assigns the value New_Item into Container, replacing -- any preexisting content of Container. Container is not empty -- after a successful call to Replace_Element. procedure Query_Element (Container : in Holder; Process : not null access procedure (Element : in Element_Type)); -- If Container is empty, Constraint_Error is propagated. -- Otherwise, Query_Element calls Process.all with the contained element as -- the argument. Program_Error is raised if Process.all tampers with the elements -- of Container. Any exception raised by Process.all is propagated. procedure Update_Element (Container : in Holder; Process : not null access procedure (Element : in out Element_Type)); -- If Container is empty, Constraint_Error is propagated. -- Otherwise, Query_Element calls Process.all with the contained element as -- the argument. Program_Error is raised if Process.all tampers with the elements -- of Container. Any exception raised by Process.all is propagated. procedure Move (Target : in out Holder; Source : in out Holder); -- If Target denotes the same object as Source, then Move has no effect. -- Otherwise, the element contained by Source (if any) is removed from Source -- and inserted into Target, replacing any preexisting content. Source is empty -- after a successful call to Move. private -- ... Not specified by the language. end Ada.Containers.Holders; !wording ** TBD ** (Most of the needed wording is given as comments above; the definition of tampering is given below.) A subprogram is said to tamper with elements of a holder object H if: * It clears the element contained by H, that is, it calls the Clear procedure with H as a parameter; * It replaces the element contained by H, that is, it calls the Replace_Element procedure with H as a parameter; * It calls the Move procedure with H as a parameter; * It finalizes H. !discussion Note that it isn't necessary to define a definite version of this container; that would just be a complicated way to write "A : Element_Type". !example ** TBD ** !ACATS test ACATS C-Test(s) are necessary for this package. !appendix From: Tucker Taft Date: Tuesday, October 2, 2007 8:06 PM As part of my homework for ASIS SI-24, the semantic model, I am trying to work out a version using tagged types rather than untagged discriminated types. One thing we agreed would be necessary is some kind of singleton container that can be used to store class-wide values returned from various functions. I have constructed such a container, which I chose to call a "holder." I have attached the proposed spec and body, which I tried to make similar to indefinite_vectors, etc. There seems no particular value for a holder of "definite" elements, so I didn't bother to define both a "definite" version and an "indefinite" version. This one handles either. As you may remember, the key problem I saw with using tagged types for the ASIS semantic model is that the operations will often be designed to return a class-wide object, but then the caller may want to be able to store this into a preexisting local variable, or the field of some record. That doesn't work for a class-wide type. However, a "holder" can be used for this purpose. Comments welcome. My intent is to define a Holder type for each of the main kinds of views/entities. The user could of course declare more if they wanted finer control. --- with Ada.Streams; with Ada.Finalization; generic type Element_Type (<>) is private; package Ada_Containers_Holders is -- This package provides a "holder" of a definite type that contains -- a single value of an indefinite type. -- This allows one to effectively declare an uninitialized variable -- of an indefinite type. type Holder is tagged private; procedure Replace_Contents( Container : in out Holder; Contents : Element_Type); -- This assigns Contents into the Container, replacing -- any preexisting content of Container. generic with procedure Update(Contents : in out Element_Type); procedure Update_Contents(Container : in out Holder); -- This calls the given procedure with the contents, if -- any, of the Container. It is a no-op if the -- container is empty. procedure Move(Target : in out Holder; Source : in out Holder); -- This moves the contents of Source into Target, leaving -- Source empty, and replacing any preexisting content of Target. -- This is a no-op if Target and Source are the same object. function Contents(Container : Holder) return Element_Type; -- This returns the element, if any, stored in the holder. -- If no element is stored in the holder, Constraint_Error is raised. function Is_Empty(Container : Holder) return Boolean; -- This returns True if the holder is empty, and -- False if it contains an element. private type Element_Ptr is access Element_Type; type Holder is new Ada.Finalization.Controlled with record Contents : Element_Ptr := null; end record; procedure Adjust(Container : in out Holder); -- This makes a copy of Container.Contents.all -- if Container.Contents non-null procedure Finalize(Container : in out Holder); -- This calls Unchecked_Deallocation on Container.Contents function "="(Left, Right : Holder) return Boolean; -- Make sure that equality works -- Provide stream attributes use Ada.Streams; procedure Write(Stream : access Root_Stream_Type'Class; Container : in Holder); for Holder'Write use Write; procedure Read(Stream : access Root_Stream_Type'Class; Container : out Holder); for Holder'Read use Read; end Ada_Containers_Holders; --- with Ada.Unchecked_Deallocation; package body Ada_Containers_Holders is -- This package provides a definite type that contains -- a single value of an indefinite type. ------------- Local and private declarations -------------- procedure Free is new Ada.Unchecked_Deallocation(Element_Type, Element_Ptr); procedure Adjust(Container : in out Holder) is -- This makes a copy of Container.Contents.all if -- Container.Contents non-null begin if Container.Contents /= null then Container.Contents := new Element_Type'(Container.Contents.all); end if; end Adjust; procedure Finalize(Container : in out Holder) is -- This calls Unchecked_Deallocation on Container.Contents begin Free(Container.Contents); end Finalize; function "="(Left, Right : Holder) return Boolean is -- Make sure that equality works begin return Left.Contents = Right.Contents or else (Left.Contents /= null and then Right.Contents /= null and then Left.Contents.all = Right.Contents.all); end "="; -- Provide stream attributes procedure Write(Stream : access Root_Stream_Type'Class; Container : in Holder) is -- Write a boolean indicating whether Holder contains -- an element, and if True, write out the element. Is_Present : constant Boolean := Container.Contents /= null; begin Boolean'Write(Stream, Is_Present); if Is_Present then Element_Type'Output(Stream, Container.Contents.all); end if; end Write; procedure Read(Stream : access Root_Stream_Type'Class; Container : out Holder) is -- Read a boolean, and then if true, read the element Is_Present : constant Boolean := Boolean'Input(Stream); begin Free(Container.Contents); if Is_Present then Container.Contents := new Element_Type'(Element_Type'Input(Stream)); end if; end Read; ------------ Visible Subprograms --------------- procedure Replace_Contents( Container : in out Holder; Contents : Element_Type) is -- This assigns Contents into the Container, replacing -- any preexisting content of Container. begin Free(Container.Contents); Container.Contents := new Element_Type'(Contents); end Replace_Contents; procedure Update_Contents(Container : in out Holder) is -- This calls the given procedure with the contents, if -- any, of the Container. It is a no-op if the -- container is empty. begin if Container.Contents /= null then Update(Container.Contents.all); end if; end Update_Contents; procedure Move(Target : in out Holder; Source : in out Holder) is -- This moves the contents of Source into Target, leaving -- Source empty, and replacing any preexisting content of Target. -- This is a no-op if Target and Source are the same object. begin if Target.Contents /= Source.Contents then Free(Target.Contents); Target.Contents := Source.Contents; Source.Contents := null; end if; -- NOTE: If Target.Contents = Source.Contents then -- either both are null, or Target and Source are -- the same object (or so I claim). end Move; function Contents(Container : Holder) return Element_Type is -- This returns the element, if any, stored in the holder. -- If no element is stored in the holder, Constraint_Error is raised. begin return Container.Contents.all; end Contents; function Is_Empty(Container : Holder) return Boolean is -- This returns True if the holder is empty, and -- False if it contains an element. begin return Container.Contents = null; end Is_Empty; end Ada_Containers_Holders; **************************************************************** From: Pascal Leroy Date: Wednesday, October 3, 2007 12:53 AM > Comments welcome. My intent is to define a Holder type for > each of the main kinds of views/entities. > The user could of course declare more if they wanted finer control. It would seem to make sense to have a procedure Clear to empty the holder. To some extent, Clear and Is_Empty go together. **************************************************************** From: Jean-Pierre Rosen Date: Wednesday, October 3, 2007 2:33 AM > As part of my homework for ASIS SI-24, the semantic > model, I am trying to work out a version using tagged > types rather than untagged discriminated types. > One thing we agreed would be necessary is some kind > of singleton container that can be used to store > class-wide values returned from various functions. > I have constructed such a container, which I chose > to call a "holder." I have attached the proposed > spec and body, which I tried to make similar to > indefinite_vectors, etc. There seems no particular > value for a holder of "definite" elements, so I > didn't bother to define both a "definite" version > and an "indefinite" version. This one handles > either. Looks nice and useful. So useful actually that it has nothing special to do with ASIS. Even if we take the opportunity to put this in ASIS05, is there a problem in naming it ada.containers.holders ? This could encourage implementations (even if they do not provide ASIS) to provide this one as part of the standard library. **************************************************************** From: Tucker Taft Date: Wednesday, October 3, 2007 12:24 PM It was my hope that it could be named Ada.Containers.Holders in the expectation that it would be the first of various additions to the Containers hierarchy. I think I'll add something like "Clear," but call it "Set_Empty" to make its relationship to "Is_Empty" crystal "clear". ;-) I was also considering changing the name of the formal type to "Contents_Type" from "Element_Type," to be consistent with the use of the function name "Contents" rather than "Element". **************************************************************** From: Tucker Taft Date: Wednesday, October 3, 2007 12:41 PM Actually, I guess "Clear" is used consistently to mean Set_Empty in the other Containers, so I should probably use that here as well. **************************************************************** From: Pascal Leroy Date: Wednesday, October 3, 2007 2:05 AM > Actually, I guess "Clear" is used consistently to mean > Set_Empty in the other Containers, so I should probably use > that here as well. I think consistency with the other containers would be a good thing. So I prefer Clear to Set_Empty and Element_Type to Content_Type. No point in changing names unless the semantics are clearly distinct. **************************************************************** From: Randy Brukardt Date: Wednesday, October 24, 2007 7:33 PM For the record, this goes for all of the names (and wording!) in this container: Update_Element, not Update_Contents; Query_Element needs to exist; Element, not Contents, to retrieve the element; Replace_Element, not Replace_Contents; and, finally, the operations need to be declared in the same order as in the other containers (otherwise, John will beat me up during editorial review). I'll make all of these changes in the AI, so we don't (I hope) have to argue about that for hours. **************************************************************** From: Randy Brukardt Date: Wednesday, October 24, 2007 8:06 PM Having written that, I found more: Function wording starts with "Returns", not "This returns"; Lots of other wording issues (copy the Vector container wording as closely as possible); The 2nd parameter name for Replace_Element should be New_Item; Query_Element and Update_Element should take an access-to-subprogram, not be generic. Possibly controversial: Query_Element and Update_Element should raise Constraint_Error if the holder is empty to be consistent with Element (Tucker called it "Contents"). For existing containers, this is a Bounded_Error in all three cases, but none of the error possibilities is no-op (which is what Tucker used for Update_Element). It seems to me that defining this as a no-op is dangerous, because the update will not happen without any indication as to why; the possibility of using the existing (random) element only makes sense for definite containers. We also need the tampering and propagation rules. We should have a To_Holder function: function To_Holder (New_Item : Element_Type) return Holder; -- Returns a non-empty holder containing an element initialized to New_Item. This is similar to the To_Vector function of a vector. (But this one is not as common as the other operations.) Yes, I looked at every routine in Vectors to see if it was relevant. That's probably the only way to do this. Amazing how many details there are even in such a simple container... ****************************************************************