Version 1.3 of ais/ai-10302.txt

Unformatted version of ais/ai-10302.txt version 1.3
Other versions for file ais/ai-10302.txt

!standard A.16          03-09-29 AI95-00302-02/02
!class amendment 03-09-25
!status work item 03-09-25
!status received 03-09-25
!priority Medium
!difficulty Hard
!subject Container library
!summary
The following API describes a standard container library for Ada. The library comprises sequence containers (vectors and linked-lists), for inserting elements at specified positions, and associative containers (sets and maps), which position elements in order by key. The library is general, flexible, and efficient, and its design has been guided by the philosophy that a library should stay out of the programmer's way.
!problem
If is often the case that the solution to a programming problem requires a collection of elements. The Ada language of course provides a built-in array type, but typical problems often require a data structure with better time behavior or more sophisticated semantics. Even if you can get by with using an array, any non-trivial array manipulation quickly becomes unwieldy, and hence error-prone.
An array is also not general enough in that it only provides a mapping from a discrete index to the element type. The developer typically needs a container that can map an element to any arbitrary type. The key type often needs to be a string, but of course this cannot be used as an array index subtype.
With no general-purpose standard container library, a developer is left to craft his own container type. The data structure invariably chosen is some kind of linked-list, because it's relatively simple. However, manipulation of raw access types tends to be error-prone, and is a source of memory leaks. A linked-list also does not perform well as a general-purpose set or map when the number of elements is large.
One argument for having a standard library is that it's nice to be able to use a language right out-of-the-box. If a developer has to leave the language to solve common problems then perhaps that is a sign the language doesn't provide enough support for the developer. Other languages with which Ada competes, such as C++ or Java, come with rich standard container libraries.
Many existing container libraries are either badly designed or difficult to use. One issue is that it should be simple and easy to instantiate a component. If it requires more than a single instantiation to create a useable container, then it's too much pain and no developer will bother. Or if he does bother his experience will be unpleasant. Programming is work, but it should be fun work, not drudgery.
Libraries often have the problem that they are not sufficiently general, or simply not intended as industrial-strength libraries. The library designer can't possibly predict all the myriad uses to which his library will be put, and so the library must be general and flexible enough to suit unimagined uses.
Another problem is that these libraries often don't scale well to large numbers of elements. Their behavior is neither predictable nor is it even specified. They are often both time and space inefficient for various other reasons. In real-time programs especially, it's important to have predictable run-time behavior. An industrial-strength, general-purpose standard container library must provide containers that perform better than linear time complexity. Searches in particular need to be fast.
!proposal
This library API proposal is modeled on the STL, an algorithms and data structure library popularized by Alexander Stepanov, and included in the C++ standard library.
We can broadly characterize containers as being either "sequence containers" or "associative containers." All sequence containers allow insertion and deletion at any position in the container, but each one optimizes differently for insertions at certain positions. Associative containers associate elements with a key, which defines how elements are ordered in the container.
A vector is a sequence container optimized for insertion at the back end. It of course allows insertion at any position, but as you move toward the front the cost of vector insertion approaches linear time complexity. A vector supports constant-time random access of elements.
A linked-list is a sequence container that provides constant-time insertion and deletion at any position, but does not provide random access. A doubly-linked list has nodes with both next and previous pointers, and thus supports both forward and reverse iteration.
Vectors specify positions via a discrete index subtype. Linked-lists and all the associative containers specify positions using an iterator, which is similar to an access type in that designates a node of internal storage. Note that unlike a discrete index, an iterator always designates the node that contains an element; it never designates an element directly. (There's a special selector operation for that.)
The associative containers order their elements by key. A map has an explicit key type, but with a set the key is implicit in the element itself. A multiset container allows elements to have non-unique values.
The map associative containers scatter keys in the container according to a hash function. The hash table is automatically resized when the number of elements equals the number of buckets. Insertions and searches therefore have a time complexity of O(1) on average.
The set and multiset associative containers maintain elements in sort order, using a balanced (red-black) tree. Insertions and searches in a sorted associative container therefore have O(log N) time complexity even in the worst case; under some circumstances insertion is amortized O(1) time complexity.
For the map containers, there are forms that use types String and Wide_String specifically as the key. Using a string as the key of a map container is so common in nearly every application that the library provides this directly. This is an example of my philosophy that a library should be designed around the common case. If you need to do it often, then the library should make it easy to do it.
There are also library-level subprograms for performing case-insensitive string comparisons, and for returning the hash value of a both case-sensitive and case-insensitive strings.
This API is based on an existing container library called Charles. The source code itself and a couple of papers about the design of Charles are at my home page:
<http://home.earthlink.net/~matthewjheaney/charles/index.html>
Following the API proper, this proposal concludes with an examples section that illustrates the kinds of problems the library solves, and the most effective way to use the library to solve them.
!wording
A.X Containers
This clause presents the specifications of the package Containers and several child packages, which provide facilities for storing collections of elements.
A.X.1 The Package Containers
The package Containers is the root of the containers subsystem.
Static Semantics
The library package Containers has the following declaration:
package Ada.Containers is pragma Pure; end Ada.Containers;
A.X.2 The Package Containers.Vectors
The package Containers.Vectors is the root of the vector containers subsystem.
Static Semantics
The library package Containers.Vectors has the following declaration:
package Ada.Containers.Vectors is pragma Pure; end Ada.Containers.Vectors;
A.X.3 The Package Containers.Vectors.Unbounded
The language-defined package Containers.Vectors.Unbounded provides a private type Container_Type and a set of operations. A vector container allows insertion and deletion at any position, but it is specifically optimized for insertion and deletion at the back end of the container. A vector container also provides random access to its elements.
A vector container object manages an unconstrained internal array, which expands as necessary as items are inserted. The "size" of a vector corresponds to the total length of the internal array, and the "length" of a vector corresponds to the number "active" elements in the internal array.
Static Semantics
The library package Containers.Vectors.Unbounded has the following declaration:
generic
type Index_Type is (<>);
type Element_Type is private;
with function "=" (Left, Right : Element_Type) return Boolean is <>;
package Ada.Containers.Vectors.Unbounded is
pragma Preelaborate;
subtype Index_Subtype is Index_Type;
type Container_Type is private;
function "=" (Left, Right : Container_Type) return Boolean;
function Length (Container : Container_Type) return Natural;
function Is_Empty (Container : Container_Type) return Boolean;
procedure Clear (Container : in out Container_Type);
procedure Swap (Left, Right : in out Container_Type);
procedure Append (Container : in out Container_Type; New_Item : in Element_Type);
procedure Insert (Container : in out Container_Type; Before : in Index_Type'Base; New_Item : in Element_Type);
procedure Insert (Container : in out Container_Type; Before : in Index_Type'Base);
procedure Insert_N (Container : in out Container_Type; Before : in Index_Type'Base; Count : in Natural; New_Item : in Element_Type);
procedure Insert_N (Container : in out Container_Type; Before : in Index_Type'Base; Count : in Natural);
procedure Delete (Container : in out Container_Type; Index : in Index_Type'Base);
procedure Delete_Last (Container : in out Container_Type);
procedure Delete_N (Container : in out Container_Type; First : in Index_Type'Base; Count : in Natural);
function Size (Container : Container_Type) return Natural;
procedure Resize (Container : in out Container_Type; Size : in Natural);
function Front (Container : Container_Type) return Index_Type'Base;
function First (Container : Container_Type) return Index_Type;
function First_Element (Container : Container_Type) return Element_Type;
function Last (Container : Container_Type) return Index_Type'Base;
function Last_Element (Container : Container_Type) return Element_Type;
function Back (Container : Container_Type) return Index_Type'Base;
function Element (Container : Container_Type; Index : Index_Type'Base) return Element_Type;
generic type Element_Access is access all Element_Type; function Generic_Element (Container : Container_Type; Index : Index_Type'Base) return Element_Access;
procedure Replace_Element (Container : in Container_Type; Index : in Index_Type'Base; By : in Element_Type);
private
... -- not specified by the language
end Ada.Containers.Vectors.Unbounded;
If an object of type Container_Type is not otherwise initialized, it will initialized with a size (internal array length) of 0.
function "=" (Left, Right : Container_Type) return Boolean;
If Left denotes the same object as Right, then this function returns True. Otherwise if Left and Right have different lengths, then this function returns False. Otherwise, it compares each element in Left to the corresponding element in Right using the generic formal equality operator. If element equality returns False, then this function returns False; otherwise, this function returns True. Any exceptions raised during evaluation of element equality are propagated.
function Length (Container : Container_Type) return Natural;
The Length function returns the number of active elements in Container.
function Is_Empty (Container : Container_Type) return Boolean;
Equivalent to Length (Container) = 0.
procedure Clear (Container : in out Container_Type);
Sets the length of Container to 0. Its size does not change.
procedure Swap (Left, Right : in out Container_Type);
Exchanges the internal array and element count of Left and Right.
procedure Append
(Container : in out Container_Type;
New_Item : in Element_Type);
Equivalent to Insert (Container, Back (Container), New_Item).
procedure Insert
(Container : in out Container_Type;
Before : in Index_Type'Base; New_Item : in Element_Type);
Equivalent to Insert_N (Container, Before, 1, New_Item).
procedure Insert
(Container : in out Container_Type;
Before : in Index_Type'Base);
Equivalent to Insert_N (Container, Before, 1).
procedure Insert_N
(Container : in out Container_Type;
Before : in Index_Type'Base; Count : in Natural; New_Item : in Element_Type);
If Count has the value 0, then Insert_N does nothing. Otherwise, it calculates the new length as the sum of the current length and the value of Count; if the new value of Last would be greater than Index_Type'Last then Constraint_Error is propagated. If the value of Before is not in the range First (Container) .. Back (Container), then Constraint_Error is propagated.
If the current vector size is greater than or equal to the new length, then Insert_N slides the elements in the range Before .. Last (Container) up by Count positions, and it assigns the value New_Item to the elements in the Count positions starting at Before. Any exceptions raised during the assignment are propagated.
Otherwise if the current vector size is less than the new length, Insert_N allocates a new internal array, whose elements are initialized from New_Item and the active elements in Container. Any exceptions are propagated and Container is not modified. Insert_N then replaces the old internal array by the new array, and then deallocates the old array. Any exceptions raised during deallocation are propagated.
procedure Insert_N
(Container : in out Container_Type;
Before : in Index_Type'Base; Count : in Natural);
Equivalent to Insert_N (Container, Before, Count, New_Item), with the difference that the elements in the Count positions starting at Before are not assigned.
procedure Delete
(Container : in out Container_Type;
Index : in Index_Type'Base);
If Index is outside the range First (Container) .. Last (Container), the operation has no effect. Otherwise, Delete slides down by one position the elements in the range Index_Type'Succ (Index) .. Last (Container). Any exceptions raised during element assignment are propagated.
procedure Delete_Last (Container : in out Container_Type);
Equivalent to Delete (Container, Last (Container)).
procedure Delete_N
(Container : in out Container_Type;
First : in Index_Type'Base; Count : in Natural);
If Count is 0, the operation has no effect. If First does not specify a value in the range First (Container) .. Last (Container), then Constraint_Error is propagated. Otherwise Delete_N slides the active elements (if any) starting First plus Count down to First. Any exceptions raised during element assignment are propagated.
function Size (Container : Container_Type) return Natural;
Returns the length of the internal array.
procedure Resize
(Container : in out Container_Type;
Size : in Natural);
If Size (Container) is equal to or greater than Size, the operation does nothing. Otherwise Resize allocates a new internal array whose length is at least the value Size, and initializes it from the active elements in Container. Any exceptions raised during the allocation are propagated, and Container is not modified. Resize then replaces the old array by the new array, and then deallocates the old array. Any exceptions raised during deallocation are propagated.
function Front (Container : Container_Type) return Index_Type'Base;
Returns the value Index_Type'Pred (Index_Type'First).
function First (Container : Container_Type) return Index_Type;
Returns the value Index_Type'First.
function First_Element (Container : Container_Type) return Element_Type;
Equivalent to Element (Container, First (Container)).
function Last (Container : Container_Type) return Index_Type'Base;
Returns the position of the last element in Container.
function Last_Element (Container : Container_Type) return Element_Type;
Equivalent to Element (Container, Last (Container)).
function Back (Container : Container_Type) return Index_Type'Base;
Equivalent to Index_Type'Succ (Last (Container)).
function Element (Container : Container_Type; Index : Index_Type'Base) return Element_Type;
If Index is not in the range First (Container) .. Last (Container), then Constraint_Error is propagated. Otherwise this function returns the element at position Index.
generic type Element_Access is access all Element_Type; function Generic_Element (Container : Container_Type; Index : Index_Type'Base) return Element_Access;
If Index is not in the range First (Container) .. Last (Container), then Constraint_Error is propagated. Otherwise this function returns an access value that designates a variable view of the element at Index.
procedure Replace_Element (Container : in Container_Type; Index : in Index_Type'Base; By : in Element_Type);
if Index does not specify a value in the range First (Container) .. Last (Container), then Constraint_Error is propagated. Otherwise this function assigns to the element at position Index the value By. Any exceptions raised during the assignment are propagated.
Implementation Requirements
No storage associated with a vector Container_Type object shall be lost upon assignment or scope exit.
A.X.Y The Package Containers.Lists
The package Containers.Lists is the root of the linked-list containers subsystem.
Static Semantics
The library package Containers.Lists has the following declaration:
package Ada.Containers.Lists is pragma Pure; end Ada.Containers.Lists;
A.X.Y The Package Containers.Lists.Double
The package Containers.Lists.Double is the root of the doubly-linked list containers subsystem.
Static Semantics
The library package Containers.Lists.Double has the following declaration:
package Ada.Containers.Lists.Double is pragma Pure; end Ada.Containers.Lists.Double;
A.X.Y The Package Containers.Lists.Double.Unbounded
The language-defined package Containers.Lists.Double.Unbounded provides private types Container_Type and Iterator_Type, and provides a set of operations for each type. A doubly-linked list container is optiminzed for insertion and deletion at any position. Positions are specified using an iterator.
A doubly-linked list container object manages a linked list of internal storage nodes, each of which contains an element and pointers to the next (successor) and previous (predecessor) internal nodes. An iterator is implemented as an access type that designates a node of storage.
Static Semantics
The library package Containers.Lists.Double.Unbounded has the following declaration:
generic
type Element_Type is private;
with function "=" (Left, Right : Element_Type) return Boolean is <>;
package Ada.Containers.Lists.Double.Unbounded is
pragma Preelaborate;
type Container_Type is private;
type Iterator_Type is private;
Null_Iterator : constant Iterator_Type;
function "=" (Left, Right : Container_Type) return Boolean;
function Length (Container : Container_Type) return Natural;
function Is_Empty (Container : Container_Type) return Boolean;
procedure Clear (Container : in out Container_Type);
procedure Swap (Left, Right : in out Container_Type);
procedure Prepend (Container : in out Container_Type; New_Item : in Element_Type);
procedure Append (Container : in out Container_Type; New_Item : in Element_Type);
procedure Insert (Container : in out Container_Type; Before : in Iterator_Type; New_Item : in Element_Type; Iterator : out Iterator_Type);
procedure Insert (Container : in out Container_Type; Before : in Iterator_Type; Iterator : out Iterator_Type);
procedure Delete (Container : in out Container_Type; Iterator : in out Iterator_Type);
procedure Delete_First (Container : in out Container_Type);
procedure Delete_Last (Container : in out Container_Type);
procedure Reverse_Container (Container : in Container_Type);
generic with function "<" (Left, Right : Element_Type) return Boolean is <>; procedure Generic_Sort (Container : in Container_Type);
generic with function "<" (Left, Right : Element_Type) return Boolean is <>; procedure Generic_Merge (Container : in out Container_Type; Source : in out Container_Type);
procedure Splice (Container : in out Container_Type; Before : in Iterator_Type; Source : in out Container_Type);
procedure Splice (Container : in out Container_Type; Before : in Iterator_Type; Source : in out Container_Type; Iterator : in Iterator_Type);
function First (Container : Container_Type) return Iterator_Type;
function First_Element (Container : Container_Type) return Element_Type;
function Last (Container : Container_Type) return Iterator_Type;
function Last_Element (Container : Container_Type) return Element_Type;
function Back (Container : Container_Type) return Iterator_Type;
function Element (Iterator : Iterator_Type) return Element_Type;
generic type Element_Access is access all Element_Type; function Generic_Element (Iterator : Iterator_Type) return Element_Access;
procedure Replace_Element (Iterator : Iterator_Type; By : Element_Type);
function Succ (Iterator : Iterator_Type) return Iterator_Type;
function Pred (Iterator : Iterator_Type) return Iterator_Type;
procedure Increment (Iterator : in out Iterator_Type);
procedure Decrement (Iterator : in out Iterator_Type);
generic with procedure Process (Iterator : in Iterator_Type) is <>; procedure Generic_Iteration (Container : in Container_Type);
generic with procedure Process (Iterator : in Iterator_Type) is <>; procedure Generic_Reverse_Iteration (Container : in Container_Type);
private
... -- not specified by the language
end Ada.Containers.Lists.Double.Unbounded;
When a doubly-linked list container object elaborates, it automatically allocates a sentinel node. The element of the sentinel node is uninitialized, except for any controlled initialization or default-initialized components. It is not included in the count of elements in the container, and it cannot be deleted.
Null_Iterator represents the null list iterator. If an object of type Iterator_Type is not otherwise initialized, it will be initialized to the same value as Null_Iterator.
The function "=" returns the same result as the corresponding vector container operation.
The function Length returns the number of elements in Container.
The function Is_Empty is equivalent to Length (Container) = 0.
Clear removes all the nodes (except for its sentinel) in Container, and sets the length to 0. Any exceptions raised during deallocation of storage are propagated.
Swap exchanges all the internal nodes (including the sentinel) of Left and Right.
Prepend is equivalent to Insert (Container, First (Container), New_Item).
Append is equivalent to Insert (Container, Back (Container), New_Item).
procedure Insert (Container : in out Container_Type; Before : in Iterator_Type; New_Item : in Element_Type; Iterator : out Iterator_Type);
if Before equals Null_Iterator, then Constraint_Error is propagated. if Before does not designate a node that belongs to Container, program execution is erroneous. Otherwise Insert allocates a new node whose element is initialized to the value New_Item, and inserts it prior to the node designated by Before. Iterator designates the newly-inserted node. Any exceptions raised during allocation of internal storage are propagated, and Container is not modified.
procedure Insert (Container : in out Container_Type; Before : in Iterator_Type; Iterator : out Iterator_Type);
Equivalent to Insert (Container, Before, New_Item, Iterator), with the difference that the element on the newly-allocated node is unitialized, except for any controlled initialization or default-initialized components.
procedure Delete (Container : in out Container_Type; Iterator : in out Iterator_Type);
If Iterator equals Null_Iterator or Back (Container), the operation has no effect. If Iterator does not designate a node that belongs Container, program execution is erroneous. Otherwise Delete removes the node designated by Iterator from Container. The Iterator value returned designates the successor of the node deleted. Any exceptions raised during deallocation of internal storage are propagated.
procedure Delete_First (Container : in out Container_Type);
If Container is empty, this operation has no effect. Otherwise Delete_First removes the front-most (non-sentinel) node from Container. Any exceptions raised during deallocation of storage are propagated.
procedure Delete_Last (Container : in out Container_Type);
If Container is empty, this operation has no effect. Otherwise Delete_Last removes the back-most (non-sentinel) node from Container. Any exceptions raised during deallocation of storage are propagated.
procedure Reverse_Container (Container : in Container_Type);
Relinks the (non-sentinel) nodes of Container in reverse order.
generic with function "<" (Left, Right : Element_Type) return Boolean is <>; procedure Generic_Sort (Container : in Container_Type);
Relinks the (non-sentinel) nodes of Container such that the elements are sorted per the order specified as the generic formal less-than operator. The sort is stable. Any exceptions raised during evalution of less-than are propagated.
generic with function "<" (Left, Right : Element_Type) return Boolean is <>; procedure Generic_Merge (Container : in out Container_Type; Source : in out Container_Type);
If Source denotes the same object as Container, the operation has no effect. Otherwise this operation relinks nodes such that they are removed from Source and moved to Container. Container and Source are assumed to be sorted in the order specified as the generic formal. The nodes in Source containing elements that are less than elements in Container are spliced in before the elements already in Container. The nodes in Source container elements that are equal to or greater than elements in Container are spliced in after the elements already in Container. Any exceptions raised during evaluation of less-than are propagated.
procedure Splice (Container : in out Container_Type; Before : in Iterator_Type; Source : in out Container_Type);
If Source denotes the same object as Container, the operation has no effect. If Before equals Null_Iterator, then Constraint_Error is propagated. If Before does not designate a node that belongs to Container, then program execution is erroneous. Otherwise, Splice relinks nodes such that they are removed from Source (except for its sentinel node) and moved to Container, just prior to Before. The length of Container is incremented by the number of nodes in Source, and the length of Source is set to 0.
procedure Splice (Container : in out Container_Type; Before : in Iterator_Type; Source : in out Container_Type; Iterator : in Iterator_Type);
If Source denotes the same object as Container, then if Iterator equals Back (Container) or Null_Iterator, or if Iterator equals Before, the operation has no effect. If Before equals Null_Iterator, then Constraint_Error is propagated. If Iterator or Before does not designate a node in Container, then program execution is erroneous. Otherwise the node designated by Iterator is relinked such that it becomes the predecessor of Before.
Otherwise if Source does not denote the same object as Container, then if Iterator equals Back (Source) or Null_Iterator, the operation has no effect. If Before equals Null_Iterator, then Constraint_Error is propagated. If Iterator does not designate a node in Source, or Before does not designate a node in Container, then program execution is erroneous. Otherwise the node designated by Iterator is relinked such that it is removed from Source and moved to Container, immediately prior to Before. The length of Container is incremented, and the length of Source is decremented.
function First (Container : Container_Type) return Iterator_Type;
Returns an iterator that designates the front-most (non-sentinel) node. If Container is empty, First returns the value of Back (Container).
function First_Element (Container : Container_Type) return Element_Type;
If Container is empty, then Constraint_Error is propagated. Otherwise, it returns the element on the node designated by First (Container).
function Last (Container : Container_Type) return Iterator_Type;
Returns an iterator that designates the back-most (non-sentinel) node. If Container is empty, Last returns the value of Back (Container).
function Last_Element (Container : Container_Type) return Element_Type;
If Container is empty, then Constraint_Error is propagated. Otherwise, it returns the element on the node designated by Last (Container).
function Back (Container : Container_Type) return Iterator_Type;
Returns an iterator that designates the sentinel node.
function Element (Iterator : Iterator_Type) return Element_Type;
Returns the element stored on the node designated by Iterator. If Iterator equals Null_Iterator, then Constraint_Error is propagated.
generic type Element_Access is access all Element_Type; function Generic_Element (Iterator : Iterator_Type) return Element_Access;
Returns an access value that designates a variable view of the element stored on the node designated by Iterator. If Iterator equals Null_Iterator, then Constraint_Error is propagated.
procedure Replace_Element (Iterator : Iterator_Type; By : Element_Type);
Assigns the value By to the element stored on the node designated by Iterator. If Iterator equals Null_Iterator, then Constraint_Error is propagated.
function Succ (Iterator : Iterator_Type) return Iterator_Type;
Returns an iterator that designates the successor of the node designated by Iterator. If Iterator equals Null_Iterator, then Constraint_Error is propagated.
AARM Note The successor of Last is Back, and the successor of Back is First. If the list is empty, First, Last, and Back all designate the same node.
function Pred (Iterator : Iterator_Type) return Iterator_Type;
Returns an iterator that designates the predecessor of the node designated by Iterator. If Iterator equals Null_Iterator, then Constraint_Error is propagated.
AARM Note The predecessor of Back is Last, and the predecessor of First is Back.
The procedure Increment is equivalent to Iterator := Succ (Iterator).
The procedure Decrement is equivalent to Iterator := Pred (Iterator).
generic with procedure Process (Iterator : in Iterator_Type) is <>; procedure Generic_Iteration (Container : in Container_Type);
Invokes the actual subprogram bound to Process with an iterator that designates each node in Container. Any exceptions raised during Process are propagated.
generic
with procedure Process (Iterator : in Iterator_Type) is <>;
procedure Generic_Reverse_Iteration (Container : in Container_Type);
Iterates over the nodes in Container as per Generic_Iteration, except that elements are traversed in reverse order.
Implementation Requirements
No storage associated with a doubly-linked list Container_Type object shall be lost upon assignment or scope exit.
A.X.3 The Package Containers.Maps
The package Containers.Maps is the root of the map containers subsystem.
Static Semantics
The library package Containers.Maps has the following declaration:
package Ada.Containers.Maps is pragma Pure; end Ada.Containers.Maps;
A.X.Y The Package Containers.Maps.Hashed
The package Containers.Maps.Hashed is the root of the hashed map containers subsystem.
Static Semantics
The library package Containers.Maps.Hashed has the following declaration:
package Ada.Containers.Maps.Hashed is pragma Pure; end Ada.Containers.Maps.Hashed;
A.X.Y The Package Containers.Maps.Hashed.Unbounded
The language-defined package Containers.Maps.Hashed.Unbounded provides private types Container_Type and Iterator_Type, and a set of operations for each type. A hashed map container allows an arbitrary type to be used as a key to find the element associated with that key.
Static Semantics
The library package Containers.Maps.Hashed.Unbounded has the following declaration:
generic
type Key_Type is private;
type Element_Type is private;
with function Hash (Key : Key_Type) return Integer'Base is <>;
with function Is_Equal_Key (Left, Right : Key_Type) return Boolean is "=";
with function "=" (Left, Right : Element_Type) return Boolean is <>;
package Ada.Containers.Maps.Hashed.Unbounded is
pragma Preelaborate;
type Container_Type is private;
type Iterator_Type is private;
Null_Iterator : constant Iterator_Type;
function "=" (Left, Right : Container_Type) return Boolean;
function Length (Container : Container_Type) return Natural;
function Is_Empty (Container : Container_Type) return Boolean;
procedure Clear (Container : in out Container_Type);
procedure Swap (Left, Right : in out Container_Type);
procedure Insert (Container : in out Container_Type; Key : in Key_Type; New_Item : in Element_Type; Iterator : out Iterator_Type; Success : out Boolean);
procedure Replace (Container : in out Container_Type; Key : in Key_Type; New_Item : in Element_Type);
procedure Insert (Container : in out Container_Type; Key : in Key_Type; Iterator : out Iterator_Type; Success : out Boolean);
procedure Delete (Container : in out Container_Type; Key : in Key_Type);
procedure Delete (Container : in out Container_Type; Iterator : in out Iterator_Type);
function Is_In (Key : Key_Type; Container : Container_Type) return Boolean;
function Find (Container : Container_Type; Key : Key_Type) return Iterator_Type;
function Element (Container : Container_Type; Key : Key_Type) return Element_Type;
function Size (Container : Container_Type) return Natural;
procedure Resize (Container : in out Container_Type; Size : in Natural);
function First (Container : Container_Type; Index : Positive) return Iterator_Type;
function Back (Container : Container_Type) return Iterator_Type;
function Index (Container : Container_Type; Key : Key_Type) return Positive;
function Succ (Iterator : Iterator_Type) return Iterator_Type;
procedure Increment (Iterator : in out Iterator_Type);
function Key (Iterator : Iterator_Type) return Key_Type;
generic type Key_Access is access constant Key_Type; function Generic_Key (Iterator : Iterator_Type) return Key_Access;
function Is_Equal_Key (Left, Right : Iterator_Type) return Boolean;
function Is_Equal_Key (Left : Iterator_Type; Right : Key_Type) return Boolean;
function Is_Equal_Key (Left : Key_Type; Right : Iterator_Type) return Boolean;
function Element (Iterator : Iterator_Type) return Element_Type;
generic type Element_Access is access all Element_Type; function Generic_Element (Iterator : Iterator_Type) return Element_Access;
procedure Replace_Element (Iterator : in Iterator_Type; By : in Element_Type);
generic with procedure Process (Iterator : in Iterator_Type) is <>; procedure Generic_Iteration (Container : in Container_Type);
private
... -- not specified by the language
end Ada.Containers.Maps.Hashed.Unbounded;
If an object of type Container_Type is not otherwise initialized, it will be initialized with a size (buckets array length) of 0.
Null_Iterator represents the null map iterator. If an object of type Iterator_Type is not otherwise initialized, it will be initialized to the same value as Null_Iterator.
function "=" (Left, Right : Container_Type) return Boolean;
If Left and Right denote the same map container object, then the function returns immediately the value True. If Left and Right have different lengths, then the function returns the value False. Otherwise, it compares elements (and only elements -- keys do not participate in the computation of container equality) in canonical order using the generic formal equality operator for elements. Any exception raised during evaluation of element equality is propagated.
The function Length returns the number of key/element pairs in Contaner.
The function Is_Empty is equivalent to Length (Container) = 0.
Clear removes all the nodes in Container, and sets the length to 0. The size of Container is not affected. Any exceptions raised during deallocation of storage propagated.
Swap exchanges the internal nodes and buckets array of Left and Right.
procedure Insert (Container : in out Container_Type; Key : in Key_Type; New_Item : in Element_Type; Iterator : out Iterator_Type; Success : out Boolean);
If Length (Container) equals Size (Container), then Insert resizes Container to some larger value as per Resize. Insert first computes the index in the buckets array from the hash value of Key and size of Container. Any exceptions raised by Hash are propagated. It then uses Is_Equal_Key to compare Key to the key of each node in the list at that index. If a key matches, Success returns False and Iterator designates the node with the matching key. Otherwise, Insert allocates a new node initialized to Key and New_Item and inserts the new node at the head of the list. Success returns True and Iterator designates the newly-inserted node. Any exceptions raised during allocation are propagated and Container is not modified.
procedure Replace (Container : in out Container_Type; Key : in Key_Type; New_Item : in Element_Type);
Replace inserts Key and New_Item as per Insert, with the difference that if Key is already in the map, then this operation assign New_Item to the element associated with Key. Any exceptions raised during assignment are propagated.
procedure Insert (Container : in out Container_Type; Key : in Key_Type; Iterator : out Iterator_Type; Success : out Boolean);
Inserts Key into Container as per Insert (Container, Key, New_Item, Iterator, Success), with the difference that the element of a newly-inserted node is uninitialized, except for any controlled initialization or default-initialized components.
procedure Delete (Container : in out Container_Type; Key : in Key_Type);
If Length (Container) equals 0, then this operation has no effect. Otherwise it first computes in index in the buckets array from the hash value of Key and the size of Container. Any exceptions raised by Hash are propagated. It then uses Is_Equal_Key to compare Key to the key of each the node in the list at that index of the buckets array. Any exceptions raised by Is_Equal_Key are propagated. If Key matches the key of a node, Delete removes the node from the list and then deallocates the node. Any exceptions raised during deallocation of storage are propagated.
procedure Delete (Container : in out Container_Type; Iterator : in out Iterator_Type);
If Iterator equals Null_Iterator, this operation has no effect. If Iterator does not designate a node that belongs to Container, program execution is erroneous. Otherwise it computes the index in the buckets array from the hash value of the key on the node designated by Iterator and the size of Container. Any exception raised by Hash is propagated. Delete then removes the node from the list and deallocates the node. Any exceptions raised during deallocation of storage are propagated. The Iterator returned designates in the list the node that was the successor of the node deleted; if this was the last node of the list then it returns Null_Iterator.
function Find (Container : Container_Type; Key : Key_Type) return Iterator_Type;
If Length (Container) equals 0, then this function returns Null_Iterator. Otherwise it computes the index of the buckets array from the hash value of Key and the size of Container. Any exception raised by Hash is propagated. If the list at that index is empty, then it returns Null_Iterator. Otherwise it uses Is_Equal_key to compare Key to the key of each node on the list. Any exception raised by Is_Equal_Key is propagated. If Is_Equal_Key returns True, it returns an iterator designating the node with the matching key; otherwise, it returns Null_Iterator.
The function Is_In is equivalent to Find (Container, Key) /= Null_Iterator.
The function Element is equivalent to Element (Find (Container, Key)).
The function Size returns the length of the buckets array.
procedure Resize (Container : in out Container_Type; Size : in Natural);
If Size (Container) is equal to or greater than Size, this operation has no effect. Otherwise, it allocates a new buckets array whose length is a least the value Size. If the allocation fails, the exception is propagated and Container is not modified. It then rehashes the nodes in Container onto the new buckets array. Any exception raised by Hash is propagated and the nodes that were moved onto the new buckets array are lost. It replaces the old buckets array with the new buckets array, and then deallocates the old buckets array. Any exceptions raised during deallocation are propagated.
function First (Container : Container_Type; Index : Positive) return Iterator_Type;
If Index is outside the range 1 .. Size (Container), then Constraint_Error is propagated. Otherwise, First returns an iterator that designates the node at the head of the list at position Index of the buckets array. If there is no list at that Index, then it returns Null_Iterator.
function Index (Container : Container_Type; Key : Key_Type) return Positive;
If Size (Container) equals 0, then Constraint_Error is propagated. Otherwise, Index returns Key (Hash) mod Size (Container).
function Succ (Iterator : Iterator_Type) return Iterator_Type;
If Iterator equals Null_Iterator, then Constraint_Error is propagated. Otherwise, Succ returns an iterator that designates the successor node in the same list. If there is no successor node then it returns Null_Iterator.
The procedure Increment is equivalent to Iterator := Succ (Iterator).
function Key (Iterator : Iterator_Type) return Key_Type;
If Iterator equals Null_Iterator, then Constraint_Error is propagated. Otherwise, this operation returns the key component of node designated by Iterator.
generic type Key_Access is access constant Key_Type; function Generic_Key (Iterator : Iterator_Type) return Key_Access;
If Iterator equals Null_Iterator, then Constraint_Error is propagated. Otherwise, this operation returns an access value that designates a constant view of the key component of the node designated by Iterator.
The function Is_Equal_Key (Left, Right : Iterator_Type) is equivalent to Is_Equal_Key (Key (Left), Key (Right)).
The function Is_Equal_Key (Left : Iterator_Type; Right : Key_Type) is equivalent to Is_Equal_Key (Key (Left), Right).
The function Is_Equal_Key (Left : Key_Type; Right : Iterator_Type) is equivalent to Is_Equal_Key (Left, Key (Right)).
function Element (Iterator : Iterator_Type) return Element_Type;
If Iterator equals Null_Iterator, then Constraint_Error is propagated. Otherwise, this operation returns the element component of the node designated by Iterator.
generic type Element_Access is access all Element_Type; function Generic_Element (Iterator : Iterator_Type) return Element_Access;
If Iterator equals Null_Iterator, then Constraint_Error is propagated. Otherwise, this operation returns an access value that designates a variable view of the element component of the node designated by Iterator.
procedure Replace_Element
(Iterator : in Iterator_Type;
By : in Element_Type);
If Iterator equals Null_Iterator, then Constraint_Error is propagated. Otherwise this operation assigns By to the element on the node designed by Iterator.
generic with procedure Process (Iterator : in Iterator_Type) is <>; procedure Generic_Iteration (Container : in Container_Type);
Generic_Iterator calls Process with an iterator that designates each node in the Container. Any exceptions raised during Process are propagated.
Implementation Requirements
No storage associated with a map Container_Type object shall be lost upon assignment or scope exit.
A.X.Y The Function Containers.Maps.Hashed.Unbounded.Unchecked_Modification
The function Containers.Maps.Hashed.Unbouned.Unchecked_Modification allows the key component of a node to be modified.
Static Semantics
The library function Containers.Maps.Hashed.Unbounded.Unchecked_Modification has the following declaration:
generic type Key_Access is access all Key_Type; function Ada.Containers.Maps.Hashed.Unbounded.Unchecked_Modification (Iterator : Iterator_Type) return Key_Access;
pragma Preelaborate -- TODO: OK?
(Ada.Containers.Maps.Hashed.Unbounded.Unchecked_Modification);
If Iterator equals Null_Iterator, then Constraint_Error is propagated. Otherwise, this operation returns an access value that designates a variable view of the key component on the node designated by Iterator.
A.X.Y The Package Containers.Maps.Hashed.Strings
The package Containers.Maps.Hashed.Strings is the root of the String-key hashed map containers subsystem.
Static Semantics
The library package Containers.Maps.Hashed.Strings has the following declaration:
package Ada.Containers.Maps.Hashed.Strings is pragma Pure; end Ada.Containers.Maps.Hashed.Strings;
A.X.Y The Package Containers.Maps.Hashed.Strings.Unbounded
The package Containers.Maps.Hashed.Strings.Unbounded provides private types Container_Type and Iterator_Type, and a set of operations for each type. It provides the same hashed map operations as the package Containers.Maps.Hashed.Unbounded does, with the difference that predefined type String is the key.
Static Semantics
The declaration of the library package Containers.Maps.Hashed.Strings.Unbounded differs from the package Containers.Maps.Hashed.Unbounded as follows:
(1) There is no generic formal Key_Type.
(2) Everywhere where the formal type Key_Type is specified, it is
systematically replaced by type String.
(3) The Generic_Key function is omitted.
A.X.Y The Package Containers.Maps.Hashed.Wide_Strings
The package Containers.Maps.Hashed.Wide_Strings is the root of the Wide_String-key hashed map containers subsystem.
Static Semantics
The library package Containers.Maps.Hashed.Wide_Strings has the following declaration:
package Ada.Containers.Maps.Hashed.Wide_Strings is pragma Pure; end Ada.Containers.Maps.Hashed.Wide_Strings;
A.X.Y The Package Containers.Maps.Hashed.Wide_Strings.Unbounded
Static Semantics
The declaration of the library package Containers.Maps.Hashed.Wide_Strings.Unbounded differs from the package Containers.Maps.Hashed.Strinsg.Unbounded as follows:
(1) Everywhere where the formal type String is specified, it is
systematically replaced by type Wide_String.
A.X.Y Hash and String Comparison Functions
The following library-level subprograms are defined:
function Ada.Containers.Hash_String
(Key : String) return Integer'Base;
pragma Pure (Ada.Containers.Hash_String);
function Ada.Containers.Hash_String_Case_Insensitive
(Key : String) return Integer'Base;
pragma Pure (Ada.Containers.Hash_String_Case_Insensitive);
function Ada.Containers.Equal_String_Case_Insensitive
(Left, Right : String) return Boolean;
pragma Pure (Ada.Containers.Equal_String_Case_Insensitive);
function Ada.Containers.Less_String_Case_Insensitive
(Left, Right : String) return Boolean;
pragma Pure (Ada.Containers.Less_String_Case_Insensitive);
function Ada.Containers.Hash_Wide_String
(Key : Wide_String) return Integer'Base;
pragma Pure (Ada.Containers.Hash_Wide_String);
function Ada.Containers.Hash_Wide_String_Case_Insensitive
(Key : Wide_String) return Integer'Base;
pragma Pure (Ada.Containers.Hash_Wide_String_Case_Insensitive);
function Ada.Containers.Equal_Wide_String_Case_Insensitive
(Left, Right : Wide_String) return Boolean;
pragma Pure (Ada.Containers.Equal_Wide_String_Case_Insensitive);
function Ada.Containers.Less_Wide_String_Case_Insensitive
(Left, Right : Wide_String) return Boolean;
pragma Pure (Ada.Containers.Less_Wide_String_Case_Insensitive);
DESIGN NOTE: I think we should have hash functions for all predefined types, such Integer, Long_Integer, etc.
It might even be nice to have a predefined attribute for built-in types to return a hash value, e.g.
package File_Cache_Types is new Ada.Containers.Maps.Hashed.Strings.Unbounded (Element_Type => Context_Access, Hash => String'Hash_Case_Insensitive, Is_Equal_Key => String'Equal_Case_Insensitive);
or something like that. Or to have an extensible mechanism a la T'Read and T'Write.
A.X.Y The Package Container.Sets
The package Containers.Sets is the root of the set containers subsystem.
Static Semantics
The library package Containers.Sets has the following declaration:
package Ada.Containers.Sets is pragma Pure; end Ada.Containers.Sets;
A.X.Y The Package Containers.Sets.Sorted
The package Containers.Sets.Sorted is the root of the sorted set containers subsystem.
Static Semantics
The library package Containers.Sets.Sorted has the following declaration:
package Ada.Containers.Sets.Sorted is pragma Pure; end Ada.Containers.Sets.Sorted;
A.X.Y The Package Containers.Sets.Sorted.Unbounded
The language-defined package Containers.Sets.Sorted.Unbounded provides private types Container_Type and Iterator_Type, and a set of operations for each type. A sorted set container orders its elements per a specified relation.
Static Semantics
The package Containers.Sets.Sorted.Unbounded has the following declaration:
generic
type Element_Type is private;
with function "<" (Left, Right : Element_Type) return Boolean is <>;
with function "=" (Left, Right : Element_Type) return Boolean is <>;
package Ada.Containers.Sets.Sorted.Unbounded is
pragma Preelaborate;
type Container_Type is private;
type Iterator_Type is private;
Null_Iterator : constant Iterator_Type;
function "=" (Left, Right : Container_Type) return Boolean;
function "<" (Left, Right : Container_Type) return Boolean;
function "<=" (Left, Right : Container_Type) return Boolean;
function ">" (Left, Right : Container_Type) return Boolean;
function ">=" (Left, Right : Container_Type) return Boolean;
function Length (Container : Container_Type) return Natural;
function Is_Empty (Container : Container_Type) return Boolean;
procedure Clear (Container : in out Container_Type);
procedure Swap (Left, Right : in out Container_Type);
procedure Insert (Container : in out Container_Type; New_Item : in Element_Type; Iterator : out Iterator_Type; Success : out Boolean);
procedure Insert (Container : in out Container_Type; Position : in Iterator_Type; New_Item : in Element_Type; Iterator : out Iterator_Type; Success : out Boolean);
procedure Delete (Container : in out Container_Type; Item : in Element_Type);
procedure Delete (Container : in out Container_Type; Iterator : in out Iterator_Type);
procedure Delete_Sans_Increment (Container : in out Container_Type; Iterator : in out Iterator_Type);
procedure Delete_First (Container : in out Container_Type);
procedure Delete_Last (Container : in out Container_Type);
function Is_In (Item : Element_Type; Container : Container_Type) return Boolean;
function Find (Container : Container_Type; Item : Element_Type) return Iterator_Type;
function Lower_Bound (Container : Container_Type; Item : Element_Type) return Iterator_Type;
function Upper_Bound (Container : Container_Type; Item : Element_Type) return Iterator_Type;
function First (Container : Container_Type) return Iterator_Type;
function First_Element (Container : Container_Type) return Element_Type;
function Last (Container : Container_Type) return Iterator_Type;
function Last_Element (Container : Container_Type) return Element_Type;
function Back (Container : Container_Type) return Iterator_Type;
function Succ (Iterator : Iterator_Type) return Iterator_Type;
function Pred (Iterator : Iterator_Type) return Iterator_Type;
procedure Increment (Iterator : in out Iterator_Type);
procedure Decrement (Iterator : in out Iterator_Type);
function "<" (Left, Right : Iterator_Type) return Boolean;
function "<" (Left : Iterator_Type; Right : Element_Type) return Boolean;
function "<" (Left : Element_Type; Right : Iterator_Type) return Boolean;
function Element (Iterator : Iterator_Type) return Element_Type;
generic type Element_Access is access constant Element_Type; function Generic_Element (Iterator : Iterator_Type) return Element_Access;
generic with procedure Process (Iterator : in Iterator_Type) is <>; procedure Generic_Iteration (Container : in Container_Type);
generic with procedure Process (Iterator : in Iterator_Type) is <>; procedure Generic_Reverse_Iteration (Container : in Container_Type);
private
... -- not specified by the language
end Ada.Containers.Sets.Sorted.Unbounded;
When a sorted set container elaborates, it automtically allocates a sentinel node. The element of the sentinel node is uninitialized, except for any controlled initialization or default-initialized components. The sentinel is not included in the count of elements in the container, and it cannot be deleted.
Null_Iterator represents the null sorted set iterator. If an object of type Iterator_Type is not otherwise initialized, it will be initialized to the same value as Null_Iterator.
function "=" (Left, Right : Container_Type) return Boolean;
If Left and Right denote the same object, then the function returns True. If Left and Right have different lengths, then the function returns False. Otherwise, it compares elements in sequential order using the generic formal equality operator for elements. Any exception raised during evaluation of element equality is propagated. If a corresponding pair of elements compare False, the function returns False. Otherwise if all corresponding elements compare True, the function returns True.
function "<" (Left, Right : Container_Type) return Boolean;
If Left denotes the same object as Right, then the function returns False. Otherwise, it compares elements in sequential order using the generic formal less-than operator for elements. Any exception raised during evaluation of less-than is propagated. If an element in Left compares less than a corresponding element in Right, the function returns True. If there are no more elements in Left, then if there are more elements in Right, then the function returns True; otherwise if there no more elements in Right, then it returns False. If there are more elements in Left but none remaining in Right, then the function returns False.
The function "<=" is equivalent to not (Left > Right).
The function ">" is equivalent to Right < Left.
The function ">=" is equivalent to not (Left < Right).
The function Length returns the number of elements in Container.
The function Is_Empty is equivalent to Length (Container) = 0.
Clear removes all the nodes in Container (except for its sentinel), and sets the length to 0. Any exceptions raised during deallocation of storage are propagated.
Swap exchanges all the nodes (including the sentinel) of Left and Right.
procedure Insert (Container : in out Container_Type; New_Item : in Element_Type; Iterator : out Iterator_Type; Success : out Boolean);
Insert compares New_Item to the elements in Container using the generic formal less-than operator for elements. Any exceptions raised by the less-than operator are propagated. If an element equivalent (see below) to New_Item is already in Container, Success is False and Iterator designates the node containing the element. Otherwise, it allocates a new node whose element is initialized to New_Item. Success returns True and Iterator designates the newly-inserted node. Any exceptions raised during allocation are propagated and Container is not modified.
The equality operator for elements is not used by this operation. Insert compares elements for "equivalence," which for elements E1 and E2 is defined as "not (E1 < E2) and not (E2 < E1)".
procedure Insert (Container : in out Container_Type; Position : in Iterator_Type; New_Item : in Element_Type; Iterator : out Iterator_Type; Success : out Boolean);
Equivalent to Insert (Container, New_Item, Iterator, Success), with the addition of a Position parameter, which specifies a hint about where to beging searching for an element equivalent to New_Item. If Position equals Null_Iterator, the operation is equivalent to the Position-less Insert. If Position does not designate a node that belongs to Container, program execution is erroneous. For a hint to be useful, it should designate a node containing an element that would be adjacent to New_Item, were it in the container.
procedure Delete (Container : in out Container_Type; Item : in Element_Type);
Delete searches Container for an element equivalent to Item, using the generic formal less-than operator for elements. Any exceptions raised by less-than are propagated. If there is an element in Container equivalent to Item, the node containing the element is removed from Container and deallocated. Any exceptions raised during deallocation of storage are propagated.
procedure Delete (Container : in out Container_Type; Iterator : in out Iterator_Type);
If Iterator equals Null_Iterator or Back (Container), the operation has no effect. If Iterator does not designate a node that belongs to Container, program execution is erroneous. Otherwise, Delete removes the node designated by Iterator from Container, and then deallocates the node. Any exception raised during deallocation of storage is propagated. The iterator value returned designates the successor of the node deleted.
procedure Delete_Sans_Increment (Container : in out Container_Type; Iterator : in out Iterator_Type);
Equivalent to Delete (Container, Iterator), with the difference that the iterator value returned equals Null_Iterator.
procedure Delete_First (Container : in out Container_Type);
If Container is empty, the operation has no effect. Otherwise the node designated by First (Container) is removed from Container and then deallocated. Any exception raised during deallocation of storage is propagated.
procedure Delete_Last (Container : in out Container_Type);
If Container is empty, the operation has no effect. Otherwise the node designated by Last (Container) is removed from Container and then deallocated. Any exception raised during deallocation of storage is propagated.
function Find (Container : Container_Type; Item : Element_Type) return Iterator_Type;
The Find operation searches for the element equivalent to Item, using the generic formal less-than operator for elements. Any exception raised by less-than is propagated. If it finds an equivalent element, it returns an iterator designating the node that contains the element. Otherwise it returns Null_Iterator.
The function Is_In is equivalent to Find (Container, Item) /= Null_Iterator.
function Lower_Bound (Container : Container_Type; Item : Element_Type) return Iterator_Type;
The Lower_Bound operation searches for the smallest element not less than Item, using the generic formal less-than operator for elements. Any exception raised by less-than is propagated. If there is an element in Container that is not less than Item, it returns an iterator designating the node containing the element. Otherwise it returns Back (Container).
function Upper_Bound
(Container : Container_Type;
Item : Element_Type) return Iterator_Type;
The Upper_Bound operation searches for the smallest element greater than Item, using the generic formal less-than operator for elements. Any exception raised by less-than is propagated. If there is an element in Container that is greater than Item, it returns an iterator designating the node containing the element. Otherwise it returns Back (Container).
function First (Container : Container_Type) return Iterator_Type;
Returns an iterator that designates the node containing the smallest element. If Container is empty, it returns Back (Container).
function First_Element (Container : Container_Type) return Element_Type;
If Container is empty, then Constraint_Error is propagated. Otherwise, it returns the element on the node designated by First (Container).
function Last (Container : Container_Type) return Iterator_Type;
Returns an iterator that designates the node containing the greatest element. If Container is empty, it returns Back (Container).
function Last_Element (Container : Container_Type) return Element_Type;
If Container is empty, then Constraint_Error is propagated. Otherwise, it returns the element on the node designated by Last (Container).
function Back (Container : Container_Type) return Iterator_Type;
Returns an iterator that designates the sentinel node.
function Succ (Iterator : Iterator_Type) return Iterator_Type;
If Iterator equals Null_Iterator, then Constraint_Error is propagated. Otherwise it returns the successor of the node designated by Iterator.
AARM NOTE The successor of Last is Back, and the successor of Back is First. If the container is empty, then First, Last, and Back all designate the sentinel node.
function Pred (Iterator : Iterator_Type) return Iterator_Type;
If Iterator equals Null_Iterator, then Constraint_Error is propagated. Otherwise it returns the predecessor of the node designated by Iterator.
AARM NOTE The predecessor of First is Back, and the predecessor of Back is Last.
The procedure Increment is equivalent to Iterator := Succ (Iterator).
The procedure Decrement is equivalent to Iterator := Pred (Iterator).
The function "<" (Left, Right : Iterator_Type) is equivalent to Element (Left) < Element (Right).
The function "<" (Left : Iterator_Type; Right : Element_Type) is equivalent to Element (Left) < Right.
The function "<" (Left : Element_Type; Right : Iterator_Type) is equivalent to Left < Element (Right).
function Element (Iterator : Iterator_Type) return Element_Type;
If Iterator equals Null_Iterator, then Constraint_Error is propagated. Otherwise, it returns the element on the node designated by Iterator.
generic type Element_Access is access constant Element_Type; function Generic_Element (Iterator : Iterator_Type) return Element_Access;
If Iterator equals Null_Iterator, then Constraint_Error is propagated. Otherwise, it returns an access value that designates a constant view of the the element on the node designated by Iterator.
generic with procedure Process (Iterator : in Iterator_Type) is <>; procedure Generic_Iteration (Container : in Container_Type);
Invokes Process with an iterator that designates each (non-sentinel) node in Container.
generic with procedure Process (Iterator : in Iterator_Type) is <>; procedure Generic_Reverse_Iteration (Container : in Container_Type);
Iterates over the nodes in Container as per Generic_Iteration, with the difference that the nodes are traversed in reverse order.
A.X.Y The Function Containers.Sets.Sorted.Unbounded.Unchecked_Modification
The function Containers.Sets.Sorted.Unbounded.Unchecked_Modification allows a set element to be modified.
Static Semantics
The library function Containers.Sets.Sorted.Unbounded.Unchecked_Modification has the following declaration:
generic type Element_Access is access all Element_Type; function Ada.Containers.Sets.Sorted.Unbounded.Unchecked_Modification (Iterator : Iterator_Type) return Element_Access;
pragma Preelaborate --TODO: check this
(Ada.Containers.Sets.Sorted.Unbounded.Unchecked_Modification);
If Iterator equals Null_Iterator, then Constraint_Error is propagated. Otherwise, it returns an access value that designates a variable view of the element on the node designated by Iterator.
A.X.Y The Package Containers.Sets.Sorted.Unbounded.Generic_Keys
The package Containers.Sets.Sorted.Unbounded.Generic_Keys provides operations that allow set manipulation in terms of a key instead of an element.
Static Semantics
The package Ada.Containers.Sets.Sorted.Unbounded.Generic_Keys has the following declaration:
generic
type Key_Type (<>) is limited private;
with function "<" (Left : Element_Type; Right : Key_Type) return Boolean is <>;
with function "<" (Left : Key_Type; Right : Element_Type) return Boolean is <>;
package Ada.Containers.Sets.Sorted.Unbounded.Generic_Keys is
pragma Preelaborate;
function Is_In (Key : Key_Type; Container : Container_Type) return Boolean;
function Find (Container : Container_Type; Key : Key_Type) return Iterator_Type;
function Element (Container : Container_Type; Key : Key_Type) return Element_Type;
function Lower_Bound (Container : Container_Type; Key : Key_Type) return Iterator_Type;
function Upper_Bound (Container : Container_Type; Key : Key_Type) return Iterator_Type;
procedure Delete (Container : in out Container_Type; Key : in Key_Type);
function "<" (Left : Iterator_Type; Right : Key_Type) return Boolean;
function "<" (Left : Key_Type; Right : Iterator_Type) return Boolean;
generic
with procedure Set_Key (Element : in out Element_Type; Key : in Key_Type) is <>;
package Generic_Insertion is
procedure Insert (Container : in out Container_Type; Key : in Key_Type; Iterator : out Iterator_Type; Success : out Boolean);
procedure Insert (Container : in out Container_Type; Position : in Iterator_Type; Key : in Key_Type; Iterator : out Iterator_Type; Success : out Boolean);
end Generic_Insertion;
end Ada.Containers.Sets.Sorted.Unbounded.Generic_Keys;
The operations named Is_In, Find, Element, Lower_Bound, Upper_Bound, Delete, and operators designated "<", are equivalent to the corresponding operations in the parent package, with the difference that the Key subprogram parameter is compared to elements in the container using the Generic_Keys generic formal less-than operators.
procedure Insert (Container : in out Container_Type; Key : in Key_Type; Iterator : out Iterator_Type; Success : out Boolean);
Insert compares Key to elements already in Container using the Generic_Keys generic formal less-than operators for keys and elements. Any exceptions raised by less-than are propagated. If an element equivalent to Key is already in Container, Success is False and Iterator designates the node containing the element equivalent to Key. Otherwise, it allocates a new node and then calls Set_Key with the element of the node and Key as the parameters. Any exceptions raised during allocation are propagated. If Set_Key raises an exception, Insert deallocates the node and then propagates the exception. Otherwise, it inserts the node into the Container. Success returns True and Iterator designates the newly-inserted node.
procedure Insert (Container : in out Container_Type; Position : in Iterator_Type; Key : in Key_Type; Iterator : out Iterator_Type; Success : out Boolean);
Equivalent to Insert (Container, Key, Iterator, Success), with the addition of a Position parameter, which specifies a hint about where to begin searching for an element equivalent to Key. If Position equals Null_Iterator then this operation is equivalent to the Position-less Insert. If Position does not designate a node that belongs to Container then program execution is erroneous.
A.X.Y The Package Containers.Multisets
The package Containers.Multisets is the root of the multiset containers subsystem.
Static Semantics
The library package Containers.Multisets has the following declaration:
package Ada.Containers.Multisets is pragma Pure; end Ada.Containers.Multisets;
A.X.Y The Package Containers.Multisets.Sorted
The package Containers.Multisets.Sorted is the root of the sorted multiset containers subsystem.
Static Semantics
The library package Containers.Multisets.Sorted has the following declaration:
package Ada.Containers.Multisets.Sorted is pragma Pure; end Ada.Containers.Multisets.Sorted;
A.X.Y The Package Containers.Multisets.Sorted.Unbounded
The language-defined package Containers.Multisets.Sorted.Unbounded provides private types Container_Type and Iterator_Type, and a set of operations for each type. A sorted multiset orders its elements per a specified relation, and allows elements to have non-unique values.
Static Semantics
The package Containers.Multisets.Sorted.Unbounded has the following declaration:
generic
type Element_Type is private;
with function "<" (Left, Right : Element_Type) return Boolean is <>;
with function "=" (Left, Right : Element_Type) return Boolean is <>;
package Ada.Containers.Multisets.Sorted.Unbounded is
pragma Preelaborate;
type Container_Type is private;
type Iterator_Type is private;
Null_Iterator : constant Iterator_Type;
function "=" (Left, Right : Container_Type) return Boolean;
function "<" (Left, Right : Container_Type) return Boolean;
function "<=" (Left, Right : Container_Type) return Boolean;
function ">" (Left, Right : Container_Type) return Boolean;
function ">=" (Left, Right : Container_Type) return Boolean;
function Length (Container : Container_Type) return Natural;
function Is_Empty (Container : Container_Type) return Boolean;
procedure Clear (Container : in out Container_Type);
procedure Swap (Left, Right : in out Container_Type);
procedure Insert (Container : in out Container_Type; New_Item : in Element_Type; Iterator : out Iterator_Type);
procedure Insert (Container : in out Container_Type; Position : in Iterator_Type; New_Item : in Element_Type; Iterator : out Iterator_Type);
procedure Delete (Container : in out Container_Type; Item : in Element_Type);
procedure Delete (Container : in out Container_Type; Iterator : in out Iterator_Type);
procedure Delete_Sans_Increment (Container : in out Container_Type; Iterator : in out Iterator_Type);
procedure Delete_First (Container : in out Container_Type);
procedure Delete_Last (Container : in out Container_Type);
function Is_In (Item : Element_Type; Container : Container_Type) return Boolean;
function Find (Container : Container_Type; Item : Element_Type) return Iterator_Type;
function Lower_Bound (Container : Container_Type; Item : Element_Type) return Iterator_Type;
function Upper_Bound (Container : Container_Type; Item : Element_Type) return Iterator_Type;
function First (Container : Container_Type) return Iterator_Type;
function First_Element (Container : Container_Type) return Element_Type;
function Last (Container : Container_Type) return Iterator_Type;
function Last_Element (Container : Container_Type) return Element_Type;
function Back (Container : Container_Type) return Iterator_Type;
function Succ (Iterator : Iterator_Type) return Iterator_Type;
function Pred (Iterator : Iterator_Type) return Iterator_Type;
procedure Increment (Iterator : in out Iterator_Type);
procedure Decrement (Iterator : in out Iterator_Type);
function "<" (Left, Right : Iterator_Type) return Boolean;
function "<" (Left : Iterator_Type; Right : Element_Type) return Boolean;
function "<" (Left : Element_Type; Right : Iterator_Type) return Boolean;
function Element (Iterator : Iterator_Type) return Element_Type;
generic type Element_Access is access constant Element_Type; function Generic_Element (Iterator : Iterator_Type) return Element_Access;
generic with procedure Process (Iterator : in Iterator_Type) is <>; procedure Generic_Iteration (Container : in Container_Type);
generic with procedure Process (Iterator : in Iterator_Type) is <>; procedure Generic_Reverse_Iteration (Container : in Container_Type);
private
... -- not specified by the language
end Ada.Containers.Multisets.Sorted.Unbounded;
When a sorted multiset container elaborates, it automtically allocates a sentinel node. The element of the sentinel node is uninitialized, except for any controlled initialization or default-initialized components. The sentinel is not included in the count of elements in the container, and it cannot be deleted.
Null_Iterator represents the null sorted multiset iterator. If an object of type Iterator_Type is not otherwise initialized, it will be initialized to the same value as Null_Iterator.
Each of the functions "=", "<", "<=", ">", ">=" returns the same result as the corresponding functions in the sorted (unique-element) set.
The functions Length, Is_Empty, First, First_Element, Last, Last_Element, Back, Succ, Pred, Lower_Bound, Upper_Bound, Element, and Generic_Elemenet return the same result as the corresonding functions in the sorted set.
The operations Clear, Swap, Delete (Container, Iterator), Delete_Sans_Increment, Delete_First, Delete_Last, Increment, Decrement, Generic_Iteration, and Generic_Reverse_Iteration have the same effect as the corresponding operations in the sorted set.
procedure Insert (Container : in out Container_Type; New_Item : in Element_Type; Iterator : out Iterator_Type);
Insert compares New_Item to elements already in Container using the generic formal less-than operator for elements. Any exceptions raised by less-than are propagated. It allocates a new node and initializes its element to New_Item, and inserts the node after any elements equivalent to New_Item already in Container. Any exceptions raised during allocation are propagated, and Container is not modified.
procedure Insert (Container : in out Container_Type; Position : in Iterator_Type; New_Item : in Element_Type; Iterator : out Iterator_Type);
Equivalent to Insert (Container, New_Item, Iterator), with the addition of a Position parameter, which specifies a hint about where to begin searching for elements equivalent to New_Item. If Position equals Null_Iterator, this operation is equivalent to the Position-less Insert. If Position does not designate a node that belongs to Container, program execution is erroneous.
Insert first assumes Position is a potential successor. If Position equals Back (Container) or the element designated by Position is not less than New_Item, and Position equals First (Container) or New_Item is not less than the predecessor of Position, then New_Item is inserted immediately prior to Position.
If Insert rules-out Position as a successor, then it assumes Position is a potential predecessor. If New_Item is not less than Position, and the successor of Position equals Back (Container) or is not less than New_Item, then New_Item is inserted immediately following Position.
Otherwise, New_Item is inserted as per the Position-less Insert.
procedure Delete (Container : in out Container_Type; Item : in Element_Type);
Delete searches Container for elements equivalent to Item, using the generic formal less-than operator for elements. Any exceptions raised by less-than are propagated. It removes from Container all the nodes with elements equivalent to Item and deallocates the nodes. Any exceptions raised during deallocation of storage are propagated.
function Find (Container : Container_Type; Item : Element_Type) return Iterator_Type;
The Find operation searches Container for elements equivalent to Item, using the generic formal less-than operator for elements. Any exception raised by less-than is propagated. If there are elements equivalent to Item, then Find returns an iterator designating the node that contains the front-most equivalent element. Otherwise, it returns Null_Iterator.
The function Is_In is equivalent to Find (Container, Item) /= Null_Iterator.
A.X.Y The Function Containers.Multitsets.Sorted.Unbounded.Unchecked_Modification
The function Containers.Multisets.Sorted.Unbounded.Unchecked_Modification allows multiset elements to be modified.
Static Semantics
The library function Containers.Multisets.Sorted.Unbounded.Unchecked_Modification has the following declaration:
generic type Element_Access is access all Element_Type; function Ada.Containers.Multisets.Sorted.Unbounded.Unchecked_Modification (Iterator : Iterator_Type) return Element_Access;
pragma Preelaborate --TODO: check this
(Ada.Containers.Multisets.Sorted.Unbounded.Unchecked_Modification);
If Iterator equals Null_Iterator, then Constraint_Error is propagated. Otherwise, it returns an access value that designates a variable view of the element on the node designated by Iterator.
A.X.Y The Package Containers.Multisets.Sorted.Unbounded.Generic_Keys
The package Containers.Multisets.Sorted.Unbounded.Generic_Keys provides operations that allow multiset manipulation in terms of a key instead of an element.
Static Semantics
The package Ada.Containers.Multisets.Sorted.Unbounded.Generic_Keys has the following declaration:
generic
type Key_Type (<>) is limited private;
with function "<" (Left : Element_Type; Right : Key_Type) return Boolean is <>;
with function "<" (Left : Key_Type; Right : Element_Type) return Boolean is <>;
package Ada.Containers.Multisets.Sorted.Unbounded.Generic_Keys is
pragma Preelaborate;
function Is_In (Key : Key_Type; Container : Container_Type) return Boolean;
function Find (Container : Container_Type; Key : Key_Type) return Iterator_Type;
function Element (Container : Container_Type; Key : Key_Type) return Element_Type;
function Lower_Bound (Container : Container_Type; Key : Key_Type) return Iterator_Type;
function Upper_Bound (Container : Container_Type; Key : Key_Type) return Iterator_Type;
procedure Delete (Container : in out Container_Type; Key : in Key_Type);
function "<" (Left : Iterator_Type; Right : Key_Type) return Boolean;
function "<" (Left : Key_Type; Right : Iterator_Type) return Boolean;
generic
with procedure Set_Key (Element : in out Element_Type; Key : in Key_Type) is <>;
package Generic_Insertion is
procedure Insert (Container : in out Container_Type; Key : in Key_Type; Iterator : out Iterator_Type);
procedure Insert (Container : in out Container_Type; Position : in Iterator_Type; Key : in Key_Type; Iterator : out Iterator_Type);
end Generic_Insertion;
end Ada.Containers.Multisets.Sorted.Unbounded.Generic_Keys;
The operations named Is_In, Find, Element, Lower_Bound, Upper_Bound, Delete, and operators designated "<", are equivalent to the corresponding operations in the parent package, with the difference that the Key subprogram parameter is compared to elements in the container using the Generic_Keys generic formal less-than operators.
procedure Insert (Container : in out Container_Type; Key : in Key_Type; Iterator : out Iterator_Type);
Insert compares Key to elements already in Container using the Generic_Keys generic formal less-than operators for keys and elements. Any exceptions raised by less-than are propagated. It allocates a new node and then calls Set_Key with the element of the node and Key as the parameters. Any exceptions raised during allocation are propagated. If Set_Key raises an exception, Insert deallocates the node and then propagates the exception. Otherwise, it inserts the node into Container after any elements that are equivalent to Key. Iterator designates the newly-inserted node.
procedure Insert (Container : in out Container_Type; Position : in Iterator_Type; Key : in Key_Type; Iterator : out Iterator_Type);
Equivalent to Insert (Container, Key, Iterator), with the addition of a Position parameter, which specifies a hint about where to begin searching for an element equivalent to Key. If Position equals Null_Iterator then this operation is equivalent to the Position-less Insert. If Position does not designate a node that belongs to Container then program execution is erroneous. Otherwise the new node is inserted as per the Insert-with-hint operation of the parent package.
!example
A.X.Y The Package Containers.Vectors.Unbounded
Append is the canonical method for inserting items into a vector container:
procedure Copy (A : Array_Subtype) is V : Vector_Subtype; begin Resize (V, Size => A'Length);
for I in A'range loop Append (V, New_Item => A (I)); end loop; ... end Copy;
The Resize operation tells the vector object to preallocate an internal array having at least the size specified. If you need to perform many repeated insertions, then if you know the ultimate length apriori you should always call Resize beforehand. This is more efficient because it allocates the internal array once, and therefore avoids the repeated reallocation, copying, and deallocation cycles that would be necessary otherwise as the array is expanded.
You can use a vector to implement a stack in the traditional way:
package Stacks is new Ada.Containers.Vectors.Unbounded (ET); use Stacks;
Stack : Stacks.Container_Type;
procedure Push (E : in ET) is begin Append (Stack, New_Item => E); end;
function Top return ET is begin return Last_Element (Stack); end;
procedure Pop is begin Delete_Last (Stack); end;
Note that we can't use the front end as the top of the stack, because there's no Delete_First.
The Insert_N operation essentially opens up a "hole" in the middle of the internal array. It's more efficient to do it this way than inserting items one-at-a-time, because the sliding is done only once. For example, we can copy an array (or any other container) into a vector at some arbitary position like this:
procedure Copy
(A : in Array_Subtype;
V : in out Vector_Subtype; I : in Index_Type'Base) is
J : Index_Type'Base := I;
begin Insert_N (V, Before => I, Count => A'Length); -- dig the hole
for Index in A'range loop Replace_Element (V, J, By => A (Index)); -- fill the hole J := J + 1; end loop; ... end Copy;
There's no Delete_First operation, as a not-so-subtle reminder to developers that that's a potentially expensive operation. However, you can achieve the same effect by simply specifying the first index:
procedure Op (V : in out Vector_Subtype) is begin Delete (V, Index => First (V)); -- pop front element ... end;
You can think of Clear as "removing" the elements in the container, but of course it does not really remove them. The elements that were formerly active simply now become inactive.
In particular, the internal array is not altered, and no "finalization" of the active elements occurs. If this is required, then then user must effect this himself prior to clearing the vector. Here is one way to do that:
type Element_Access is access all Element_Subtype; for Element_Access'Storage_Size use 0;
function To_Access is new Generic_Element (Element_Access);
procedure Finalize (Element : in out Element_Subtype) is ...;
procedure My_Clear (V : in out Vector_Subtype) is begin for I in First (V) .. Last (V) loop Finalize (To_Access (V, I).all); end loop;
Clear (V); end My_Clear;
Here we use the Generic_Element selector function to get a variable view of the vector elements.
The internal array never shrinks, and it only expands under specific conditions. If you want to clear the vector and also deallocate the internal array, you can use the swap trick:
procedure Clear_And_Deallocate (V : in out Vector_Subtype) is
V2 : Vector_Subtype; -- internal array is null begin Swap (V, V2); end;
The internal array that belonged to V is swapped into V2, and the null array of V2 is swapped into V. When V2 is finalized, its internal array is deallocated. This also invokes Finalize for controlled elements.
The Resize operation can only be used to grow the internal array, not to shrink it. If for whatever reason you want more efficient use of storage, you can use the swap trick to allocate an array having the minimum size necessary to store the active elements:
procedure Reclaim_Storage (V : in out Vector_Subtype) is Temp : Vector_Subtype := V; begin Swap (V, Temp); end;
This copies all active elements in V to the temporary vector object Temp, which is allocated using a smaller internal array (presumably the smallest size necessary to store the elements, according to whatever algorithm the implementor has chosen). The new array is swapped with the original array, which is then deallocated when Temp is finalized.
If some sort of finalization of the last element is necessary prior to its "removal" by Delete_Last, the programmer is responsible for effecting this action prior to calling Delete_Last. As an example, suppose we have a vector whose elements are access objects, and we need to deallocate the element when it is "popped" from the vector. If we reuse our instantiation of Generic_Element from the example above, then we can do this:
procedure Pop (V : in out Container_Type) is
procedure Free is new Ada.Unchecked_Deallocation (T, T_Access); begin Free (To_Access (V, Last (V)).all); Delete_Last (V); end Pop;
The Front selector exists so that during reverse iteration over a vector, the index can "fall off the end" of the range onto the sentinel:
procedure Op (V : in Vector_Subtype) is I : Index_Subtype := Last (V); J : constant Index_Subtype := Front (V); begin while I /= J loop Process (E => Element (V, I)); I := Index_Type'Pred (I); end loop; end Op;
The First and Last selectors allow iteration over a vector analogous to iteration over an array, using the loop machinary provided by the language:
procedure Op (V : in Vector_Subtype) is procedure Process (E : in Element_Subtype) is ...; begin for I in First (V) .. Last (V) loop Process (E => Element (V, I)); end loop; end Op;
The Generic_Element function is very important, as it allows in-place modification of elements. For example, suppose we have a vector whose elements are lists, and we want to append an item to the list at a specified vector position. We can do that as follows:
type List_Access is access all List_Subtype; for List_Access'Storage_Size use 0;
function To_Access is new Generic_Element (List_Access);
procedure Append (V : List_Vectors.Container_Type; I : Index_Type'Base; E : Element_Subtype) is
List : List_Subtype renames To_Access (V, I).all; begin Append (List, New_Item => E); end;
It's often the case that during an insertion you don't have an item value to assign to the new element, and instead you want simply to reserve space for an element and then modify it directly. For example:
procedure Op (V : in out Vector_Subtype) is begin Insert (V, Before => Back (V)); -- no item
declare E : Element_Subtype renames To_Access (V, Last (V)).all; begin -- now modify E end; end Op;
Given our vector-of-lists from the earlier example, we could swap a list object with one of the list elements already in vector, like this:
procedure Swap
(V : in List_Vectors.Container_Type;
I : in Index_Type'Base; L : in out List_Subtype) is
VE : List_Subtype renames To_Access (V, I).all;
begin Swap (L, VE); end;
This would allow us to populate a list object, separate from a vector, and then add it to the vector without having to actually copy it:
procedure Op (V : in List_Vectors.Container_Type) is L : List_Subtype; begin Append (L, New_Item => E); ... -- populate list some more Insert (V, Before => Back (V)); -- create a new element in V Swap (V, Last (V), L); -- swap new element in V with L end;
The new list element that is appended to V during Insert is immediately swapped with the list object L. This effectively "moves" L onto V, without any copying. The element that was in V is moved into L, which is then finalized.
To swap two list elements in the same vector-of-lists, we could implement it this way:
procedure Swap
(V : in out List_Vectors.Container_Type;
I, J : in Index_Type'Base) is
IE : List_Subtype renames To_Access (V, I).all; JE : List_Subtype renames To_Access (V, J).all;
begin Swap (IE, JE); end;
If ordinary assignment of elements is acceptable, then Replace_Element allows array-like modification of vector elements:
procedure Op (V : in out Vector_Subtype) is I : Index_Subtype := ...; E : Element_Subtype := ...; begin Insert (V, Before => I, New_Item => E); ... -- modify E some more Replace_Element (V, Index => I, By => E); -- aka V(I) := E; end;
A.X.Y The Package Containers.Lists.Double.Unbounded
Linked-list containers turn out to be extremely useful especially when used with other containers, especially associative containers that preserve an order-relationship among elements. For example, suppose we have a histogram that counts the frequency of each word in a text file. We would use the string-key map, that has type String as the key and Natural as its Element_Type.
After we've exhausted the input, we want to display the words in frequency order. However, we can't iterate over the map directly, since it is ordered by key -- we need to order by element (here, a count).
We can do that very easily by creating a linked-list of map iterators, and then sorting the linked-list according to the element count. For example:
package List_Types is new Ada.Containers.ists.Double.Unbounded (Element_Type => String_Integer_Maps.Iterator_Type);
procedure Post_Process (Histogram : in String_Integer_Maps.Container_Type) is
List : List_Types.Container_Type;
procedure Process (I : in String_Integer_Maps.Iterator_Type) is begin Append (List, New_Item => I); end;
procedure Populate_List is new String_Integer_Maps.Generic_Iteration; -- use default name
begin -- Post_Process
Populate_List (Histogram);
... -- see below
end Post_Process;
Here we use the passive iterator for maps to populate the linked-list container object.
Now that we have the linked-list, we need to sort it, so we need an order relation. We want to sort the elements in reverse order, so that largest count is listed first in the output. We can define the order relation like this:
declare function "<" (L, R : String_Integer_Maps.Iterator_Type) return Boolean is begin return Element (L) > Element (R); -- yes end;
procedure Sort is new List_Types.Generic_Sort; -- use "<" default begin Sort (List); end;
We can do better though: suppose that for counts that are equal, we want to list the items in alphabetic order of the words. We only have fix our order relation to compare keys, too:
declare function "<" (L, R : String_Integer_Maps.Iterator_Type) return Boolean is begin if Element (L) = Element (R) then return Key (L) < Key (R); -- compare String else return Element (L) > Element (R); -- compare Integer end if; end;
procedure Sort is new List_Types.Generic_Sort; -- use "<" default begin Sort (List); end;
To display the results, all we have to do is iterate through the sorted list:
declare procedure Process (LI : List_Types.Iterator_Type) is MI : String_Integer_Maps.Iterator_Type := Element (LI); begin Put (Element (MI), Width => 0); -- the count Put (':'); Put (Key (MI)); -- the word New_Line; end;
procedure Print_Results is new List_Types.Generic_Iterator; -- accept default name begin Print_Results (List); end;
Et voila! If we only want to display the top 10 words, then we can use an active iterator, like this:
declare I : List_Types.Iterator_Type := First (List); J : constant List_Types.Iterator_Type := Back (List); begin Print_Results: for Index in 1 .. 10 loop exit when I = J;
declare K : constant String_Integer_Maps.Iterator_Type := Element (I); begin Put (Element (K), Width => 0); Put (':'); Put (Key (K)); New_Line; end;
Increment (List, I); end loop Print_Results; end;
As with all iterators, there's no need to pass an extra parameter to indicate that iteration should stop, since that functionality can be built using active iterators:
procedure Op (C : in Container_Type) is I : Iterator_Type := First (C); J : constant Iterator_Type := Back (C); begin while I /= J loop exit when Predicate (Element (I)); Process (Element (I)); Increment (I); end loop; end Op;
There are a couple of ways to actively iterate (here, in reverse) over a double list. One way is to start at the back sentinel and then stop at first:
procedure Op (C : in Container_Type) is I : Iterator_Type := Back (C); J : constant Iterator_Type := First (C); begin while I /= J loop Decrement (I); Process (Element (I)); end loop; end Op;
The other way is to start at last and then "fall off the end" from first onto the back sentinel. This works because a list has wrap-around semantics:
procedure Op (C : in Container_Type) is I : Iterator_Type := Last (C); J : constant Iterator_Type := Back (C); begin while I /= J loop Process (Element (I)); Decrement (I); end loop; end Op;
This even works in the other direction: for forward iteration you could start at back and navigate to last:
procedure Op (C : in Container_Type) is I : Iterator_Type := Back (C); J : constant Iterator_Type := Last (C); begin while I /= J loop Increment (I); Process (Element (I)); end loop; end Op;
A.X.Y The Package Containers.Maps.Hashed.Unbounded
It's often the case that you know apriori the total number of elements you intend to insert into the map. Under these circumstances you should always Resize the map first (similar to a vector container), and then perform the insertions. This preallocates a buckets array that is properly sized, and thus avoids the automatic rehashing that occurs during normal insertion to preserve the load factor. For example:
procedure Op (N : Natural) is Map : Map_Types.Container_Type; -- Size = 0
Iterator : Map_Types.Iterator_Type; Success : Boolean; begin Resize (Map, Size => N); -- Size >= N
for I in 1 .. N loop Insert -- no resizing will occur (Container => Map, Key => Get_Key (I), New_Item => Get_Element (I), Iterator => Iterator, Success => Success); end loop; ... end Op;
Note that Clear doesn't delete the buckets array -- it just deletes the nodes hanging off the buckets array. If you want to delete the buckets array too (thus setting the map's size to 0), then you can Swap with a temporary map object. (Map objects are default-initialized with a zero-length buckets array.)
The simplest and fastest way to iterate over all the elements in the map is to use one of the passive iterator:
procedure Op (Map : in Map_Types.Container_Type) is
procedure Process (I : in Map_Types.Iterator_Type) is K : Key_Subtype := Key (I); E : Element_Subtype := Element (I); begin ... -- do whatever end;
procedure Iterate is new Generic_Iteration; -- accept default name begin Iterate (Map); end;
You could implement this function yourself, by iterating over the list in each bucket of the hash table:
procedure Op (Map : in Map_Types.Container_Type) is
procedure Process (I : in Map_Types.Iterator_Type) is ...;
I : Map_Types.Iterator_Type;
begin
Iterate: for Index in 1 .. Size (Map) loop
I := First (Map, Index); -- head of list at buckets[index]
while I /= Null_Iterator loop Process (I); Increment (I); end loop;
end loop Iterate;
end Op;
If you want to walk the entire map in the more traditional way, then you can do it like this:
procedure Op (Map : in Map_Types.Container_Type) is
Index : Positive := 1; Iter : Iterator_Type;
I : Positive := 1;
begin
if Length (Map) = 0 then return; end if;
Find_First_Iter: loop Iter := First (Map, Index); exit when Iter /= Null_Iterator; Index := Index + 1; end loop Find_First_Iter;
Iterate: loop
Process (Iter); -- process I'th
I := I + 1;
exit when I > Length (Map);
Increment (Iter);
while Iter = Null_Iterator loop Index := Index + 1; Iter := First (Map, Index); end loop;
end loop Iterate;
end Op;
This latter locution allows you to walk a hashed map container while you're simultaneously walking another container.
generic algorithms are typically written to work with iterators this way:
generic type IT is private; with function Succ (I : IT) return IT is <>; with procedure Process (I : IT) is <>; with function "=" (L, R : IT) return Boolean is <>; procedure Generic_Algorithm (First, Back : in IT);
The implementation would look something like this:
procedure Generic_Algorithm (First, Back : in IT) is I : IT := First; begin while I /= Back loop ... Process (I); ... I := Succ (I); end loop; end Generic_Algorithm;
The benefit is that this algorithm will work with any "sequence of items," which just means any container with an iterator having the requisite properties, as specified in the generic formal region. The virtue of this approach is that it abstracts-away the container. The generic algorithm above (and others like it) works with all the containers in the library -- it even works for built-in array types.
To make this work with a map, we need only to override the default successor operation for map iterators and keep around some context:
procedure Op (Map : in Map_Types.Container_Type) is
Index : Positive := 1; I : Positive := 1;
function Succ (Iter : Iterator_Type) return Iterator_Type is Result : Iterator_Type := Map_Types.Succ (Iter); begin I := I + 1;
if I > Length (Map) then return Result; end if;
while Result = Null_Iterator loop Index := Index + 1; Result := First (Map, Index); end loop;
return Result; end Succ;
procedure Process (Iter : Iterator_Type) is ...;
procedure Algorithm is new Generic_Algorithm (Iterator_Type); -- accept defaults
First_Iter : Iterator_Type;
begin -- Op
if Length (Map) = 0 then return; end if;
Find_First_Iter: loop First_Iter := First (Map, Index); exit when First_Iter /= Null_Iterator; Index := Index + 1; end loop Find_First_Iter;
Algorithm (First => First_Iter, Back => Null_Iterator);
end Op;
In a POSIX OS that supports real-time signals, the OS will deliver a payload-carrying signal to the app. In the case of a socket, when I/O completes asynchronously, the OS delivers an RT signal that specifies the file descriptor of the socket whose I/O completed.
The problem is that I typically declare the socket as part the representation of some abstraction that gets allocated dynamically, and therefore I have no idea which object the socket belonged to, so I have no idea how to act on the information the OS is providing me.
The abstraction I have in mind looks like this:
package Sessions is
type Session_Type (<>) is limited private;
function Session_Access is access all Session_Type;
function Setup_Session return Session_Type; -- ctor
procedure Play (Session : access Session_Type; Stream : in String); ... procedure IO_Completion (Session : access Session_Type);
private
type Session_Type is limited record Socket : Socket_Type; ...; end Session_Type;
end Sessions;
What I need to do is correlate the file descriptor reported in the RT signal to a session object. With a map it's almost trivial. In the body I can instantiate the map like this:
function Hash_FD is
new Ada.Containers.Generic_Hash_Discrete (C.int); -- or whatever
package FD_Map_Types is new Ada.Containers.Maps.Hashed.Unbounded (Key_Type => C.int, Element_Type => Session_Access, Hash => Hash_FD);
Now I can declare a map object in the body:
package Sessions is ... FD_Map : FD_Map_Types.Container_Type;
When I allocate a new session object, this opens the socket. A socket object has a selector function to return its file descriptor. I use this as the key to insert the session object into the map:
function Setup_Session return Session_Access is Session : constant Session_Access := new Session_Type;
Iterator : FD_Map_Types.Iterator_Type; Success : Boolean; begin Open (Session.Socket, ...);
Insert (Container => FD_Map, Key => FD (Session.Socket), New_Item => Session, Iterator => Iterator, Success => Success); ... return Session; end;
Now that the session object has inserted itself into the map, I can use map lookup to find that session when I receive a signal. Something like:
procedure Handle_Signal (Siginfo : in Siginfo_Type) FD : constant C.int := Siginfo.FD; Iter : constant Iterator_Type := Find (FD_Map, Key => FD); begin if Iter /= Null_Iterator then IO_Completion (Element (Iter)); end if; end Handle_Signal;
and then the session object reacts to the I/O completion accordingly.
A.X.Y The Function Containers.Maps.Hashed.Unbounded.Unchecked_Modification
Suppose we have a key declared this way:
type Key_Type is record X : Integer; Y : Integer; end record;
It's going to be used to instantiate a map container. The hash function for keys is defined as follows:
function Hash_On_X (Key : Key_Type) return Integer'Base is begin return Ada.Containers.Hash_Integer (Key.X); -- or whatever end;
Note that the hash function for keys only depends on the value of the X component. We instantiate the map in the normal way:
package Map_Types is new Ada.Containers.Maps.Hashed.Unbounded (Key_Type, Element_Type, Hash => Hash_On_X);
Map : Map_Types.Container_Type;
Now let's insert a key into the map:
procedure Insert_42_1776 (I : out Iterator_Type) is K : Key_Type := (X => 42, Y => 1776); B : Boolean; begin Insert (Map, K, I, B); end;
Now let's suppose we need to change the Y component of our key. One way to do that would be to delete it from the map, change the value, and then (re)insert it, like this:
procedure Insert_42_2001 (I : in out Iterator_Type) is K : constant Key_Type := Key (I); E : constant Element_Type := Element (I); B : Boolean; begin Delete (Map, I); K.Y := 2001; -- modify key Insert (Map, K, E, I, B); end;
What we did was make a copy of the key/element in the map, then delete the node containing the original key/element pair, then turn around and immediately insert the key/element back into the map. This is silly. What we really wanted to do was modify the key object in place.
The library provides such a parachute function. It's the generic child procedure Unchecked_Modification. It returns an access value that designates a variable view of the key:
with Ada.Containers.Maps.Hashed.Unbounded.Unchecked_Modification;
type Key_Access is access all Key_Type; for Key_Access'Storage_Size use 0;
package Map_Types is ...;
function Modify_Key is new Map_Types.Unchecked_Modification (Key_Access);
Now that we have this modifier, we can rewrite our little op to be much more simple and efficient:
procedure Insert_42_2001 (I : in out Iterator_Type) is K : Key_Type renames Modify_Key (I).all; begin K.Y := 2001; -- that's all! end;
Clearly, modifying keys indiscriminantly could break the map. However, sometimes we have the need to modify a key, and here it's perfectly safe, because we only change Y, not X. So this is a benign change as far as the map is concerned.
A.X.Y The Package Containers.Maps.Hashed.Strings.Unbounded
Hashed maps with type String as the key are nearly ubiquitous. The canonical example is of course the word-frequency problem, in which "words" (using some suitable definition of delimiter) are counted as they are scanned in the input stream.
We can solve this problem easily using a map with string as the key and subtype Natural as the element:
with Ada.Containers.Hash_String;
package Wordcount_Maps is new Ada.Containers.Maps.Hashed.Strings.Unbounded (Element_Type => Natural, Hash => Ada.Containers.Hash_String); -- case-sensitive
Map : Wordcount_Maps.Container_Type;
Here we've specified the hash function for strings provided by the library. The scanning phase looks like this:
Open (File, In_File, Name);
Scan_Lines: while not End_Of_File (File) loop
Get_Line (File, Line, Line_Last);
Line_First := Line'First;
Scan_Line: loop
Find_Token (Line (Line_First .. Line_Last), ...);
exit when Word_Last = 0;
-- the interesting part happens here: Insert (Word => Line (Word_First .. Word_Last));
Line_First := Word_Last + 1;
end loop Scan_Line;
end loop Scan_Lines;
Now all we have to do is implement Insert. That function looks like this:
procedure Insert (Word : String) is
Iterator : Wordcount_Maps.Iterator_Type; Success : Boolean;
type Count_Access is access all Natural;
function To_Element_Access is new Wordcount_Maps.Generic_Element (Count_Access);
begin -- Insert
Insert (Container => Map, Key => To_Lower (Word), New_Item => 0, -- yes Iterator => Iterator, Success => Success);
declare N : Natural renames To_Element_Access (Iterator).all; begin N := N + 1; end;
end Insert;
Map (and set) insertion works conditionally. It searches the container (here, in the list of the buckets array at the index computed from the hash-value of the key), to determine whether there is an equal key already in the map.
Note that in the example above, the New_Item parameter has the value 0. This is deliberate. What happens is that if the word is already in the map, then the insertion "fails" in the sense that no new node is allocated. Rather, Insert reports the fact that the key was already in the map (by returning the value False for Success), and an iterator that designates the node with the matching key.
But not inserting a new node is exactly the behavior we want. In the case of a word already in the map, the iterator returned designates an existing word/count pair, whose count is non-zero. When we alias the count object, we simply increment its value.
However, the word might not be in the map, in which case the insertion "succeeds," which means a new node is inserted whose element is initialized to the value of New_Item, which here is 0. Iterator designates the newly-inserted element (really, it designates the node containing that key/element pair). When we alias the element, the count has the value 0, and so by incrementing it the count gets set to the value 1.
Conditional insertion is a necessary feature of any efficient map abstraction. It makes no sense to search for the element (via Find, say) to determine whether it's in the map, and if it's not in the map call a separate operation to insert it. This would be horribly inefficient because the lookup done by insert would only duplicate the lookup just done by the search.
To dump the contents of the map, you can use the passive iterator:
declare procedure Process (I : in Wordcount_Maps.Iterator_Type) is begin Put (Key (I)); Put (':'); Put (Element (I)); New_Line; end;
procedure Dump is new Wordcount_Maps.Generic_Iteration; -- "Process"
begin Dump (Map); end;
This would display the words in their order in the hashed map. That's probably not what you want (especially for a well-performing hash table, which would scatter keys everywhere), which is to display them in order by frequency. I showed how to do that in the doubly-linked list example. What we did was to populate the list with iterators that designate the map entries, and then sort the list:
declare List : List_Subtype;
procedure Process (I : Wordcount_Maps.Iterator_Type) is begin Append (List, New_Item => I); end;
procedure Populate_List is new Wordcount_Maps.Generic_Iteration; -- "Process" begin Populate_List (Map); ... -- see doubly-linked list end;
As with all containers, it's usually simpler and more efficient to use the passive iterators if you're going to traverse all the elements in the container. The library is often used by making a lot of little on-the-fly passive-iterator instantiations, as in the examples above.
As another example, consider a client that connects to a streaming media server to play a video. The server opens the file and then transmits frames to the client. Ultra-efficient file I/O is usually done by memory-mapping the sections on disk. Typically, a server maintains its own file cache, so that many clients streaming the same file can share the memory-mapped sections.
When the client request comes in, the server must look up the file by name in order to see if it's in cache. If it's already in cache, then we increment a reference count. If it's not, we create some context for the file and create a new entry for it in the cache.
Suppose the context type looks like this:
type Context_Type is limited record File : File_Type; Ref_Count : Natural; ...; end record;
type Context_Access is access Context_Type;
Our cache is just a map, indexed by file name:
package File_Cache_Types is new Ada.Containers.Maps.Hashed.Strings.Unbounded (Element_Type => Context_Access, Hash => Ada.Containers.Hash_String_Case_Insensitive, Is_Equal_Key => Ada.Containers.Equal_String_Case_Insensitive);
File_Cache : File_Cache_Types.Container_Type;
The naive way to implement the lookup is:
procedure Setup (Name : in String) is
Iter : File_Cache_Types.Iterator_Type := File (File_Cache, Key => Name);
Success : Boolean;
Context : Context_Access;
begin -- Setup
if Iter = Null_Iterator then
Context := new Context_Type; Context.Ref_Count := 0; ... -- init Context
Insert (Container => File_Cache, Key => Name, New_Item => Context, Iterator => Iter, Success => Success);
else
Context := Element (Iter);
end if;
Context.Ref_Count := Context.Ref_Count + 1; ... -- use Context
end Setup;
The problem is that we're duplicating work: we first searched for the file context in the cache, and if it wasn't found we insert a new entry, which just searches again. The correct way to do this is as follows:
procedure Setup (Name : in String) is
Iter : File_Cache_Types.Iterator_Type; Not_In_Cache : Boolean; Context : Context_Access;
begin
Insert (Container => File_Cache, Key => Name, New_Item => null, -- yes Iterator => Iter, Success => Not_In_Cache);
if Not_In_Cache then
pragma Assert (Element (Iter) = null);
Context := new Context_Type; Context.Ref_Count := 0; ... -- init context
Replace_Element (Iter, By => Context);
else
Context := Element (Iter);
end if;
Context.Ref_Count := Context.Ref_Count + 1; ... -- use context
end Setup;
Here we make an insertion attempt, by trying to insert a null context into the map. If it's already in the map, then the insertion fails, but that's just what we want to happen, because we wish to share the file already in cache. If it's not in the map, the insertion succeeds, by creating a slot for this file (the context is null), which we just fill in with a newly-allocated context object.
In the RTSP protocol, requests are sent to a server in (clear) text. To create a session, a client connects to the server and sends it a SETUP request, e.g.
SETUP rtsp://mheaney/thematrix.avi RTSP/1.0 Transport: ...
Each RTSP session has a "session id," which is a random string of characters at least 8 characters long. When a SETUP request doesn't specify a session id, this is interpreted to mean that the client wishes to create a new session.
On the server side, it allocates a session object (described above), and generates a session id for it. The representation of the session object looks like this:
type Session_Type is limited record Id : String (1 .. 8); ...; end record;
And the session constructor looks like:
function Setup_Session return Session_Access is Session : constant Session_Access := new Session_Type; begin Generate_Id (Session.Id); ... -- see below end;
The server responds by including the session id in the response:
RTSP/1.0 200 OK Session: HAiye8-r
And thereafter, a client sends requests to a session by specifying a session id header:
PLAY rtsp://mheaney/thematrix.avi RTSP/1.0 Range: npt=101.42- Session: HAiye8-r
The server-side problem is this. When the server receives the request from the client, it parses the request and looks for a session id. The problem then becomes finding the session object that is associated with that unique id. There might very well be hundreds of session objects, so whatever method we use has to run fast. (The is a real-time streaming server, and RTSP request/response overhead must be kept to a minimum.)
What we do is declare a string-key map object that uses the session id as the key, and a Session_Access as the element, like this:
package Id_Maps is new Ada.Containers.Maps.Hashed.Strings.Unbounded (Element_Type => Session_Access, Hash => Ada.Containers.Hash_String); -- case-sensitive
Id_Map : Id_Maps.Container_Type; use Id_Maps;
When the session is allocated, we insert the id/session pair into the map like this:
function Setup_Session return Session_Access is Session : constant Session_Access := new Session_Type;
Iterator : Id_Map_Types.Iterator_Type; Success : Boolean; begin Generate_Id (Session.Id);
Insert (Container => Id_Map, Key => Session.Id, New_Item => Session, Iterator => Iterator, Success => Success); ... return Session; end;
When a client issues a request, we parse out the session id, and then forward the command to the session object associated with that session id key:
procedure Play
(Session_Id : in String;
NPT_Range : in NPT_Range_Type; RTSP_Status : out RTSP_Status_Type) is
Iter : constant Iterator_Type := Find (Id_Map, Key => Session_Id);
Session : Session_Access;
begin if Iter = Null_Iterator then RTSP_Status := RTSP.Session_Not_Found; return; end if;
Session := Element (Iter);
Play (Session, NPT_Range, RTSP_Status); end;
The Id map we instantiated in the previous example takes type Session_Access as the element type. The constructor (repeated here) looks like this:
function Setup_Session return Session_Access is Session : constant Session_Access := new Session_Type;
Iterator : Id_Maps.Iterator_Type; Successs : Boolean; begin Generate_Id (Session.Id);
Insert (Container => Id_Map, Key => Session.Id, New_Item => Session, Iterator => Iterator, Success => Success); ... return Session; end;
Here we first allocate a session object, and then separately (during Insert) allocate a node (in the Id_Map container) whose element is the pointer to the session object. If you think about it, this means there are actually two separate allocations: one for the element (here, a session object), and another for the node.
If a container were to support element types that were limited, then we could rewrite the example like this:
package Id_Maps is new Ada.Containers.Maps.Hashed.Strings.Unbounded_Limited (Element_Type => Session_Type, -- not an access type Hash => Ada.Containers.Hash_String);
Id_Map : Id_Maps.Container_Type;
That would allow us to allocate just a node, and then implement the constructor to return a pointer that designates the session object element on the node itself:
function To_Access is
new Id_Maps.Generic_Element (Session_Access);
function Setup_Session return Session_Access is
I : Id_Maps.Iterator_Type; B : Boolean;
Id : Id_Subtype; Session : Session_Access; -- no allocation here
begin
Generate_Id (Id);
Insert (Container => Id_Map, Key => Id, -- specify key only; no element Iterator => I, Success => B;
Session := To_Access (I); -- point to node's element ... return Session;
end Session_Setup;
It doesn't make any difference to the caller of Setup_Session how the session object it returns is allocated. Indeed the whole point of that function is to hide that very detail.
In the interest of keeping things small, this API does not specify any containers that support limited elements. But here you get a taste of how containers that do support limited elements would work. It's nice because we can push the responsibility for "deallocating" the element onto the container (when the node is deleted, it contains the element, so the element goes away too). Compare this to our other examples, which require explicit action by the client to deallocate the object (because the container element is just an access object that designates the "real" object).
When we create a session object, we insert a pointer to the session object in the Id_Map. The complementary problem is how to handle deletion of the session object. Suppose we have a function like this:
procedure Free (X : in out Session_Access);
What's the best way to remove the object from the map? Since the session knows its own Id, it can use the key-form of Delete:
procedure Free (X : in out Session_Access) is begin if X /= null then Delete (Id_Map, Key => X.Id); Deallocate (X); end if; end;
Another option is to use the iterator-form of Delete. What we can do is cache the iterator returned by Insert as part of the state of the session object:
function Setup_Session return Session_Access is Session : constant Session_Access := new Session_Type; Success : Boolean; begin Generate_Id (Session.Id);
Insert (Container => Id_Map, Key => Session.Id, New_Item => Session, Iterator => Session.Id_Map_Iter, Success => Success);
pragma Assert (Success); pragma Assert (Key (Session.Id_Map_Iter) = Session.Id); pragma Assert (Element (Session.Id_Map_Iter) = Session); ...
return Session; end Setup_Session;
Now we can implement Free this way:
procedure Free (X : in out Session_Access) is begin if X /= null then Delete (Id_Map, Iterator => X.Id_Map_Iter); Deallocate (X); end if; end;
This turns out to be a very common idiom. In the body of a package that declares a type, you declare a set or map container to keep track of instances of the type. As part of its representation, the type includes an iterator that designates the node that contains this instance. When the object is finalized, it deletes itself from the container.
In the examples we've seen, the session object inserts itself into the map during the constructor function Setup_Session, and deletes itself during the destructor operation Free. If the type is controlled, another possibility is to do the insertion during Initialize, and the deletion during Finalize. This technique would work even for stack-allocated (controlled) objects.
Let's pretend Free doesn't remove the designated session object from the map, but rather has its traditional semantics of merely deallocating the object. To shutdown all the sessions, we could do this:
Id_Map : Id_Map_Types.Container_Type; ...
procedure Shutdown_Sessions is
procedure Process (I : Id_Map_Types.Iterator_Type) is Session : Session_Access := Element (I); begin Free (Session); -- free a copy of the map element end;
procedure Free_Sessions is new Id_Map_Types.Generic_Iteration; -- accept default
begin -- Shutdown_Sessions
Free_Sessions (Id_Map);
Clear (Id_Map);
end Shutdown_Sessions;
The passive iterator visits all the sessions in the map and Free's them. We then Clear the map, which sets its length to 0.
Note that in the example above all the session objects are deallocated, but all the map elements remain non-null. Here it doesn't really make any difference because we immediately clear the map. Another slightly more safe techique would be to use Generic_Element to get a variable view of the element, and call Free on the map element directly:
procedure Shutdown_Sessions is
function To_Access is new Id_Map_Types.Generic_Elemenets (Session_Access);
procedure Process (I : Id_Map_Types.Iterator_Type) is Session : Session_Access renames To_Access (I).all; begin Free (Session); -- free map element directly end;
procedure Free_Sessions is new Id_Map_Types.Generic_Iteration; -- accept default
begin -- Shutdown_Sessions
Free_Sessions (Id_Map);
Clear (Id_Map);
end Shutdown_Sessions;
A.X.Y The Package Containers.Sets.Sorted.Unbounded
Suppose we have a set and want to copy the elements from the set into an array. Here's one way to do it:
procedure Op (S : in Integer_Sets.Container_Type) is
A : Integer_Array (1 .. Length (S)); I : Integer_Sets.Iterator_Type := First (S); J : Positive := A'First; begin while I /= Back (S) loop A (J) := Element (I); Increment (I); J := J + 1; end loop; ... end Op;
Here we're incrementing both the array index and the set iterator manually. However, when you're iterating over two containers simultaneously, you can often let one or the other drive the iteration, so that only one position needs to be incremented manually.
We can change the example use a built-in for loop:
procedure Op (S : in Integer_Sets.Container_Type) is
A : Integer_Array (1 .. Length (S)); I : Integer_Sets.Iterator_Type := First (S); begin for J in A'range loop A (J) := Element (I); Increment (I); end loop; ... end Op;
This lets the array drive the iteration. In general, however, you should use a passive iterator in preference to an explicit loop. The reason is that if a container knows that its elements are being traversed sequentially, then it can use a traversal algorithm that takes advantage of that container's unique representation. The algorithm above might run faster if we do this:
procedure Op (S : in Integer_Sets.Container_Type) is
A : Integer_Array (1 .. Length (S)); J : Positive := A'First;
procedure Process (I : Integer_Sets.Iterator_Type) is begin A (J) := Element (I); J := J + 1; end;
procedure Fill_Array_A is new Integer_Sets.Generic_Iteration; -- accept default begin Fill_Array_A (S); ... end Op;
This lets the set drive the iteration.
Let's continue the streaming server examples from earlier. In TCP, a client "connects" to a server, who is "listening" on a port known to the client. When the server "accepts" the connection, this establishes a dedicated connection with that client. We can reify this in code as follows:
package Connections is
type Connection_Type (<>) is limited private;
type Connection_Access is access Connection_Type;
function Accept_Connection (Socket : Socket_Type) return Connection_Access; -- the ctor for connection objects ... end Connections;
We have a need to keep track of all of our current connections. So when we create a new connection object, what we do is insert it in a set:
package body Connections is
function "<" (L, R : Connection_Access) return Boolean is begin return L.all'Address < R.all'Address; end;
package Connection_Sets is new Ada.Containers.Sets.Sorted.Unbounded (Connection_Access);
Connection_Set : Connection_Sets.Container_Type;
function Accept_Connection (Socket : Socket_Type) return Connection_Access is
Connection : constant Connection_Access := new Connect_Type;
Iterator : Connection_Sets.Container_Type; Success : Boolean; begin Insert (Container => Connection_Set, New_Item => Connection, Iterator => Iterator, Success => Success); ... return Connection; end; ... end Connections;
When a new connection object is allocated, it is inserted into the Connection_Set. Here insertions will always succeed because each allocated object has a unique access value.
Now let's suppose we want to shutdown a specific connection. We can do it like this:
procedure Shutdown (X : in out Connection_Access) is begin if X /= null then Delete (Connection_Set, Item => X); Free (X); end if; end Shutdown;
Here we use the item-form of Delete to simply remove the item from the set, and then deallocate it.
Now let's suppose we want to shutdown the entire system, and so we need to clear out all of the connection objects. We could do it like this:
procedure Shutdown_Connections is X : Connection_Access; begin while not Is_Empty (Connection_Set) loop X := First_Element (Connection_Set); Shutdown (X); end loop; end Shutdown_Connections;
Another technique would be to use an active iterator, like this:
procedure Shutdown_Connections is I : Iterator_Type := First (Connection_Set); J : constant Iterator_Type := Back (Connection_Set);
X : Connection_Access; begin while I /= J loop X := Element (I); Delete (Connection_Set, Iterator => I); Free (X); end loop; end Shutdown_Connections;
Here we use the iterator-form of Delete. Delete returns the successor of the iterator deleted, so eventually the loop terminates.
In other exmples, we have been silent about the operation of Generate_Id, which makes a session id for us comprising a sequence of 8 random characters. One of our requirements for a session id is that it must be unique among all the sessions currently being streamed by this server.
Even if we use a random number generator to synthesize characters, we still must check to ensure that this session id is unique. The obvious way is to use Find to see if it's in the already:
procedure Generate_Id (Id : out Id_Subtype) is Iter : Id_Maps.Iterator_Type; begin loop Synthesize_Random_String (Id); Iter := Find (Id_Map, Key => Id); exit when Iter = Null_Iterator; -- good: not found end loop; end Generate_Id;
The constructor for session objects generates a unique session id, and then uses the id as the key when inserting the new session object:
function Setup_Session return Session_Access is
I : Id_Maps.Iterator_Type; B : Boolean;
Id : Id_Subtype; begin Generate_Id (Id);
Insert (Container => Id_Map, Key => Id, Iterator => I, Success => B; ... end Setup_Session;
This works, but we can do better. The problem is that Insert duplicates the work done just earlier by Find, when checking the newly-synthesized id for uniqueness.
Find is a bit of a sledgehammer: it tells us that the key isn't already in the map, but it doesn't tell us anything else. What would like to ask the container is, if the key isn't in the map, can you tell me who its neighbor would be?
The function we're interested in is called Lower_Bound, which searches the container for the smallest key in the map not less than some key. It satisfies the invariant that
Key <= Lower_Bound (Container, Key)
The result of Lower_Bound is an iterator that designates the node that matches Key, if indeed Key is already in the map, or an iterator that designates its successor, if Key is not in the map. To disambiguate which case we have we only need to make a simple test:
Iter := Lower_Bound (Container, Key);
if Key < Iter then ... -- no match: Key is not in the map else ... -- match: Key is equivalent to Key (Iter) end if;
Let's rewrite our Generate_Id procedure so that it returns this extra information:
procedure Generate_Id
(Id : out Id_Subtype;
Hint : out Id_Maps.Iterator_Type) is
begin loop Synthesize_Random_String (Id); Hint := Lower_Bound (Id_Map, Key => Id); exit when Hint = Back (Id_Map); -- no match exit when Id < Hint; -- no match end loop; end Generate_Id;
Now we can rewrite our constructor to use this new information during insertion:
function Setup_Session return Session_Access is
I : Id_Maps.Iterator_Type; B : Boolean;
Id : Id_Subtype; Hint : Id_Maps.Iterator_Type; begin Generate_Id (Id, Hint);
Insert (Container => Id_Map, Position => Hint, Key => Id, Iterator => I, Success => B;
pragma Assert (B); pragma Assert (Succ (I) = Hint); ... end Setup_Session;
Here we use the insert-with-hint procedure, which specifies where to begin searching for a matching Id. Since Hint is the result of Lower_Bound, we know the hint is useful and therefore the insertion has amortized constant time. It's hard to beat that!
This is a great because we can test whether insert will succeed, and then reuse the results our test so that the insertion itself has essentially no cost. Thus we have a way to first search for a key and then insert it later, without incuring any penalty.
When a client issues a play request, he can specify the time from which play begins. This lets him jump to any position in the file and play from that point. Let's assume a video frame index is implemented as a sorted set, with elements that look like this:
0 9 34 42 50 78 85 ...
where the numbers above represent the time of the leading edge of each video frame in the file.
If the play request specifies a time, we have to find the frame that "goes with" that time, which means the frame whose leading edge comes at or before the time specified.
This is kind of like a floor function. You might think you could use Lower_Bound to do it, like this:
function Floor (Time : Time_Type) return Iterator_Type is begin return Lower_Bound (Video_Index, Key => Time); end;
This would work if the key matched a value already in the index, for example:
Floor (42) = Lower_Bound (VI, 42) = 42
The problem is if the key isn't in the index. Lower_Bound returns the smallest key in the container equal to or greater than a key, e.g.
Floor (45) = Lower_Bound (VI, 45) = 50
But this isn't what we want. The "floor" of 45 is 42, not 50.
The solution is to use Upper_Bound, which returns the smallest key in the container greater than a key, and then back up one position:
function Floor (Time : Time_Type) return Iterator_Type is begin return Pred (Upper_Bound (VI, Key => Time)); end;
This works for all key values:
Floor (42) = Pred (UB (VI, 42)) = Pred (50) = 42 Floor (45) = Pred (UB (VI, 45)) = Pred (50) = 42
And now all is well. Note that it doesn't matter if you specify some large key value that is greater than every key in the set. In that case Upper_Bound returns Back, and the Pred of Back is Last, which is just what we want.
The previous two examples were intended to demonstrate the utility of the Lower_Bound and Upper_Bound functions, and to show how insert-with-hint can be used to improve performance. Another way to efficiently generate a unique session id and insert it into the map is to simply check whether the insertion succeeded:
function Setup_Session return Session_Access is
Iterator : Id_Maps.Iterator_Type; Success : Boolean;
Id : Id_Subtype;
begin -- Setup_Session
loop
Generate_Id (Id);
Insert (Container => Id_Map, Key => Id, Iterator => Iterator, Success => Success;
exit when Success;
end loop; ... end Setup_Session;
Here we don't need a separate call to Find, because the regular Insert operation does a search anyway.
An interesting question is, What is a set? In the philosophy of this library, the answer is "any sorted container."
Let's say we have two containers. One is a sorted set. The other is a linked-list that has been sorted according the same order relation as the set. Suppose we want to take the union of the elements in both containers and then print the resulting "set" in sort order.
We have stated in earlier examples that generic algorithms abstract-away the containers. We see the benefit of such an approach now, because we need the union of elements in two different kinds of containers.
The union of a set and a sorted list can be done like this:
procedure Print_Union
(Set : in Set_Types.Container_Type;
List : in List_Types.Container_Type) is
procedure Print (E : in Element_Subtype) is ...;
procedure Process (SI : Set_Types.Iterator_Type) is begin Print (Element (SI)); end;
procedure Process (LI : List_Types.Iterator_Type) is begin Print (Element (LI)); end;
function Is_Less (SI : Set_Types.Iterator_Type; LI : List_Types.Iterator_Type) return Boolean is begin return SI < Element (LI); -- set has "<" ops for iter_t end;
function Is_Less (LI : List_Types.Iterator_Type; SI : Set_Types.Iterator_Type) return Boolean is begin return Element (LI) < SI; end;
procedure Union is new Generic_Set_Union_2 -- liberal definition of "set" (Set_Types.Iterator_Type, List_Types.Iterator_Type); -- accept defaults
begin -- Print_Union
Union (First (Set), Back (Set), First (List), Back (List));
end Print_Union;
The action of the union algorithm is to print all the elements in both the set and list containers, in element-order. The generic algorithm itself just manipulates iterators. It doesn't know anything about containers or even about elements. A very powerful idea, indeed!
A.X.Y The Package Containers.Sets.Sorted.Unbounded.Generic_Keys
One of the canonical container examples is the set-of-employees. Suppose we have an employee type defined this way:
type Employee_Type is record SSN : SSN_Type; -- social security no. Name : Name_Type; Home_Address : Home_Address_Type; ...; end record;
To make a set, we need to establish an order relationship for its elements. Since each social security number is presumably unique (unless your identity has been stolen), we can use that to define an order for Employee_Type:
function "<" (L, R : Employee_Type) return Boolean is begin
return L.SSN < R.SSN;
end;
This allows us to instantiate the set in the normal way:
package Employee_Sets is new Ada.Containers.Sets.Sorted.Unbounded (Employee_Type);
Employees : Employee_Sets.Container_Type;
When someone gets a job at our firm, we add them to our little database as follows:
procedure Hire (Name : Name_Type; SSN : SSN_Type; ...) is
Employee : Employee_Type; begin Employee.SSN := SSN; Employee.Name := Name; ... Insert (Employees, New_Item => Employee); end;
Now let's suppose that we need to modify some information for an employee. Like a map, a set orders its elements in key order, except that for a set the element is its own key. In the example here, the key is really the SSN part of the employee object.
Suppose we only know the employee's social security number. How do we find that employee? Remember that the "key" of a set is just the element itself. One way is to synthesize a dummy employee object, and then look it up by element type:
procedure Change_Address
(SSN : SSN_Type;
New_Home : Home_Address_Type) is
Iter : Iterator_Type;
begin declare Dummy : Employee_Type; begin Dummy.SSN := SSN; Iter := Find (Employees, Item => Dummy); end;
if Iter /= Null_Iter then ...; end;
But this is kind of a hack. I don't really want to make a dummy element just to look up the real element. For many types synthesizing a dummy object this way might not even be possible.
A much more elegant technique is to use the generic child package Generic_Keys, which allows you to explicitly name the key-part of the element. We can instantiate that package like this:
function "<"
(Employee : Employee_Type;
SSN : SSN_Type) return Boolean is
begin return Employee.SSN < SSN; end;
function "<" (SSN : SSN_Type; Employee : Employee_Type) return Boolean is begin return SSN < Employee.SSN; end;
package SSN_Keys is new Employee_Sets.Generic_Keys (SSN_Type);
With this new package we can now look up the employee by his SSN directly:
procedure Change_Address
(SSN : SSN_Type;
New_Home : Home_Address_Type) is
Iter : Iterator_Type := Find (Employees, Key => SSN);
begin if Iter /= Null_Iter then ...; end;
OK. That problem was solved. Here's our next problem. We want to modify the employee object this way:
declare E : Employee_Type renames To_Access (Iter).all; begin E.Home_Address := New_Home; end;
This is exactly how we'd do it if we were using a map instead of a set. The To_Access function is an instantiation of the Generic_Element selector function:
type Employee_Access is access all Employee_Type
function To_Access is new Employee_Sets.Generic_Element (Employee_Access);
However, this code fragment won't compile, because Generic_Element only accepts an access constant type, like this:
type Employee_Constant_Access is access constant Employee_Type
function To_Constant_Access is new Employee_Sets.Generic_Element (Employee_Constant_Access);
This now compiles, but it's not what we want either, because we need a variable view of the employee object in order to modify it. After all, we're trying to change his address.
The reason the set abstraction doesn't let you modify elements (very easily, anyway -- keep reading) is that elements have a strict order, and if you were to change an element's value such that it was no longer in order then you'd break the set.
However, here we have a legitimate reason to modify an element in place. We're not trying to change his social security number (which is what defines the "order" for employees in this set), just his address, so we should be allowed to make this modification.
To accomodate this need each of the associative containers has a generic child subprogram to permit the key to be modified. It's up the the developer to ensure that his modifications are benign. The new instantion looks like this:
with Ada.Containers.Sets.Sorted.Unbounded.Unchecked_Modification;
package Employee_Sets is ...;
type Employee_Access is access all Employee_Type
function To_Access is new Employee_Sets.Unchecked_Modification (Employee_Access);
This is a sharp knife, but if we promise to be careful then we're allowed to say:
declare E : Employee_Type renames To_Access (Iter).all; begin E.Home_Address := New_Home; -- OK end;
And now all is well.
Now let's say that the employee's wallet was stolen, which contained his social security card. In order to prevent identity theft, he needs to apply for a new social security number, and then change his entry in the database.
Clearly we can't use the technique we just showed, because changing his SSN would break the database. In this case we need to copy him out of the set, change the value of the SSN field, and then (re)insert him:
procedure Change_SSN
(Old_SSN : SSN_Type;
New_SSN : SSN_Type) is
Iterator : Iterator_Type := Find (Employees, Key => Old_SSN); Success : Boolean;
begin if Iterator /= Null_Iterator then declare Employee : Employee_Type := Element (Iterator); begin Employee.SSN := New_SSN; Insert (Employees, Employee, Iterator, Success); end; end if; end Change_SSN;
Suppose now we want a list all the employees in the firm. One way to do it is like this:
procedure Display is
procedure Process (I : in Employee_Sets.Iterator_Type) is E : Employee_Type renames To_Constant_Access (I).all; begin Put ("Name: "); Put (E.Name); Put ("SSN: "); Put (E.SSN); ...; end;
procedure Print is new Employee_Sets.Generic_Iteration; -- "Process" begin Print (Employees); end;
However, this will list employees in order of their social security number. This is probably not what we want, which is to print employees in order of name.
One way would be to copy the elements into some other container, which is sorted according to the criterion we desire. However, if elements are large or otherwise not easily copied, then this is not not really an option.
A much better way is not to copy elements directly but rather to copy iterators that designate those elements. Here we use linked-list, which comes with its own sort operation:
package List_Types is new Ada.Containers.Lists.Double.Unbounded (Element_Type => Employee_Sets.Iterator_Type);
procedure Display is
List : List_Types.Container_Type;
begin -- Display
declare procedure Process (I : Employee_Sets.Iterator_Type) is begin Append (List, New_Item => I); end;
procedure Populate_List is new Employee_Sets.Generic_Iteration; -- "Process" begin Populate_List (Employees); end;
declare function "<" (L, R : Employee_Sets.Iterator_Type) return Boolean is
EL : Employee_Type renames To_Constant_Access (L).all; ER : Employee_Type renames To_Constant_Access (R).all; begin return EL.Name < ER.Name; end;
procedure Sort is new List_Types.Generic_Sort; -- "<" begin Sort (List); end;
declare procedure Process (I : in List_Types.Iterator_Type) is J : Employee_Sets.Iterator_Type := Element (I); E : Employee_Type renames To_Constant_Access (J).all; begin Put ("Name: "); Put (E.Name); Put ("SSN: "); Put (E.SSN); ...; end;
procedure Print_Employees is new List_Types.Generic_Iteration; -- "Process" begin Print_Employees (List); end;
end Display;
First we use the passive iterator for sets to insert an iterator designating every employee into the linked-list container. Next we define an order for relation for list elements, which here are just set iterators. We wish to print employees in order of name, so the order relation for iterators is defined in terms of the names of the employees designated by the iterators.
Note carefully that we didn't use the element selector for iterators:
EL : Employee_Type := Element (L); -- wrong!
This would make a copy of the employee designated by the iterator, which is not what we want at all. That would undermine our reason for sorting set iterators instead of set elements. So we used our element access selector, which returns an access object. Here we just use the plain selector, that returns an access object with a constant view, since we don't need to modify the employee -- only read it.
Now that the employees (really, iterators that designate the employees) have been sorted, we use the passive iterator for lists to traverse all the set iterators, and print each employee (in name order) in turn.
In you were paying very careful attention to the Id_Map hashed-map example, you might have realized that since the session object (which was the element of the map) had an Id field, we were in fact duplicating the Id object, since it's also stored as the key-part of the map entry.
In turns out we didn't really need to use a map. We could have used a set, and instantiated the generic child package Generic_Keys using type String as the formal Key_Type.
function "<" (L, R : Session_Access) return Boolean is begin
return L.Id < R.Id;
end;
package Session_Set_Types is new Ada.Containers.Sets.Sorted.Unbounded (Session_Access);
-- instead of Id_Map, use a set to store sessions: Session_Set : Session_Set_Types.Container_Type;
function "<" (Session : Session_Access; Id : String) return Boolean is begin return Session.Id < Id; end;
function "<" (Id : String; Session : Session_Access) return Boolean is begin return Id < Session.Id; end;
package Id_Keys is new Session_Set_Types.Generic_Keys (String);
This lets us perform session lookups based on the session identifier:
procedure Play
(Session_Id : in String;
NPT_Range : in NPT_Range_Type; RTSP_Status : out RTSP_Status_Type) is
Iter : constant Session_Set_Types.Iterator_Type := Find (Session_Set, Key => Session_Id);
Session : Session_Access;
begin if Iter = Null_Iterator then RTSP_Status := RTSP.Session_Not_Found; return; end if;
Session := Element (Iter);
Play (Session, NPT_Range, RTSP_Status); end;
We can insert a session object into the set in the normal way, using the item-form of insertion:
function Setup_Session return Session_Access is Session : constant Session_Access := new Session_Type; -- allocate
Iterator : Session_Set_Types.Iterator_Type; Success : Boolean; begin Generate_Id (Session.Id);
Insert (Container => Sessions_Set, New_Item => Session, -- element, has its own key Iterator => Iterator, Success => Success); ... return Session; end;
An alternate way to do this is to use the key-form of insertion:
procedure Set_Key
(Session : in out Session_Access;
Id : in String) is
begin Session := new Session_Type; Session.Id = Id; end;
procedure Insertion is new Id_Types.Generic_Insertion; -- accept default
function Setup_Session return Session_Access is Session : Session_Access; -- no allocation here
Iterator : Session_Set_Types.Iterator_Type; Success : Boolean;
Id : Id_Subtype; begin Generate_Id (Id);
Insert (Container => Sessions_Set, Key => Id, -- key, not element Iterator => Iterator, Success => Success);
Session := Element (Iterator); -- element was allocated in Set_Key ... return Session; end;
This example also illustrates that sets and maps are essentially the same. The only real difference is where the key lives.
A.X.Y The Package Containers.Multisets.Sorted.Unbounded
My streaming media server is essentially a main loop, which works off "events" from a queue. Each event has a time tag that specifies when the event comes due. Events that come due earlier in time are processed sooner than events that expire later.
We basically have a priority queue, with element priority determined by an event's time. An event looks like this:
type Event_Type; type Event_Access is access all Event_Type;
function "<" (L, R : Event_Access) return Boolean;
A sorted multiset suits our needs exactly. We use a multiset because many events can come due at the same time:
package Event_Queue_Types is new Ada.Containers.Multisets.Sorted.Unbounded (Event_Access);
type Event_Type is limited record Time : Time_Type; Event_Queue_Iterator : Event_Queue_Types.Iterator_Type; ...; end record;
function "<" (L, R : Event_Access) return Boolean is begin return L.Time < R.Time; end;
Note that here the order relation is defined in terms of the time-tag of the events designated by each access object. We're not comparing access objects directly.
In fact the session objects with which we are all by now imtimately familiar are actually implemented something like this:
type Session_Type is limited record Id : Id_Subtype; RTP_Event : aliased Event_Type; ... end record;
The event queue object is declared the body the sessions package:
package body Sessions is
Event_Queue : Event_Queue_Types.Container_Type; ...
We work off events in the event queue like this:
Main: loop
while Is_Empty (Event_Queue) loop ... -- do some work sleep (1); -- delay for 1ms end loop;
Curr_Time : constant Time_Type := Clock;
loop
Event : constant Event_Access := First_Element (Event_Queue); -- top
exit when Event.Time > Curr_Time;
Process (Event); -- Event.Time <= Curr means it's due NOW
Delete_First (Event_Queue); -- pop
exit when Is_Empty (Event_Queue);
end loop;
sleep (1); -- be nice to other processes on this machine
end loop Main;
Events are placed in the queue when a client issues an RTSP PLAY request:
procedure Play_Stream
(Session : access Session_Type;
Track : in String) is
Success : Boolean;
begin ... Insert (Container => Event_Queue, New_Item => Session.RTP_Event'access, Iterator => Session.RTP_Event.Event_Queue_Iterator, Success => Success); ... end;
Events can leave the queue for many reasons. One reason is when a client issues an RTSP PAUSE request:
procedure Pause_Stream
(Session : access Session_Type;
Track : in String) is
begin ... Delete (Event_Queue, Session.RTP_Event.Event_Queue_Iterator); ... end;
It's not good enough to use the item-form (or even the key-form) of Delete, because many events can have the same key (remember this is a multiset). We need a way to uniquely specify the element we want do delete, which is what the iterator-form does.
This example also demonstrates a nearly-ubiquitous idiom. In the body of a package in which a type is declared, an associative container object is declared, which keeps track of instances of the type.
A.X.Y The Package Containers.Multisets.Sorted.Unbounded.Generic_Keys
The canonical example for multisets is to have a database of people objects, with the name as the key. We can set things up like this:
type Person_Type is record Family_Name : Name_Subtype; Given_Name : Name_Subtype; Gender : Gender_Type; ... end Person_Type;
function "<" (L, R : Person_Type) return Boolean is begin return Ada.Containers.Less_String_Case_Insensitive -- or whatever (Left => L.Family_Name, Right => R.Family_Name); end;
package Person_Multiset_Types is new Ada.Containers.Multisets.Sorted.Unbounded (Person_Type);
Persons : Person_Multiset_Types.Container_Type;
Now we can add some people to our database:
procedure Op is P : Person_Type;
I : Person_Multiset_Types.Iterator_Type; B : Boolean; begin P.Family_Name := "Heaney"; P.Given_Name := "Matthew"; ... Insert (Persons, P, I, B);
P.Family_Name := "Santini"; P.Given_Name := "Thomas"; ... Insert (Persons, P, I, B);
P.Family_Name := "Manchester"; P.Given_Name := "Janet"; ... Insert (Persons, P, I, B); end;
Now if we instantiate the nested Generic_Keys package, like this:
function "<"
(Person : Person_Type;
Name : String) is
begin return Less_String_Case_Insensitive (Person.Name, Name); end;
function "<" (Name : String; Person : Person_Type) is begin return Less_String_Case_Insensitive (Name, Person.Name); end;
package Name_Keys is new Person_Multiset_Types.Generic_Keys (Key_Type => String);
We can now iterate over all the persons that share a family name:
procedure Print (Family : in String) is
procedure Process (I : Person_Multiset_Types.Iterator_Type) is P : Person_Type renames To_Constant_Access (I).all; begin ... -- print this person end;
procedure Print is new Person_Multiset_Types.Generic_Iteration;
I : Person_Multiset_Types.Iterator_Type := Lower_Bound (Persons, Key => Family);
J : constant Person_Multiset_Types.Iterator_Type := Upper_Bound (Persons, Key => Family);
begin -- Print
while I /= J loop Print (I); Increment (I); end loop;
end Print;
--!corrigendum A.16
!ACATS Test
ACATS tests will be needed for this library.
!appendix

From: Matthew Heaney
Sent: Thursday, September 25, 2003  1:52 PM

This document was formatted using hard line-breaks, with a maximum of 72
columns.  If you print it I suggest using 10 pt. font.

The container library API described here comprises the following
library-level units:

Ada.Containers
Ada.Containers.Vectors
Ada.Containers.Vectors.Unbounded
Ada.Containers.Vectors.Bounded
Ada.Containers.Deques
Ada.Containers.Deques.Unbounded
Ada.Containers.Lists
Ada.Containers.Lists.Double
Ada.Containers.Lists.Double.Unbounded
Ada.Containers.Lists.Double.Bounded
Ada.Containers.Lists.Single
Ada.Containers.Lists.Single.Unbounded
Ada.Containers.Lists.Single.Bounded
Ada.Containers.Maps
Ada.Containers.Maps.Hashed
Ada.Containers.Maps.Hashed.Unbounded
Ada.Containers.Maps.Hashed.Unbounded.Unchecked_Modification
Ada.Containers.Maps.Hashed.Strings
Ada.Containers.Maps.Hashed.Strings.Unbounded
Ada.Containers.Maps.Hashed.Wide_Strings
Ada.Containers.Maps.Hashed.Wide_Strings.Unbounded
Ada.Containers.Maps.Sorted
Ada.Containers.Maps.Sorted.Unbounded
Ada.Containers.Maps.Sorted.Unbounded.Unchecked_Modification
Ada.Containers.Maps.Sorted.Strings
Ada.Containers.Maps.Sorted.Strings.Unbounded
Ada.Containers.Maps.Sorted.Wide_Strings
Ada.Containers.Maps.Sorted.Wide_Strings.Unbounded
Ada.Containers.Multimaps
Ada.Containers.Multimaps.Hashed
Ada.Containers.Multimaps.Hashed.Unbounded
Ada.Containers.Multimaps.Hashed.Unbounded.Unchecked_Modification
Ada.Containers.Multimaps.Hashed.Strings
Ada.Containers.Multimaps.Hashed.Strings.Unbounded
Ada.Containers.Multimaps.Hashed.Wide_Strings
Ada.Containers.Multimaps.Hashed.Wide_Strings.Unbounded
Ada.Containers.Multimaps.Sorted
Ada.Containers.Multimaps.Sorted.Unbounded
Ada.Containers.Multimaps.Sorted.Unbounded.Unchecked_Modification
Ada.Containers.Multimaps.Sorted.Strings
Ada.Containers.Multimaps.Sorted.Strings.Unbounded
Ada.Containers.Multimaps.Sorted.Wide_Strings
Ada.Containers.Multimaps.Sorted.Wide_Strings.Unbounded
Ada.Containers.Sets
Ada.Containers.Sets.Hashed
Ada.Containers.Sets.Hashed.Unbounded
Ada.Containers.Sets.Hashed.Unbounded.Unchecked_Modification
Ada.Containers.Sets.Sorted
Ada.Containers.Sets.Sorted.Unbounded
Ada.Containers.Sets.Sorted.Unbounded.Unchecked_Modification
Ada.Containers.Multisets
Ada.Containers.Multisets.Hashed
Ada.Containers.Multisets.Hashed.Unbounded
Ada.Containers.Multisets.Hashed.Unbounded.Unchecked_Modification
Ada.Containers.Multisets.Sorted
Ada.Containers.Multisets.Sorted.Unbounded
Ada.Containers.Multisets.Sorted.Unbounded.Unchecked_Modification
Ada.Containers.Hash_String
Ada.Containers.Hash_String_Case_Insensitive
Ada.Containers.Equal_String_Case_Insensitive
Ada.Containers.Less_String_Case_Insensitive
Ada.Containers.Hash_Wide_String
Ada.Containers.Hash_Wide_String_Case_Insensitive
Ada.Containers.Equal_Wide_String_Case_Insensitive
Ada.Containers.Less_Wide_String_Case_Insensitive
Ada.Containers.Hash_Integer
Ada.Containers.Hash_Long_Integer
Ada.Containers.Hash_<etc>


Note that only the terminal units are interesting.  The other units are
merely placeholders.

The API has been annotated with lots of examples to illustrate the kinds
of problems the library solves, and the most effective way to use the
library to solve them.  So what you have here is a tutorial, too.  Note
that the actual container specifications are relatively simple, and none
of them are any more complicated than, say, Ada.Text_IO or
Ada.Strings.Unbounded.  (In fact all the containers use type, operation,
and parameter names that were inspired by those packages.)

This library API is modeled on the STL.  It has the same containers as
the STL does, using the same container names and semantics.  If you know
the STL already then you will instantly understand how this library
works.  It does not have any of the STL algorithms, but that's only
because I wanted to keep the API small (and not because it's not
possible to support STL-style algorithms -- it is).  The API described
here has exactly the minimum set of containers I believe all Ada
developers need, in exactly the form they need it.  (To be honest: I'd
wish it had container forms to store limited elements, too, but what's
here is the minimal set.)

We can broadly characterize containers as being either "sequence
containers" or "associative containers."  All sequence containers allow
insertion and deletion at any position in the container, but each one
optimizes differently for insertions at certain positions.  Associative
containers associate elements with a key, which defines how elements are
ordered in the container.

A vector is a sequence container optimized for insertion at the back
end.  It of course allows insertion at any position, but as you move
toward the front the cost of vector insertion approaches O(n).

A deque is a sequence container optimized for insertion at either the
front or the back end.  Like all sequence containers it allows items to
be inserted at any position, but as you move towards the middle
positions the cost of deque insertion approaches O(n).

Both vectors and deques support constant-time random access of elements.
Except for a couple of operations, the interfaces are identical.

A linked-list container provides constant-time (meaning O(1) time
complexity) insertion and deletion at any position, but of course does
not provide random access.  A doubly-linked list has nodes with both
next and prev pointers, and a singly-linked list has nodes with only a
next pointer.

A doubly-linked list supports both forward and reverse iteration; the
singly-linked list supports only forward iteration.  A doubly-linked
list container is thus more general than a singly-linked list container,
but a singly-linked list consumes less storage and forward iteration is
often all you need anyway.

Vectors and deques specify positions via a discrete index subtype.
Linked-lists (and all the associative containers) specify positions
using an iterator, which is just a fancy name for an access type that
designates an internal node of storage.  Note that unlike a discrete
index, an iterator always designates the node that contains an element;
it never designates an element directly.  (There's a special selector
operation for that.)

The vector and linked-list containers have both unbounded and bounded
forms.  The latter form allows the maximum number of elements to be
specified as a discriminant, so that storage for the container can be
implemented as a stack-allocated array.  This allows those container
packages to be instantiated at any nesting level.

The associative containers order their elements by key.  A map has an
explicit key type, but with a set the key is implicit in the element
itself.  Multimaps and multisets allow multiple keys and elements,
respectively, to have non-unique values.

The associative containers have both sorted and hashed versions.  For
insertions, deletions, and searches, a sorted associative container
guarantees O(log N) time complexity even in the worst case; under some
circumstances insertion is amortized O(1).  Hashed containers provide
O(1) average time complexity, but of course cannot make any worst-case
guarantees.

For the maps and multimaps, there are forms that use types String and
Wide_String specifically as the key.  Using a string as the key of a map
container is so common in nearly every application that the library
provides this directly.  This is an example of my philosophy that a
library should be designed around the common case.  If you need to do it
often, then the library should make it easy to do it.

There are also library-level subprograms for performing case-insensitive
string comparisons, and for returning the hash value of a both
case-sensitive and case-insensitive strings.

This API is based on an existing container library called Charles.  Some
of you were at my paper presentation in Toulouse, and so may already be
familiar with that library.  The source code itself and a couple of
papers about the design of Charles are at my home page:

<http://home.earthlink.net/~matthewjheaney/charles/index.html>

The PowerPoint slides ("foils") from my paper presentation at AE-2003
are also there.  It's in various formats including HTML, so you can get
some background info by viewing that, too.

My immediate plan is to synchronize the Charles library to the spec I've
submitted today, which should only take a few days.  Thanks to important
contributions from Mario Alves and especially Chris Grein, the packages
described here are much simpler than the corresponding specs in Charles.
However, even in its current state the version at my website is close
enough to what I've shown here that if desired you could use it as-is to
bootstrap your own implementation.

The Charles release also has an examples subdirectory containing a few
programs you can run.

If the ARG chooses this API as the basis for standardization, then I and
the other members of the ASCLWG will work on building a test suite for
testing implementors' conformance to the API.  Let us know what you'd
like done, and we'll take care of it.

Here are a few issues:

The set container packages have a generic nested package called
Generic_Keys.  I didn't know whether to nest it in the parent package,
or to declare it as a generic child package.  If anyone has a preference
please let me know.

For a few containers I have described them indirectly, in terms of how
they differ from another container.  If this is not your preferred API
format then I'll change it to however you like.

The containers specified here can only be instantiated with elements
that are non-limited and definite.  If there is interest in containers
for limited element types, or for indefinite element types, then let me
know and I will amend this specification.

I didn't know what to do about the stream attributes.  Should containers
be streamable?  It's simple enough for a user to just iterate through
the elements in the container and write them out himself, so I wasn't
sure whether the containers needed to provide stream support directly.

Charles has a container for strings, which is similar to a vector except
that the internal array designates type String.  It's not unlike type
Unbounded_String, but it's a proper container and thus fits within the
container framework.  If there is interest in a container optimized for
storing an unbounded String (or Wide_String) let me know.

Charles also has a Pointer_Type abstraction, which is sort of a
"container" for storing a single access object.  It is similar to the
auto_ptr in C++.  The access object can be transferred from Poiner_Type
object to Pointer_Type object, thus transferred ownership of the access
object.  If a Pointer_Type object still owns an access object during its
Finalize, then it automatically Free's the access object, thus providing
a cheap form of garbage collection.  If there is interest in
standardizing such an abstraction then let me know.

There are no bounded forms for the associative containers.  The package
names have been chosen to allow for the possibility, but this API only
specifies unbounded forms.  I'm pretty sure the sorted associative
containers can implemented in the normal way (i.e. as an array), and I
think I know how to implement a hash table on the stack, so if there is
interest then let me know.

The sorted associative containers are implemented using a tree.  Each
node consumes 3 pointers worth of storage in addition to the element
(and key, for maps).  It is possible to implement a sorted associative
container using a sorted vector, which might be useful as a
storage-optimized form.  Again, if there is interest let me know.

A votre service,
Matt


--STX

The root of the containers subsystem is as follows:

package Ada.Containers is
   pragma Pure;
end Ada.Containers;


The specification of the root of the vector containers subsystem is as
follows:

package Ada.Containers.Vectors is
   pragma Pure;
end Ada.Containers.Vectors;

The specification of the unbounded vector container package is as
follows:

generic

   type Index_Type is (<>);

   type Element_Type is private;

   with function "="
     (Left, Right : Element_Type) return Boolean is <>;

package Ada.Containers.Vectors.Unbounded is

   pragma Preelaborate;

   subtype Index_Subtype is Index_Type;

   type Container_Type is private;

   procedure Assign
     (Target : in out Container_Type;
      Source : in     Container_Type);

   function "=" (Left, Right : Container_Type) return Boolean;

   function Length (Container : Container_Type) return Natural;

   function Is_Empty (Container : Container_Type) return Boolean;

   procedure Clear (Container : in out Container_Type);

   procedure Swap (Left, Right : in out Container_Type);

   procedure Append
     (Container : in out Container_Type;
      New_Item  : in     Element_Type);

   procedure Insert
     (Container : in out Container_Type;
      Before    : in     Index_Type'Base;
      New_Item  : in     Element_Type);

   procedure Insert
     (Container : in out Container_Type;
      Before    : in     Index_Type'Base);

   procedure Insert_N
     (Container : in out Container_Type;
      Before    : in     Index_Type'Base;
      Count     : in     Natural;
      New_Item  : in     Element_Type);

   procedure Insert_N
     (Container : in out Container_Type;
      Before    : in     Index_Type'Base;
      Count     : in     Natural);

   procedure Delete
     (Container : in out Container_Type;
      Index     : in     Index_Type'Base);

   procedure Delete_Last
     (Container : in out Container_Type);

   procedure Delete_N
     (Container : in out Container_Type;
      First     : in     Index_Type'Base;
      Count     : in     Natural);

   function Size
     (Container : Container_Type) return Natural;

   procedure Resize
     (Container : in out Container_Type;
      Size      : in     Natural);

   function Front
     (Container : Container_Type) return Index_Type'Base;

   function First
     (Container : Container_Type) return Index_Type;

   function First_Element
     (Container : Container_Type) return Element_Type;

   function Last
     (Container : Container_Type) return Index_Type'Base;

   function Last_Element
     (Container : Container_Type) return Element_Type;

   function Back
     (Container : Container_Type) return Index_Type'Base;

   function Element
     (Container : Container_Type;
      Index     : Index_Type'Base) return Element_Type;

   generic
      type Element_Access is access all Element_Type;
   function Generic_Element
     (Container : Container_Type;
      Index     : Index_Type'Base) return Element_Access;

   procedure Replace_Element
     (Container : in Container_Type;
      Index     : in Index_Type'Base;
      By        : in Element_Type);

   generic
      with procedure Process
        (Element : in out Element_Type) is <>;
   procedure Generic_Process_Element
     (Container : in Container_Type;
      Index     : in Index_Type'Base);

   generic
      with procedure Process
        (Element : in out Element_Type) is <>;
   procedure Generic_Process_Elements
     (Container : in Container_Type);

   generic
      with procedure Process
        (Element : in out Element_Type) is <>;
   procedure Generic_Reverse_Process_Elements
     (Container : in Container_Type);

private

   --not specified by this standard

end Ada.Containers.Vectors.Unbounded;


!discussion

A vector container manages an internal array whose component subtype is
Element_Type.  The element components are aliased.  (Indeed, elements
are aliased in every container.)  The "size" of a vector corresponds to
the length of the internal array (and hence to the number of "physical"
elements), and the "length" of the vector corresponds to the number of
"logical" or "active" elements in the internal array.  The elements of
the array between the vector length and the vector size are "inactive."

As elements are inserted into the container (thus increasing the length
of the vector), the internal array automatically expands as necessary
(thus increasing the size of the vector).  The new size of the vector
following expansion is always a factor of its current size, although the
exact algorithm is not specified by this standard.  (A typical value for
the new size is two times the current size.)  Insertions at the back end
of a vector container therefore have amortized constant time complexity.

!end discussion

Container_Type is a pass-by-reference type.  The size of a
default-initialized vector is 0.

Implementation Requirements:
The internal array has C convention.

!discussion

The lingua franca for APIs is C.  We need to be able to pass the
internal array (really, a pointer to its first element) to a C function
with a T* argument.  Therefore the elements must have the same alignment
as they have in C.

For example, what I often do in C++ on Windows is this:

   typedef std::vector<HANDLE> handles_t;

   handles_t handles = /* ... */ ;

   WaitForMultipleObjects(handles.size(),
                          &handles[0],
                          FALSE,
                          INFINITE);

The Ada analog looks like this:

   type Handle_Access is access all Win32.HANDLE;
   for Handle_Access'Storage_Size use 0;
   pragma Convention (C, Handle_Access);

   function To_Access is
      new Handle_Vectors.Generic_Element (Handle_Access);

   Handles : Handle_Vectors.Container_Type;
   ...
   WaitForMultipleObjects
     (Length (Handles),
      To_Access (Handles, Index => First (Handles)),
      False,
      INFINITE);

This is actually quite close to the C++ example above.

!end discussion


procedure Assign
  (Target : in out Container_Type;
   Source : in     Container_Type);

If Source is an alias of Target, the operation has no effect.  If the
length of Source is less than or equal to the size of Target, then the
active elements in Source are assigned to the elements in Target.  Any
exceptions raised during assignment are propagated.  Otherwise if the
length of Source is greater than the size of Target, a new internal
array is allocated with elements that are initialized from the active
elements in Source.  Any exceptions raised during allocation are
propagated, and Target is not modified.  Otherwise, the existing array
is replaced by the new array, and then the old array is deallocated.
Any exceptions raised during deallocation are propagated.


function "=" (Left, Right : Container_Type) return Boolean;

The equality operator for vectors.  If Left and Right refer to the same
container, then the function returns immediately with the value True.
If Left and Right have different lengths, then the function returns
immediately with the value False.  Otherwise, elements are compared
sequentially using the equality operator supplied as the generic actual.
Any exception raised during evaluation of element equality is
propagated, immediately terminating the iteration.


function Length (Container : Container_Type) return Natural;

Returns the number of active elements in the vector.


function Is_Empty (Container : Container_Type) return Boolean;

Equivalent to Length (Container) = 0.


procedure Clear (Container : in out Container_Type);

Sets the length of the container to 0.  The size of the container does not
change.

!discussion

You can think of Clear as "removing" the elements in the container, but
of course it does not really remove them.  The elements that were
formerly active simply now become inactive.

In particular, the internal array is not altered, and no finalization of
the active elements occurs.  If this is required, then then user must
effect this himself prior to clearing the vector.  There are many ways
to do that; here is one way:

   procedure My_Clear (V : in out Vector_Subtype) is

      procedure Finalize
        (E : in out Element_Subtype) is ...;

      procedure Finalize_Elements is
         new Generic_Process_Elements (Process => Finalize);
   begin
      Finalize_Elements (V);
      Clear (V);
   end;

The internal array never shrinks, and it only expands under specific
conditions.  If you want to clear the vector and also deallocate the
internal array, you can use the swap trick:

   procedure Clear_And_Deallocate (V : in out Vector_Subtype) is

      V2 : Vector_Subtype; -- internal array is null
   begin
      Swap (V, V2);
   end;

The internal array that belonged to V is swapped into V2, and the null
array of V2 is swapped into V.  When V2 is finalized, its internal array
is deallocated.  This also invokes Finalize for controlled elements.

!end discussion


procedure Swap (Left, Right : in out Container_Type);

Exchanges the internal array and element count of the Left vector with
that of the Right vector.  Elements that were in the Left vector are now
in the Right vector, and vice versa.  The time complexity of Swap is
O(1).

!discussion

The internal array of a vector container only grows.  The Resize
operation can only be used to grow the internal array, not to shrink it.
If for whatever reason you want more efficient use of storage, you can
use the swap trick to allocate an array having the minimum size
necessary to store the active elements:

   procedure Reclaim_Storage (V : in out Vector_Subtype) is
      Temp : Vector_Subtype := V;
   begin
      Swap (V, Temp);
   end;

This copies all active elements in V to the temporary vector object
Temp, which is allocated using a smaller internal array (presumably the
smallest size necessary to store the elements, according to whatever
algorithm the implementor has chosen).  The new array is swapped with
the original array, which is then deallocated when Temp is finalized.

!end discussion


procedure Append
  (Container : in out Container_Type;
   New_Item  : in     Element_Type);

Equivalent to Insert (Container, Back (Container), New_Item).  This
operation has (amortized) O(1) time complexity.

!example

Append is the canonical method for inserting items into a vector
container:

   procedure Copy (A : Array_Subtype) is
      V : Vector_Subtype;
   begin
      Resize (V, Size => A'Length);

      for I in A'Range loop
         Append (V, New_Item => A (I));
      end loop;
      ...
   end Copy;

The Resize operation tells the vector object to preallocate an internal
array having at least the size specified.  If you need to perform many
repeated insertions, then if you know the ultimiate length apriori you
should always call Resize beforehand.  This is more efficient because it
allocates the internal array once, and therefore avoids the repeated
reallocation, copying, and deallocation cycles that would be necessary
otherwise as the array is expanded.

!end example

!example

You can use a vector to implement a stack in the traditional way:

  package Stacks is new Ada.Containers.Vectors.Unbounded (ET);
  use Stacks;

  Stack : Stacks.Container_Type;

  procedure Push (E : in ET) is
  begin
     Append (Stack, New_Item => E);
  end;

  function Top return ET is
  begin
     return Last_Element (Stack);
  end;

  procedure Pop is
  begin
     Delete_Last (Stack);
  end;

Note that we can't use the front end as the top of the stack, because
there's no Delete_First.  Compare this to the single list example.

!end example


procedure Insert
  (Container : in out Container_Type;
   Before    : in     Index_Type'Base;
   New_Item  : in     Element_Type);

Equivalent to Insert_N (Container, Before, 1, New_Item).


procedure Insert
  (Container : in out Container_Type;
   Before    : in     Index_Type'Base);

Equivalent to Insert_N (Container, Before, 1).


procedure Insert_N
  (Container : in out Container_Type;
   Before    : in     Index_Type'Base;
   Count     : in     Natural;
   New_Item  : in     Element_Type);

If Count has the value 0, then Insert_N does nothing.  Otherwise the new
length is calculated from the sum of the current length and the value of
Count; if the new value of Last would be greater than Index_Type'Last
the operation fails and Constraint_Error is raised.  If the value of
Before is not in the range First (Container) .. Back (Container), the
operation fails and Constraint_Error is raised.

If the current vector size is greater than or equal to the new length,
the elements in the range Before .. Last (Container) slide up by Count
positions, and the elements in the Count positions starting at Before
are assigned to the value New_Item.  Any exceptions raised during the
assignment are propagated.

Otherwise if the current vector size is less than the new length, a new
internal array is allocated and initialized from New_Item and the active
elements in Container.  Any exceptions are propagated and Container is
not modified.  The existing array is replaced by the new array, and then
the old array is deallocated.  Any exceptions raised during the
deallocation are propagated.


procedure Insert_N
  (Container : in out Container_Type;
   Before    : in     Index_Type'Base;
   Count     : in     Natural);

Equivalent to Insert_N (Container, Before, Count, New_Item), with the
difference that the elements in the Count positions starting at Before
are not assigned.

!example

This operation essentially opens up a "hole" in the middle of the
internal array.  It's more efficient to do it this way than inserting
one-item-at-a-time, because the sliding is done only once.  For example,
we can copy an array (or any other container) into a vector at some
arbitary position like this:

   procedure Copy
     (A : in     Array_Subtype;
      V : in out Vector_Subtype;
      I : in     Index_Type'Base) is

      J : Index_Type'Base := I;
   begin
      Insert_N (V, Before => I, Count => A'Length); -- dig the hole

      for Index in A'Range loop
         Replace_Element (V, J, By => A (Index));  -- fill the hole
         J := J + 1;
      end loop;
      ...
   end Copy;

!end example


procedure Delete
  (Container : in out Container_Type;
   Index     : in     Index_Type'Base);

If Index is outside the range First (Container) .. Last (Container), the
operation does nothing.  Otherwise the elements in the range
Index_Type'Succ (Index) .. Last (Container) slide down by one position.
Any exceptions raised during element assignment are propagated.

!example

There's no Delete_First operation, as a not-so-subtle reminder to
developers that that's a potentially expensive operation.  However, you
can achieve the same effect by simply specifying the first index:

   procedure Op (V : in out Vector_Subtype) is
   begin
      Delete (V, Index => First (V));  -- pop front element
      ...
   end;

!end example


procedure Delete_Last (Container : in out Container_Type);

Equivalent to Delete (Container, Last (Container)).  This operation has
O(1) time complexity.

!example

If some sort of finalization of the last element is necessary prior to
its removal, the programmer is responsible for effecting this action
prior to calling Delete_Last.  As an example, suppose we have a vector
whose elements are access objects, and we need to deallocate the element
when it is "popped" from the vector:

   procedure Pop (V : in out Container_Type) is

      procedure Process is
         new Ada.Unchecked_Deallocation (T, T_Access);

      procedure Free is
         new Generic_Process_Element; -- use "Process" default
   begin
      Free (V, Index => Last (V));
      Delete_Last (V);
   end;

!end example


procedure Delete_N
  (Container : in out Container_Type;
   First     : in     Index_Type'Base;
   Count     : in     Natural);

If Count is 0, the operation does nothing.  If First does not specify a
value in the range First (Container) .. Last (Container), the operation
fails and Constraint_Error is raised.  The back of the half-open range
to be deleted is then calculated as the minimum of First plus Count, and
Back (Container).  The active elements of the internal array starting at
the back of the calculated range slide down to the corresponding index
positions starting at First.  Any exceptions raised during the
assignment are propagated.


function Size (Container : Container_Type) return Natural;

Returns the length of the internal array.


procedure Resize
  (Container : in out Container_Type;
   Size      : in     Natural);

If Size (Container) is greater than or equal to Size, the operation does
nothing.  Otherwise a new internal array is allocated having at least
the length specified and initialized from the active elements in
Container.  Any exceptions raised during the allocation are propagated,
and Container is not modified.  Otherwise the existing array is replaced
by the new array, and then the old array is deallocated.  Any exceptions
raised during deallocation are propagated.


function Front
  (Container : Container_Type) return Index_Type'Base;

Returns the value Index_Type'Pred (Index_Type'First).

!example

The Front selector exists so that during reverse iteration over a
vector, the index can "fall off the end" of the range onto the sentinel:

   procedure Op (V : in Vector_Subtype) is
      I : Index_Subtype := Last (V);
      J : constant Index_Subtype := Front (V);
   begin
      while I /= J loop
         Process (E => Element (V, I));
         I := Index_Type'Pred (I);
      end loop;
   end Op;

!end example


function First
  (Container : Container_Type) return Index_Type;

Returns the value Index_Type'First.


function First_Element
  (Container : Container_Type) return Element_Type;

Equivalent to Element (Container, First (Container)).


function Last
  (Container : Container_Type) return Index_Type'Base;

Returns the index value corresponding to the length (meaning number of
active elements) of the vector.


function Last_Element
  (Container : Container_Type) return Element_Type;

Equivalent to Element (Container, Last (Container)).


function Back
  (Container : Container_Type) return Index_Type'Base;

Equivalent to Index_Type'Succ (Last (Container)).


function Element
  (Container : Container_Type;
   Index     : Index_Type'Base) return Element_Type;

If Index is not in the range First (Container) .. Last (Container), the
operation fails and Constraint_Error is raised.  Otherwise it returns
the element of the internal array at the specified Index.


!example

The First and Last selectors allow iteration over a vector analogous to
iteration over an array, using the loop machinary provided by the
language:

   procedure Op (V : in Vector_Subtype) is
      procedure Process (E : in Element_Subtype) is ...;
   begin
      for I in First (V) .. Last (V) loop
         Process (E => Element (V, I));
      end loop;
   end Op;

Here we've used the element selector function.  Another possibility is
pass the element as the parameter of a generic subprogram:

   procedure Op (V : in Vector_Subtype) is

      procedure Process (E : in out Element_Subtype) is ...;

      procedure Process is
         new Generic_Process_Element; -- accept "Process" default
   begin
      for I in First (V) .. Last (V) loop
         Process (V, I);
      end loop;
   end Op;

But if we're going to iterate over the entire range of active elements,
then it's probably simpler to just use a passive iterator:

   procedure Op (V : in Vector_Subtype) is

      procedure Process (E : in out Element_Subtype) is ...;

      procedure Process is
         new Generic_Process_Elements; -- accept "Process" default
   begin
      Process (V);
   end Op;

In fact here we make yet another simplification:

   procedure Process (E : in out Element_Subtype) is ...;

   procedure Op is
      new Generic_Process_Elements; -- accept "Process" default

!end example


generic
   type Element_Access is access all Element_Type;
function Generic_Element
  (Container : Container_Type;
   Index     : Index_Type'Base) return Element_Access;

If Index is not in the range First (Container) .. Last (Container), the
operation fails and Constraint_Error is raised.  Otherwise it returns an
access value that designates a variable view of the element of the
internal array at Index.

!example

This function is very important, as it allows in-place modification of
elements.  For example, suppose we have a vector whose elements are
lists, and we want to append an item to the list at a specified vector
position.  We can do that as follows:

   procedure Append
     (V : List_Vectors.Container_Type;
      I : Index_Type'Base;
      E : Element_Subtype) is

      type List_Access is access all List_Subtype;
      function To_Access is new Generic_Element (List_Access);

      List : List_Subtype renames To_Access (V, I).all;
   begin
      Append (List, New_Item => E);
   end;

It's often the case that during an insertion you don't have an item
value to assign to the new element, and instead you want simply to
reserve space for an element and then modify it directly.  For example:

   procedure Op (V : in out Vector_Subtype) is
   begin
      Insert (V, Before => Back (V));  -- no item

      declare
         E : Element_Subtype renames To_Access (V, Last (V)).all;
      begin
         -- now modify E
      end;
   end Op;

!end example


procedure Replace_Element
  (Container : in Container_Type;
   Index     : in Index_Type'Base;
   By        : in Element_Type);

If Index does not specify a value in the range First (Container) .. Last
(Container), the operation fails and Constraint_Error is raised.
Otherwise the element at position Index is assigned the value By.  Any
exceptions raised during the assignment are propagated.

!example

This allows array-like modification of vector elements:

   procedure Op (V : in out Vector_Subtype) is
      I : Index_Subtype := ...;
      E : Element_Subtype := ...;
   begin
      Insert (V, Before => I, New_Item => E);
      ... -- modify E some more
      Replace_Element (V, Index => I, By => E); -- aka V(I) := E;
   end;

!end example


generic
   with procedure Process
     (Element : in out Element_Type) is <>;
procedure Generic_Process_Element
  (Container : in Container_Type;
   Index     : in Index_Type'Base);

If Index does not specify a value in the range First (Container) .. Last
(Container), the operation fails and Constraint_Error is raised.
Otherwise, the actual subprogram bound to Process is called with the
element at position Index.

!example

You can use this operation to modify an element in-place.  For example,
given our vector-of-lists from the earlier example, we could swap a list
object with one of the list elements already in vector, like this:

   procedure Swap
     (V : in     List_Vectors.Container_Type;
      I : in     Index_Type'Base;
      L : in out List_Subtype) is

      procedure Process (VE : in out List_Subtype) is
      begin
         Swap (L, VE);
      end;

      procedure Swap_Element is
        new Generic_Process_Element; -- accept "Process" default
   begin
      Swap_Element (V, Index => I);
   end;

This would allow us to populate a list object, separate from a vector,
and then add it to the vector without having to actually copy it:

   procedure Op (V : in List_Vectors.Container_Type) is
      L : List_Subtype;
   begin
      Append (L, New_Item => E);
      ... -- populate list some more
      Insert (V, Before => Back (V)); -- create a new element in V
      Swap (V, Last (V), L);          -- swap new element in V with L
   end;

The new list element that is appended to V during Insert is immediately
swapped with the list object L.  This effectively "moves" L onto V,
without any copying.  The element that was in V is moved into L, which
is then finalized.

Note that if you wanted to swap two vector elements, you could use
Generic_Process_Element but it would be awkward.  It would probably be
easier to use an instantiation of Generic_Element to get a variable view
of the elements and then use them directly.  For example, given our
instantiation To_Access from the earlier example, we would implement the
operation this way:

  procedure Swap
    (V    : in out List_Vectors.Container_Type;
     I, J : in     Index_Type'Base) is

     IE : List_Subtype renames To_Access (V, I).all;
     JE : List_Subtype renames To_Access (V, J).all;
  begin
     Swap (IE, JE);
  end;

!end example


generic
   with procedure Process
     (Element : in out Element_Type) is <>;
procedure Generic_Process_Elements
  (Container : in Container_Type);

Calls the actual subprogram bound to Process for each active element in
Container.  Any exception raised during Process is propagated,
immediately terminating the iteration.


generic
   with procedure Process
     (Element : in out Element_Type) is <>;
procedure Generic_Reverse_Process_Elements
  (Container : in Container_Type);

Invokes the actual subprogram bound to Process as per
Generic_Process_Elements, except that the elements are traversed in
reverse order.


This standard specifies a bounded vector container.  It differs from the
unbounded vector container as follows:

(0) The name of the bounded vector container package is
    Ada.Containers.Vectors.Bounded.

(1) The package has Pure categorization, not Preelaborate.

(2) The container type is limited, and has a discrimiant, as follows:

        type Container_Type (Size : Natural) is limited private;

    Container_Type is a pass-by-reference type.  The Size discriminant
    specifies the maximum length (number of elements) for the container
    object.

    The container type is not controlled, and therefore the package may
    be instantiated at any nesting level.

(3) The Swap and Resize operations are omitted.

(4) The implementation requirements are as follows.  The internal array
    has C convention.  The Container_Type should be implemented as a
    stack-allocated array with exactly Size components.

(5) The semantics of Insert_N differ as follows:

procedure Insert_N
  (Container : in out Container_Type;
   Before    : in     Index_Type'Base;
   Count     : in     Natural;
   New_Item  : in     Element_Type);

If Count has the value 0, the operation does nothing.  If the current
length of Container plus Count is greater than Container.Size, or if the
new value of Last would be greater than Index_Type'Last, the operation
fails and Constraint_Error is propagated.  If the value of Before is
outside the range First (Container) .. Back (Container), the operation
fails and Constraint_Error is propagated.

Otherwise the elements in the range Before .. Last (Container) slide up
by Count positions and the elements in the Count positions starting at
Before are assigned the value New_Item.  Any exceptions raised during
the assignment are propagated.

!example

The Win32 API limits the number of kernel synchronization objects for
which it will wait to a maximum of 64.  The bounded form of the vector
container allows you to express this restriction directly in code:

   Handles : Handle_Vectors.Container_Type (64);  -- max
   ...
   WaitForMultipleObjects
     (Length (Handles),
      To_Access (Handles, Index => First (Handles)),
      False,
      INFINITE);

This way if you violate the limit, you'll know about it sooner (at the
time of insertion of the 65th element), instead of later when you call
the API function.

!end example


The root of the deque containers subsystem is as follows:

package Ada.Containers.Deques is
   pragma Pure;
end Ada.Containers.Deques;


This standard specifies an unbounded deque container.  It differs from
the unbounded vector container as follows:

(1) The name of the package is Ada.Containers.Deques.Unbounded.

(2) The final declaration in the generic formal region is a generic
    formal constant:

    Elements_Per_Node : in Positive := 8;

NOTE:
I have passed the number of elements per node as a generic formal
constant, but I again I don't know whether this is exposing too many
implementation details, or whether this will constrain implementor
freedom.  If there is little enthusiasm among implementors for allowing
a user to specify the number of elements per node, then we can get rid
of it.

(3) The following insertion operation is defined:

procedure Prepend
  (Container : in out Container_Type;
   New_Item  : in     Element_Type);

Equivalent to Insert (Container, First (Container), New_Item).  This
operation has O(1) time complexity.


(4) The following deletion operation is defined:

procedure Delete_First (Container : in out Container_Type);

Equivalent to Delete (Container, First (Container)).  The time
complexity of this operation is O(1).


(5) The semantics of Clear differ as follows.  Sets the length of
    Container to 0.  Any exceptions raised during deallocation of
    internal storage are propagated.


(6) The semantics of Swap differ as follows.  Exchanges the internal
    nodes of Left and Right.  Elements that were in the Left deque are
    now in the Right deque, and vice versa.  The time complexity of Swap
    is O(1).

(7) The semantics of Insert_N differ as follows:

procedure Insert_N
  (Container : in out Container_Type;
   Before    : in     Index_Type'Base;
   Count     : in     Natural;
   New_Item  : in     Element_Type);

If Count has the value 0, the operation does nothing.  Otherwise the new
length is calculated from the sum of the current length and the value of
Count; if the new value of Last would be greater than Index_Type'Last
the operation fails and Constraint_Error is raised.  If Before is
outside the range First (Container) .. Back (Container), the operation
fails and Constraint_Error is raised.

Otherwise, active elements are created in the Count positions starting
at Before, by sliding existing elements either towards the front of the
container or towards the back, whichever is the smaller distance, and
then assigned the value New_Item.  Any exceptions raised during element
assignment or allocation of internal storage are propagated.


(8) The semantics of Delete differ as follows:

procedure Delete
  (Container : in out Container_Type;
   Index     : in     Index_Type'Base);

If Index is outside the range First (Container) .. Last (Container), the
operation does nothing.  Otherwise either the front elements slide up,
or the back elements slide down, whichever is the smaller range.  Any
exceptions raised during element assignment or storage deallocation are
propagated.


(9) The semantics of Delete_N differ as follows:

procedure Delete_N
  (Container : in out Container_Type;
   First     : in     Index_Type'Base;
   Count     : in     Natural);

If Count is 0, the operation does nothing.  Otherwise if First is
outside the range First (Container) .. Last (Container), the operation
fails and Constaint_Error is raised.  The back of the range to be
deleted is calculated as the minimum of the sum of First and Count and
Back (Container).  Either the elements in the range starting at the back
of the calculated range slide down to the corresponding index positions
starting at First, or the elements that precede First slide up,
whichever is the fewer number of elements.  Any exceptions raised during
element assignment or storage deallocation are propagated.


(10) The Size and Resize operations are omitted.


!discussion

The name "deque" is pronounced the same as "deck," as in "deck of
cards."

A deque is implemented as a set of nodes, each containing a fixed number
of elements (Elements_Per_Node).  Like a vector, a deque supports random
access, but unlike a vector insertion and deletion at the front end is a
constant-time operation.

The nodes are addressed internally via a secondary index, comprising an
array of pointers to nodes.  As elements are inserted and the number of
nodes increases, the array grows as necessary to address each node.  The
storage overhead for a deque container is therefore about 1 pointer per
node.

Do not confuse this container with data structures variously called
"stacks" or "queues" or "double-ended queues" etc, that restrict the
positions at which insertion or deletion may occur.  A deque is a
sequence container, and like all sequence containers it allows insertion
and deletion at any position.

It is interesting to compare a deque to a vector.  A vector makes room
for a new item inserted in the middle by sliding existing elements up
towards the back, and during deletion in the middle the existing
elements slide down towards the front.

Insertion in the middle of the deque works by determining which is the
closer end, and then sliding existing elements out towards that end.
Deletion in the middle works analogously, by sliding existing elements
in from the closer end.

!end discussion

!example

What is often under-appreciated about a deque is that it will perform
better than a vector if you must append many elements to the container,
and you don't know the total number of elements apriori.  In the case of
a vector, you wouldn't be able to use Resize, which means there would be
repeated allocation and deallocation as the internal array grows.  A
deque, however, can simply allocate a new internal node, which doesn't
affect existing nodes.  For example:

   procedure Copy (A : Really_Long_Array_Subtype) is
      D : Deque_Subtype;
   begin
      for I in A'Range loop
         Append (D, New_Item => A (I));  -- always cheap
      end loop;
      ...
   end Copy;

A deque also has the benefit that it doesn't need large contiguous
regions of memory, which is different from a vector.

!end example

!example

A deque object has an offset to keep track of which element on the front
node is the first active element.  Given an index value, the offset is
added to the index, and that sum is divided by the number of elements
per node.  This is how the deque figures out where the element
associated with that index is, and why the deque is able to provide
random-access to its elements.

Therefore it's possible to iterate through the elements using a regular
for loop, analogous to array iteration:

   procedure Op (D : in Deque_Types.Container_Type) is
   begin
      for I in Index_Subtype range First (D) .. Last (D) loop
         Do_Something (Element (D, I));
      end loop;
   end;

However, although element selection is a constant-time operation,
there's still a calculation to be made as each element is selected out
of the deque.  In this case, a passive iterator is likely to beat a for
loop, because the iterator maintains context during iteration:

   procedure Op (D : in Deque_Types.Container_Type) is

      procedure Iterate is
         new Deque_Types.Generic_Process_Element (Do_Something);
   begin
      Iterate (D);
   end;

In general, using a passive iterator is preferred to using an active
iterator, among other reasons because it's likely to be more efficient.
If the container knows that it's visiting elements in sequence (as is
the case in a passive iterator), then it can visit the elements in a way
that takes advantage of that container's representation.  For example
the sorted associative containers use a recursive algorithm to perform
an in-order tree traversal.

!end example

!example

You can use a deque to implement a stack in the traditional way, but
using the front end, too:

  package Stacks is new Ada.Containers.Deques.Unbounded (ET);
  use Stacks;

  Stack : Stacks.Container_Type;

  procedure Push (E : in ET) is
  begin
     Prepend (Stack, New_Item => E);
  end;

  function Top return ET is
  begin
     return First_Element (Stack);
  end;

  procedure Pop is
  begin
     Delete_First (Stack);
  end;

Compare this to the vector example, which can only use the back end.
Here it doesn't make any difference which end, because the time
complexity (O(1)) is the same.

We can also use a deque to implement a queue, in which items are
inserted at one end and deleted from the other end.  This might be
better than, say, using a singly-linked list container (see the example
in that section), because a deque has a smaller storage cost than a
linked-list.

!end example


!example

An AVI file is a popular format for storing digital media comprising
more than one stream, such as separate tracks for video and audio.
Video and audio frames are typically "interleaved" within the file,
meaning that the audio frame that "goes with" a frame of video is
physically close to it on disk, allowing them to be read together in a
single read operation.

To know where each frame of media is on the disk, an AVI file has an
"index chunk" comprising "index entries," each of which describes the
kind of frame (video vs. audio), its file offset, and its size.

Within the index chunk, entries for video and audio are interspersed,
because this is an interleaved file.  I need to have an index comprising
just one kind of frame or the other, because I need random access to the
index entries.  (A client can specify a time in his play request, so I
need to be able to find the video frame that corresponds to that time.)

My first attempt at solving the problem used a vector, like this:

   package Index_Types is
      new Ada.Containers.Vectors.Unbounded (Index_Entry_Type);

   Video_Index : Index_Types.Container_Type;
   Audio_Index : Index_Types.Container_Type;

   procedure Load_Index (File : AVI_File_Type) is
      IE : Index_Entry_Type;
   begin
      Seek_Index (File);

      while Offset (File) /= End_Of_Index (File) loop
          Read (File, IE);

          if Is_Video (IE) then
             Append (Video_Index, New_Item => IE);
          elsif Is_Audio (IE) then
             Append (Audio_Index, New_Item => IE);
          end if;
      end loop;
   end Load_Index;

When I ran this on some typical files, my performance was horrible.
Why?  Feature-length movies can have over two hours worth of data, which
means hundreds of thousands of frames.  The frame index is therefore
very large.

Insertion into a vector works by using up the available space in the
internal array (the "inactive" elements).  When the space runs out, the
container allocates a new internal array (probably twice the size of the
old one), copies the contents of the old array onto the new array, and
then deallocates the old array.  You begin to see the problem: for a
large AVI file index, this cycle of reallocation, copying, and
deallocation repeats over and over again until the resulting internal
array is large enough to contain all the items.

I said earlier that if you know the ultimate length of a vector
container apriori then should always call Resize first, before you make
any insertions.  However, that option isn't available to us here,
because an AVI file doesn't tell us how many video entries (say) there
are in the index -- all we know is the total index size, which has
multiple kinds of entries.

To solve the problem I made a one-line change:

   package Index_Types is
      new Ada.Containers.Deques.Unbounded (Index_Entry_Type);

This solved the problem because inserting at the end of a deque when
there are no more inactive elements on the last internal page simply
appends a new page onto the internal index of pages.  The elements
already in the deque aren't affected, because they live on different
pages.

I didn't make this example up.  It really happened.  Real programmers
really do have real performance issues, that can make the difference
between success and failure in an application.  It's simply not an
option for me to have my server top-out the CPU every time it loads a
file, or to put potentially thousands of clients on hold, because some
guy in Idaho decided he wanted to watch the two-hour version of The
Matrix instead of the two-minute trailer.  Performance matters.

!end example

The root of the linked-list containers subsystem is as follows:

package Ada.Containers.Lists is
   pragma Pure;
end Ada.Containers.Lists;

The root of the doubly-linked list containers subsystem is as follows:

package Ada.Containers.Lists.Double is
   pragma Pure;
end Ada.Containers.Lists.Double;


The specification of the unbounded doubly-linked list container package
is as follows:

generic

   type Element_Type is private;

   with function "="
     (Left, Right : Element_Type) return Boolean is <>;

package Ada.Containers.Lists.Double.Unbounded is

   pragma Preelaborate;

   type Container_Type is private;

   type Iterator_Type is private;

   Null_Iterator : constant Iterator_Type;

   procedure Assign
     (Target : in out Container_Type;
      Source : in     Container_Type);

   function "=" (Left, Right : Container_Type) return Boolean;

   function Length (Container : Container_Type) return Natural;

   function Is_Empty (Container : Container_Type) return Boolean;

   procedure Clear (Container : in out Container_Type);

   procedure Swap (Left, Right : in out Container_Type);

   procedure Prepend
     (Container : in out Container_Type;
      New_Item  : in     Element_Type);

   procedure Append
     (Container : in out Container_Type;
      New_Item  : in     Element_Type);

   procedure Insert
     (Container : in out Container_Type;
      Before    : in     Iterator_Type;
      New_Item  : in     Element_Type;
      Iterator  :    out Iterator_Type);

   procedure Insert
     (Container : in out Container_Type;
      Before    : in     Iterator_Type;
      New_Item  : in     Element_Type);

   procedure Insert
     (Container : in out Container_Type;
      Before    : in     Iterator_Type;
      Iterator  :    out Iterator_Type);

   procedure Delete
     (Container : in out Container_Type;
      Iterator  : in out Iterator_Type);

   procedure Delete_First
     (Container : in out Container_Type);

   procedure Delete_Last
     (Container : in out Container_Type);

   procedure Reverse_Container
     (Container : in Container_Type);

   generic
      with function "<"
        (Left, Right : Element_Type) return Boolean is <>;
   procedure Generic_Sort
     (Container : in Container_Type);

   generic
      with function "<"
        (Left, Right : Element_Type) return Boolean is <>;
   procedure Generic_Merge
     (Container : in out Container_Type;
      Source    : in out Container_Type);

   procedure Splice
     (Container : in out Container_Type;
      Before    : in     Iterator_Type;
      Source    : in out Container_Type);

   procedure Splice
     (Container : in out Container_Type;
      Before    : in     Iterator_Type;
      Source    : in out Container_Type;
      Iterator  : in     Iterator_Type);

   function First
     (Container : Container_Type) return Iterator_Type;

   function First_Element
     (Container : Container_Type) return Element_Type;

   function Last
     (Container : Container_Type) return Iterator_Type;

   function Last_Element
     (Container : Container_Type) return Element_Type;

   function Back
     (Container : Container_Type) return Iterator_Type;

   function Element
     (Iterator : Iterator_Type) return Element_Type;

   generic
      type Element_Access is access all Element_Type;
   function Generic_Element
     (Iterator : Iterator_Type) return Element_Access;

   procedure Replace_Element
     (Iterator : Iterator_Type;
      By       : Element_Type);

   generic
      with procedure Process
        (Element : in out Element_Type) is <>;
   procedure Generic_Process_Element
     (Iterator : in Iterator_Type);

   function Succ
     (Iterator : Iterator_Type) return Iterator_Type;

   function Pred
     (Iterator : Iterator_Type) return Iterator_Type;

   procedure Increment (Iterator : in out Iterator_Type);

   procedure Decrement (Iterator : in out Iterator_Type);

   generic
      with procedure Process
        (Iterator  : in Iterator_Type) is <>;
   procedure Generic_Iteration
     (Container : in Container_Type);

   generic
      with procedure Process
        (Iterator  : in Iterator_Type) is <>;
   procedure Generic_Reverse_Iteration
     (Container : in Container_Type);

   generic
      with procedure Process
        (Element : in out Element_Type) is <>;
   procedure Generic_Process_Elements
     (Container : in Container_Type);

   generic
      with procedure Process
        (Element : in out Element_Type) is <>;
   procedure Generic_Reverse_Process_Elements
     (Container : in Container_Type);

private

   -- not specified by this standard

end Ada.Containers.Lists.Double.Unbounded;


!discussion

A doubly-linked list container manages a linked list of internal storage
nodes, each of which contains an element and pointers to the next
(successor) and previous (predecessor) internal nodes.

An iterator is similar to an access type (indeed, that is how it's
implemented), that designates a node of internal storage.

Unlike a vector (which is optimized for insertion at the back) or a
deque (optimized for insertion at the front or back), insertion into a
doubly-linked list container at any position is a constant-time
operation.

When a doubly-linked list container object elaborates, a sentinel node
belonging to the container is allocated automatically.  Its element is
uninitialized, except for any controlled initialization or
default-initialized components.  It is not included in the count of
elements in the container, and it cannot be deleted.

The function Back returns an iterator that designates the sentinel node.
The First and Last functions return iterators designating the front-most
and back-most (non-sentinel) nodes, respectively.  Back is the successor
of Last, and Last is the predecessor of Back.  A doubly-linked list also
has wrap-around semantics, meaning that the successor of Back is First,
the predecessor of First is Back.

Insertion and deletion of an element executes in constant time.  During
insertion, iterators that designate nodes already in the doubly-linked
list are not affected, and therefore an iterator that designates an
element continues to designate that same element.  (Really, an iterator
designates a node, but we sometimes say "designates an element" if the
context is clear.  Iterators never designate elements directly.)

Deletion of an element (really, deletion of the node containing an
element) only affects the iterators that designate that node, which
become invalid.  Iterators that designate other elements of the
doubly-linked list container are not affected.

Nodes can migrate from one doubly-linked list container to another
during Swap, Merge, and Splice.  Iterators designating nodes that move
of course remain valid, and continue to designate the same node, in its
new home.

!end discussion


Container_Type is a pass-by-reference type.

In the absence of any explicit initialization, objects of type
Iterator_Type are default-initialized to the value Null_Iterator.


Implementation Requirements

Do we need to require that instances of type Iterator_Type be passed by
copy?  For example, this locution is allowed:

   procedure Insert_N
     (C : in out Container_Type;
      I : in out Iterator_Type;
      E : in     Element_Subtype;
      N : in     Natural) is
   begin
      for Index in 1 .. N loop
         -- I is in-param for Before, and out-param Iterator
         Insert (C, Before => I, New_Item => E, Iterator => I);
      end loop;
   end;

This isn't any real burden, since (in the unbounded case, anyway) the
full view of Iterator_Type is implemented as an access type.


procedure Assign
  (Target : in out Container_Type;
   Source : in     Container_Type);

If Source is an alias of Target, the operation does nothing.  Otherwise,
assigns to Target a copy of the elements from Source.  Any exceptions
raised during internal allocation are propagated, and Target is not
modified.

AARM NOTE
The sentinel doesn't count as an element, so it is never copied.  The
sentinel is not affected by Assign, and so following Assign the Target
has the same sentinel as it did prior to Assign.


function "=" (Left, Right : Container_Type) return Boolean;

The equality operator for double-linked list containers.  If Left and
Right refer to the same container, then the function returns immediately
with the value True.  If Left and Right have different lengths, then the
function returns immediately with the value False.  Otherwise, elements
are compared sequentially using the equality operator supplied as the
generic actual.  Any exception raised during evaluation of element
equality is propagated, immediately terminating the iteration.


function Length (Container : Container_Type) return Natural;

Returns the number of elements in Container.  The time complexity of
this operation is O(1).

AARM NOTE
The time complexity of Length differs from the corresponding size()
operation in C++, which allows size() for a list to have O(n) time
complexity.


function Is_Empty (Container : Container_Type) return Boolean;

Equivalent to Length (Container) = 0.


procedure Clear (Container : in out Container_Type);

Deletes all the nodes that belong to Container, and sets the length of
to 0.  Any exceptions raised during deallocation are propagated.

AARM NOTE

Here and elsewhere, when an operation propagates an exception, the
container is never left in an inconsistent state.  This standard might
not specify what state the container is left in, but it does require
that whatever that state is, that it is consistent, and the container is
of course still usable.

In the case of Clear (for example), we don't specify how many nodes
remain in the container if the deallocation raises an exception.  But we
do require that if, say, 10 nodes were removed and then deallocation of
the 10th node failed, that the new length has a value 10 less than the
old length.

For example, an implementation has permission to implement Clear by
unlinking the nodes from Container, setting the length to 0, and then
walking the list to deallocate each node.  If there's an exception, then
the un-deallocated nodes would be lost, but Length (Container) would
accurately reflect the fact that the container is empty.

Another implementation may choose to implement Clear by as a loop,
unlinking a node, decrementing the container length by 1, and then
deallocating the node.  If the deallocation fails then in that case
Length (Container) would return a non-zero value, but it would
accurately reflect the fact that there are that many nodes remaining in
the container.

END AARM NOTE


procedure Swap (Left, Right : in out Container_Type);

Exchanges the internal nodes of Left and Right.  Elements that were in
the Left container are now in the Right container, and vice versa.  The
time complexity of Swap is O(1).

AARM NOTE
The Back sentinel node follows the rest of the nodes to their new home
in the other container.


procedure Prepend
  (Container : in out Container_Type;
   New_Item  : in     Element_Type);

Equivalent to Insert (Container, First (Container), New_Item).  The time
complexity of this operation is O(1).


procedure Append
  (Container : in out Container_Type;
   New_Item  : in     Element_Type);

Equivalent to Insert (Container, Back (Container), New_Item).  The time
complexity of this operation is O(1).


procedure Insert
  (Container : in out Container_Type;
   Before    : in     Iterator_Type;
   New_Item  : in     Element_Type;
   Iterator  :    out Iterator_Type);

If Before equals Null_Iterator, the operation fails and Constraint_Error
is propagated.  If Before does not designate a node that belongs to
Container, program execution is erroneous.  Otherwise it allocates a new
node whose element is initialized to the value New_Item, and inserts it
prior to the node designated by Before.  Iterator designates the
newly-allocated node.  Any exceptions raised during allocation of
internal storage are propagated, and Container is not modified.  The
time complexity of this operation is O(1).


procedure Insert
  (Container : in out Container_Type;
   Before    : in     Iterator_Type;
   New_Item  : in     Element_Type);

Equivalent to Insert (Container, Before, New_Item, Iterator), with the
difference that the iterator parameter is omitted.  The time complexity
of this operation is O(1).


procedure Insert
  (Container : in out Container_Type;
   Before    : in     Iterator_Type;
   Iterator  :    out Iterator_Type);

Equivalent to Insert (Container, Before, New_Item, Iterator), with the
difference that the element on the newly-allocated node is unitialized,
except for any controlled initialization or default-initialized
components.  The time complexity of this operation is O(1).


procedure Delete
  (Container : in out Container_Type;
   Iterator  : in out Iterator_Type);

If Iterator equals Null_Iterator or Back (Container), the operation does
nothing.  If Iterator does not designate a node that belongs Container,
program execution is erroneous.  Otherwise it removes the node
designated by Iterator from the Container.  The Iterator value returned
designates the successor of the node deleted.  Any exceptions raised
during deallocation are propagated.  The time complexity of this
operation is O(1).


procedure Delete_First
  (Container : in out Container_Type);

If Container is empty, the operation does nothing.  Otherwise it deletes
the front-most node, as per Delete (Container, First (Container)).  The
time complexity of this operation is O(1).


procedure Delete_Last
  (Container : in out Container_Type);

If Container is empty, the operation does nothing.  Otherwise it deletes
the back-most (non-sentinel) node, as per Delete (Container, Last
(Container)).  The time complexity of this operation is O(1).


procedure Reverse_Container (Container : in Container_Type);

The nodes in Container are put in reverse order.


generic
   with function "<"
     (Left, Right : Element_Type) return Boolean is <>;
procedure Generic_Sort
  (Container : in Container_Type);

The nodes in Container are sorted according to the order specified as
the generic formal less-than operator.  The sort is stable.  Any
exceptions raised during evalution of the less-than operator are
propagated, immediately terminating the iteration.

Here and elsewhere the actual bound to the less-than operator must
behave as a "strict" relation, in the sense that Left must really be
less than Right.  If Left is equal to Right then "<" must return False.


generic
   with function "<"
     (Left, Right : Element_Type) return Boolean is <>;
procedure Generic_Merge
  (Container : in out Container_Type;
   Source    : in out Container_Type);

If Source is an alias of Container, the operation has no effect.
Otherwise the elements of Source are spliced into Container.  Both
Container and Source are assumed to be sorted in the order specified as
the generic formal.  Elements in Source that are less than elements in
Container are spliced in before the elements already in Container.
Elements in Source that are equal to or greater than elements in
Container are spliced in after the elements already in Container.  Any
exceptions raised during evaluation of less-than are propagated,
immediately terminating the iteration.


procedure Splice
  (Container : in out Container_Type;
   Before    : in     Iterator_Type;
   Source    : in out Container_Type);

If Source is an alias of Container, the operation has no effect.  If
Before equals Null_Iterator, the operation fails and Constraint_Error is
propagated.  If Before does not designate a node that belongs to
Container, then program execution is erroneous.  Otherwise, all the
nodes in Source (except for its Back sentinel) are moved onto Container,
just prior to Before.  The length of Container is incremented by the
number of nodes in Source, and the length of Source is set to 0.


procedure Splice
  (Container : in out Container_Type;
   Before    : in     Iterator_Type;
   Source    : in out Container_Type;
   Iterator  : in     Iterator_Type);

Relinks the node designated by Iterator so that it immediately precedes
Before.

If Source is an alias of Container, then if Iterator equals Back
(Container) or Null_Iterator, or if Iterator equals Before, the
operation has no effect.  If Before equals Null_Iterator, the operation
fails and Constraint_Error is propagated.  If Iterator or Before does
not designate a node in Container, then program execution is erroneous.
Otherwise the node designated by Iterator is moved so that it becomes
the predecessor of Before.

Otherwise if Source is not an alias of Container, then if Iterator
equals Back (Source) or Null_Iterator, the operation has no effect.  If
Before equals Null_Iterator, the operation fails and Constraint_Error is
propagated.  If Iterator does not designate a node in Source, or Before
does not designate a node in Container, then program execution is
erroneous.  Otherwise the node designated by Iterator is moved from the
Source linked-list onto Container, immediately prior to Before.  The
length of Container is incremented, and the length of Source is
decremented.


function First
  (Container : Container_Type) return Iterator_Type;

Returns an iterator that designates the front-most node.  If Container
is empty, First returns the value of Back (Container).


function First_Element
  (Container : Container_Type) return Element_Type;

If Container is empty, the operation fails and Constraint_Error is
raised.  Otherwise, it returns the element on the node designated by
First (Container).


function Last
  (Container : Container_Type) return Iterator_Type;

Returns an iterator that designates the back-most (non-sentinel) node.
If Container is empty, Last returns the value of Back (Container).


function Last_Element
  (Container : Container_Type) return Element_Type;

If Container is empty, the operation fails and Constraint_Error is
raised.  Otherwise, it returns the element on the node designated by
Last (Container).


function Back
  (Container : Container_Type) return Iterator_Type;

Returns an iterator that designates the sentinel node.


function Element
  (Iterator : Iterator_Type) return Element_Type;

Returns the element stored on the node designated by Iterator.  If
Iterator equals Null_Iterator, the operation fails and Constraint_Error
is propagated.


generic
   type Element_Access is access all Element_Type;
function Generic_Element
  (Iterator : Iterator_Type) return Element_Access;

Returns an access value that designates a variable view of the element
stored on the node designated by Iterator.  If Iterator equals
Null_Iterator, the operation fails and Constraint_Error is propagated.


procedure Replace_Element
  (Iterator : Iterator_Type;
   By       : Element_Type);

Assigns the value By to the element stored on the node designated by
Iterator.  If Iterator equals Null_Iterator, the operation fails and
Constraint_Error is propagated.


generic
   with procedure Process
     (Element : in out Element_Type) is <>;
procedure Generic_Process_Element
  (Iterator : in Iterator_Type);

Invokes the actual subprogram bound to Process with the element on the
node designated by Iterator.  If Iterator equals Null_Iterator, the
operation fails and Constraint_Error is propagated.


function Succ
  (Iterator : Iterator_Type) return Iterator_Type;

Returns an iterator that designates the node immediately following
(towards the back) the node designated by Iterator.  If Iterator equals
Null_Iterator, the operation fails and Constraint_Error is raised.

AARM NOTE
The successor of Last is Back, and the successor of Back is First.  If
the list is empty, First, Last, and Back all designate the same node.


function Pred
  (Iterator : Iterator_Type) return Iterator_Type;

Returns an iterator that designates the node immediately prior to
(towards the front) the node designated by Iterator.  If Iterator equals
Null_Iterator, the operation fails and Constraint_Error is raised.

AARM NOTE
The predecessor of Back is Last, and the predecessor of First is Back.


procedure Increment (Iterator : in out Iterator_Type);

Equivalent to Iterator := Succ (Iterator).


procedure Decrement (Iterator : in out Iterator_Type);

Equivalent to Iterator := Pred (Iterator).


generic
   with procedure Process
     (Iterator : in Iterator_Type) is <>;
procedure Generic_Iteration
  (Container : in Container_Type);

Invokes the actual subprogram bound to Process with an iterator that
designates each node in Container.  Any exceptions raised during Process
are propagated, immediately terminating the iteration.


generic
   with procedure Process
     (Iterator : in Iterator_Type) is <>;
procedure Generic_Reverse_Iteration
  (Container : in Container_Type);

Iterates over the nodes in Container as per Generic_Iteration, except
that elements are traversed in reverse order.


generic
   with procedure Process
     (Element : in out Element_Type) is <>;
procedure Generic_Process_Elements
  (Container : in Container_Type);

Invokes the actual subprogram bound to Process for each element in
Container.  Any exception raised during Process is propagated,
immediately terminating the iteration.

!example

There's no need to pass an extra parameter to indicate that iteration
should stop, since that functionality can be built using active
iterators:

   procedure Op (C : in Container_Type) is
      I : Iterator_Type := First (C);
      J : constant Iterator_Type := Back (C);
   begin
      while I /= J loop
         exit when Predicate (Element (I));
         Process (Element (I));
         Increment (I);
      end loop;
   end Op;

!end example


generic
   with procedure Process
     (Element : in out Element_Type) is <>;
procedure Generic_Reverse_Process_Elements
  (Container : in Container_Type);

Iterates over elements on Container as per Generic_Process_Elements,
with the difference that the elements are traversed in reverse order.

!example

There are a couple of ways to actively iterate (here, in reverse) over a
double list.  One way is to start at the back sentinel and then stop at
first:

   procedure Op (C : in Container_Type) is
      I : Iterator_Type := Back (C);
      J : constant Iterator_Type := First (C);
   begin
      while I /= J loop
         Decrement (I);
         Process (Element (I));
      end loop;
   end Op;

The other way is to start at last and then "fall off the end" from first
onto the back sentinel.  This works because a list has wrap-around
semantics:

   procedure Op (C : in Container_Type) is
      I : Iterator_Type := Last (C);
      J : constant Iterator_Type := Back (C);
   begin
      while I /= J loop
         Process (Element (I));
         Decrement (I);
      end loop;
   end Op;

This even works in the other direction: for forward iteration you could
start at back and navigate to last:

   procedure Op (C : in Container_Type) is
      I : Iterator_Type := Back (C);
      J : constant Iterator_Type := Last (C);
   begin
      while I /= J loop
         Increment (I);
         Process (Element (I));
      end loop;
   end Op;

!end example


This standard specifies a bounded doubly-linked list container.  It
differs from the unbounded doubly-linked list container as follows:

(1) The name of the bounded doubly-linked list container package is
    Ada.Containers.Lists.Double.Bounded.

(2) The package has Pure categorization, not Preelaborate.

(3) The container type is limited, and has a discrimiant, as follows:

        type Container_Type (Size : Natural) is limited private;

    Container_Type is a pass-by-reference type.  The Size discriminant
    specifies the maximum length (number of elements) for the container
    object.

    The container type is not controlled, and therefore the package may
    be instantiated at any nesting level.

(4) The Implementation Requirements are as follows.  Container_Type
    should be implemented as a stack-allocated array, comprising
    exacitly Size node components.  No heap should be used for container
    storage.

(5) There is no sentinel node.

(6) The semantics of Assign differ as follows.  If Source is an alias of
    Target, the operation does nothing.  If the length of Source is
    greater than Target.Size, the operation fails and Constraint_Error
    is propagated.  Otherwise, copies the active elements of Source to
    Target.  Any exceptions raised during element assignment are
    propagated.

(7) The semantics of Clear differ as follows.  Sets the length of the
    container to 0.  The time complexity of this operation is O(1).

!discussion

A bounded linked-list (both doubly-linked and singly-linked) is
implemented as an array, with a storage node as the array component.
Similar to a vector, a bounded linked-list has nodes that are "active"
and nodes that are "inactive."  The active nodes are the ones that we
include in the value returned by Length.  The amount of "free storage"
available to a bounded linked list container object is just Size -
Length number of nodes.

During insertion, a node in the "free list" or "free store" becomes
"active," meaning that it is unlinked from the free list and (re)linked
onto the active list.  There is never any copying of nodes, and the only
element that is ever copied is the one inserted (this is true for all
linked lists).

During deletion, the opposite sequence occurs, and an active node is
simply linked onto the free store of the container, and thus becomes
"inactive."

A bounded linked-list container keeps track of which node is the front
end of the active list and which node is the back end of the active
list.  Therefore Clear for a bounded linked-list container can be
implemented by simply splicing the entire active list onto the free
store.

If a user needs to "finalize" the elements somehow prior to their
"removal" from the bounded linked-list container, then he can use a
passive iterator to first modify each element as appropriate, and then
call Clear.

!end discussion

(8) The operation Swap is omitted.

(9) The semantics of Insert differ as follows:

procedure Insert
  (Container : in out Container_Type;
   Before    : in     Iterator_Type;
   New_Item  : in     Element_Type;
   Iterator  :    out Iterator_Type);

If Container.Size equals the length of Container, the operation fails
and Constraint_Error is propagated.  Otherwise, one node of Container's
free storage is reserved and its element is assigned the value New_Item.
If element assignment fails, the exception is propagated and except for
free-store manipulation Container is not modified.  Otherwise the node
is linked onto the active list just prior to Before, and the container
length is incremented by 1.  If Before equals Back (Container) (this is
the same value as Null_Iterator), the node is linked onto the back end
of the active list.  Iterator designates the newly-inserted node.  The
time complexity of this operation is O(1).

(10) The signature and semantics of Splice differ as follows.

procedure Splice
  (Container : in Container_Type;
   Before    : in Iterator_Type;
   Iterator  : in Iterator_Type);

The node designated by Iterator is relinked so that it becomes the
predecessor of Before.  If Iterator equals Back (Container) (which is
the same as Null_Iterator), or if Iterator equals Before, the operation
has no effect.  If Before equals Back (Iterator) (again, this is the
same value as Null_Iterator), the node is relinked at the back end of
the active list.

Note that in a bounded linked-list container, nodes cannot be spliced
across containers.  Hence there is only a single Splice operation, sans
Source parameter.


(11) The semantics of Back differ as follows:

function Back (Container : Container_Type) return Iterator_Type;

Returns the value Null_Iterator.


(12) The signature of Element differs as follows:

function Element
  (Container : Container_Type;
   Iterator  : Iterator_Type) return Element_Type;

Returns the element stored on the node designated by Iterator.  If
Iterator equals Null_Iterator, the operation fails and Constraint_Error
is propagated.


(13) The signature of Generic_Element differs as follows:

generic
   type Element_Access is access all Element_Type;
function Generic_Element
  (Container : Container_Type;
   Iterator  : Iterator_Type) return Element_Access;

Returns an access value that designates a variable view of the element
stored on the node designated by Iterator.  If Iterator equals
Null_Iterator, the operation fails and Constraint_Error is propagated.


(14) The signature of Replace_Element differs as follows:

procedure Replace_Element
  (Container : in Container_Type;
   Iterator  : in Iterator_Type;
   By        : in Element_Type);

Assigns the value By to the element stored on the node designated by
Iterator.  If Iterator equals Null_Iterator, the operation fails and
Constraint_Error is propagated.  Any exceptions raised during the
assignment are propagated.


(15) The signature of Generic_Process_Element differs as follows:

generic
   with procedure Process
     (Element : in out Element_Type) is <>;
procedure Generic_Process_Element
  (Container : in Container_Type;
   Iterator  : in Iterator_Type);

Invokes the actual subprogram bound to Process with the element on the
node designated by Iterator.  If Iterator equals Null_Iterator, the
operation fails and Constraint_Error is propagated.


(16) The signature of Succ differs as follows:

function Succ
  (Container : Container_Type;
   Iterator  : Iterator_Type) return Iterator_Type;

Returns an iterator that designates the node immediately following
(towards the back) the node designated by Iterator.  If Iterator equals
Back (Container), the value First (Container) is returned.  If Iterator
equals Last (Container), the value Back (Container) is returned.


(17) The signature of Pred differs as follows:

function Pred
  (Container : Container_Type;
   Iterator  : Iterator_Type) return Iterator_Type;

Returns an iterator that designates the node immediately prior to
(towards the front) the node designated by Iterator.  If Iterator equals
Back (Container), the value Last (Container) is returned.  If Iterator
equals First (Container), the value Back (Container) is returned.


(18) The signature of Increment differs as follows:

procedure Increment
  (Container : in     Container_Type;
   Iterator  : in out Iterator_Type);

Equivalent to Iterator := Succ (Container, Iterator).


(19) The signature of Decrement differs as follows:

procedure Decrement
  (Container : in     Container_Type;
   Iterator  : in out Iterator_Type);

Equivalent to Iterator := Pred (Container, Iterator).


!example

Bounded linked-list containers turn out to be extremely useful
especially when used with other containers.  For example, suppose we
have a histogram that counts the frequency of each word in a text file.
We would use the string-key map, that has type String as the key and
Natural as its Element_Type.

After we've exhausted the input, we want to display the words in
frequency order.  However, we can't iterate over the map directly, since
it is ordered by key -- we need to order by element (here, a count).

We can do that very easily by creating a linked-list of map iterators,
and then sorting the linked-list according to the element count.  We
know exactly how big the linked-list needs to be -- it's just the number
of elements in the map.  For example:

   procedure Post_Process
     (Histogram : in String_Integer_Maps.Container_Type) is

      package List_Types is  -- nested instantiation is allowed
        new Ada.Containers.Lists.Double.Bounded
          (Element_Type => String_Integer_Maps.Iterator_Type);

      List : List_Types.Container_Type (Size => Length (Histogram));

      procedure Process (I : in String_Integer_Maps.Iterator_Type) is
      begin
         Append (List, New_Item => I);
      end;

      procedure Populate_List is
        new String_Integer_Maps.Generic_Iteration; -- use default name

   begin -- Post_Process

      Populate_List (Histogram);

      ... -- see below

   end Post_Process;

Here we use the passive iterator for maps to populate the linked-list
container object.

Now that we have the linked-list, we need to sort it, so we need an
order relation.  We want to sort the elements in reverse order, so that
largest count is listed first in the output.  We can define the order
relation like this:

   declare
      function "<" (L, R : String_Integer_Maps.Iterator_Type)
        return Boolean is
      begin
         return Element (L) > Element (R); -- yes
      end;

      procedure Sort is
        new List_Types.Generic_Sort;  -- use "<" default
   begin
      Sort (List);
   end;

We can do better though: suppose that for counts that are equal, we want
to list the items in alphabetic order of the words.  We only have fix
our order relation to compare keys, too:

   declare
      function "<" (L, R : String_Integer_Maps.Iterator_Type)
        return Boolean is
      begin
         if Element (L) = Element (R) then
            return Key (L) < Key (R);  -- compare String
         else
            return Element (L) > Element (R);  -- compare Integer
         end if;
      end;

      procedure Sort is
        new List_Types.Generic_Sort;  -- use "<" default
   begin
      Sort (List);
   end;

To display the results, all we have to do is iterate through the sorted
list:

   declare
      procedure Process  -- prints "n:word" to stdout
        (I : in out String_Integer_Maps.Iterator_Type) is
      begin
         Put (Element (I), Width => 0);   -- the count
         Put (':');
         Put (Key (I));                   -- the word
         New_Line;
      end;

      procedure Print_Results is
        new List_Types.Generic_Process_Elements;  -- use default name
   begin
      Print_Results (List);
   end;

Et voila!  If we only want to display the top 10 words, then we can use
an active iterator, like this:

   declare
      I : List_Types.Iterator_Type := First (List);
      J : constant List_Types.Iterator_Type := Back (List);
   begin
      Print_Results:
      for Index in 1 .. 10 loop
         exit when I = J;

         declare
            K : constant String_Integer_Maps.Iterator_Type :=
              Element (List, I);
         begin
            Put (Element (K), Width => 0);
            Put (':');
            Put (Key (K));
            New_Line;
         end;

         Increment (List, I);
      end loop Print_Results;
   end;

In the example immediately above, we use the Element selector function
to extract the linked-list element.  Here that's just a map iterator, so
in this particular case there is no cost for making a copy of the
element.  However, in general this is not the case (consider our vector
of lists example), and so we need a way to inspect the element in place.
To do that we can use Generic_Process_Element to do the output, which
also allows us to remove the inner declare block:

   declare
      procedure Process
        (I : in out String_Integer_Maps.Iterator_Type) is
      begin
         Put (Element (I), Width => 0);   -- the count
         Put (':');
         Put (Key (I));                   -- the word
         New_Line;
      end;

      procedure Print_Element is
        new List_Types.Generic_Process_Element; -- use default

      I : List_Types.Iterator_Type := First (List);
      J : constant List_Types.Iterator_Type := Back (List);
   begin
      Print_Results:
      for Index in 1 .. 10 loop
         exit when I = J;
         Print_Element (List, I);
         Increment (List, I);
      end loop Print_Results;
   end;

In this example, we only required forward iteration, so we could have
used the single-linked bounded list container, which consumes less
storage.

!end example

!discussion

A bounded linked-list container is just implemented using an array in
the normal way:

   type Container_Type (Size : Natural) is limited record
      Nodes : Node_Array (1 .. Size);
      ...
   end record;

An iterator is just an array index, but that self-initializes:

   type Iterator_Type is record
      Index : Natural := 0;
   end record;

   Null_Iterator : constant Iterator_Type := (Index => 0);

I think we need to say somewhere that if the Index component of an
Iterator_Type value lies outside the range of array, then
Constraint_Error is propagated.  For example:

   declare
     L1 : List_Types.Container_Type (1);
     L2 : List_Types.Container_Type (2);

     I : List_Types.Iterator_Type;
   begin
     Append (L2, X);
     Append (L2, Y);
     I := Last (L2);       -- I.Index = 2
     Z := Element (L1, I); -- raise CE
   end;

The other issue is what happens if an iterator "points" to a node in the
free-store part of the nodes array, e.g.

   declare
     L : List_Types.Container_Type (2);
     I : List_Types.Iterator_Type;
   begin
     Append (L, X);   -- L.Nodes (1) = X
     Append (L, Y);   -- L.Nodes (2) = Y
     I := Last (L);   -- I.Index = 2
     Delete_Last (L);
     Z := Element (L, I);  -- L.Nodes (2) is in L's "free store"
   end;

I don't know if this is an "error" or a "feature."  Perhaps it's a
"bounded error"?

!end discussion


The specification of the root of the singly-linked list containers
subsystem is as follows:

package Ada.Containers.Lists.Single is
   pragma Pure;
end Ada.Containers.Lists.Single;

The specification of the unbounded singly-linked list container package
is as follows:

generic

   type Element_Type is private;

   with function "="
     (Left, Right : Element_Type) return Boolean is <>;

package Ada.Containers.Lists.Single.Unbounded is

   pragma Preelaborate;

   type Container_Type is private;

   type Iterator_Type is private;

   Null_Iterator : constant Iterator_Type;

   procedure Assign
     (Target : in out Container_Type;
      Source : in     Container_Type);

   function "=" (Left, Right : Container_Type) return Boolean;

   function Length (Container : Container_Type) return Natural;

   function Is_Empty (Container : Container_Type) return Boolean;

   procedure Clear (Container : in out Container_Type);

   procedure Swap (Left, Right : in out Container_Type);

   procedure Prepend
     (Container : in out Container_Type;
      New_Item  : in     Element_Type);

   procedure Append
     (Container : in out Container_Type;
      New_Item  : in     Element_Type);

   procedure Insert_After
     (Container : in out Container_Type;
      Position  : in     Iterator_Type;
      New_Item  : in     Element_Type;
      Iterator  :    out Iterator_Type);

   procedure Insert_After
     (Container : in out Container_Type;
      Position  : in     Iterator_Type;
      New_Item  : in     Element_Type);

   procedure Insert_After
     (Container : in out Container_Type;
      Position  : in     Iterator_Type;
      Iterator  :    out Iterator_Type);

   procedure Delete_After
     (Container : in out Container_Type;
      Position  : in     Iterator_Type);

   procedure Delete_First
     (Container : in out Container_Type);

   procedure Reverse_Container
     (Container : in out Container_Type);

   generic
      with function "<"
        (Left, Right : Element_Type) return Boolean is <>;
   procedure Generic_Sort (Container : in out Container_Type);

   generic
      with function "<"
        (Left, Right : Element_Type) return Boolean is <>;
   procedure Generic_Merge
     (Container : in out Container_Type;
      Source    : in out Container_Type);

   procedure Splice_After
     (Container : in out Container_Type;
      Position  : in     Iterator_Type;
      Source    : in out Container_Type);

   procedure Splice_After
     (Container : in out Container_Type;
      Position  : in     Iterator_Type;
      Source    : in out Container_Type;
      Pred      : in     Iterator_Type);

   function First
     (Container : Container_Type) return Iterator_Type;

   function First_Element
     (Container : Container_Type) return Element_Type;

   function Last
     (Container : Container_Type) return Iterator_Type;

   function Last_Element
     (Container : Container_Type) return Element_Type;

   function Back
     (Container : Container_Type) return Iterator_Type;

   function Element
     (Iterator : Iterator_Type) return Element_Type;

   generic
      type Element_Access is access all Element_Type;
   function Generic_Element
     (Iterator : Iterator_Type) return Element_Access;

   procedure Replace_Element
     (Iterator : in Iterator_Type;
      By       : in Element_Type);

   generic
      with procedure Process
        (Element : in out Element_Type) is <>;
   procedure Generic_Process_Element
     (Iterator : in Iterator_Type);

   function Succ
     (Iterator : Iterator_Type) return Iterator_Type;

   procedure Increment
     (Iterator : in out Iterator_Type);

   generic
      with procedure Process
        (Iterator : in Iterator_Type) is <>;
   procedure Generic_Iteration
     (Container : in Container_Type);

   generic
      with procedure Process
        (Element : in out Element_Type) is <>;
   procedure Generic_Process_Elements
     (Container : in Container_Type);

private

   --not specified by this standard

end Ada.Containers.Lists.Single.Unbounded;


!discussion

The singly-linked list container is implemented as a linked-list of
storage nodes, each of which contains an element and a pointer to the
next (successor) node.  A singly-linked list therefore consumes less
storage than a doubly-linked list, but since a single-link node does not
have a pointer that designates its predecessor, a singly-linked list
container does not support reverse iteration.

An iterator is implemented as an access type that designates a node.
Iterators always designate internal nodes of storage, never elements
directly.  During Swap, Merge, and Splice, nodes can migrate across
containers and so an iterator that designated the node in its old
linked-list container continues to designate that same node in its new
container.

The same as for the doubly-linked list container, insertions and
deletions have constant time complexity.  Note that elements live on
nodes and so when an element is inserted into the linked-list container
really it's a node (having an element component) that is being inserted.
Deletion of an element means that the node containing that element is
first removed from the container by unlinking it from other nodes in the
same linked-list, the container length is decremented, and then the node
that was removed is deallocated.

Iterators that designate nodes in the linked-list container are not
affected by insertion, and the iterators continue to designate the same
nodes, with the same elements.  During deletion the only iterators
affected are iterators that designate the node deleted.

Unlike a doubly-linked list container, a singly-linked list container
does not allocate a sentinel node.  The reason is that a sentinel is
only necessary to support reverse iteration, so that when you fall off
the end of the linked-list onto Back, you're able to backup onto Last.
However, since a singly-linked list doesn't support backing up, no
sentinel is necessary.

!end discussion


Container_Type is a pass-by-reference type.

In the absence of any explicit initialization, an instance of type
Iterator_Type is initialized to the value Null_Iterator.

Implementation Requirements:

The implementation must cache an iterator that designates the node
containing the last element, in order to conform to the requirement that
the functions Last and Append execute in constant time.


procedure Assign
  (Target : in out Container_Type;
   Source : in     Container_Type);

If Source is an alias of Target, the operation does nothing.  Otherwise,
assigns to Target a copy of the elements from Source.  Any exceptions
raised during internal allocation are propagated, and Target is not
modified.


function "=" (Left, Right : Container_Type) return Boolean;

The equality operator for singly-linked list containers.  If Left and
Right refer to the same container, then the function returns immediately
with the value True.  If Left and Right have different lengths, then the
function returns immediately with the value False.  Otherwise, elements
are compared sequentially using the actual subprogram bound to the
generic formal equality operator.  Any exception raised during
evaluation of the equality operator is propagated, immediately
terminating the iteration.


function Length (Container : Container_Type) return Natural;

Returns the number of elements in Container.  The time complexity of
this operation is O(1).


function Is_Empty (Container : Container_Type) return Boolean;

Equivalent to Length (Container) = 0.


procedure Clear (Container : in out Container_Type);

Sets the length of Container to 0.  Any exceptions raised during
deallocation of internal storage are propagated.


procedure Swap (Left, Right : in out Container_Type);

Exchanges the internal nodes of Left and Right.  Elements that were in
the Left vector are now in the Right vector, and vice versa.  The time
complexity of Swap is O(1).


procedure Prepend
  (Container : in out Container_Type;
   New_Item  : in     Element_Type);

Equivalent to Insert_After (Container, Back (Container), New_Item).  The
time complexity of this operation is O(1).

!example

You can use a single list to implement a stack:

  package Stacks is new Lists.Single.Unbounded (ET);
  use Stacks;

  Stack : Stacks.Container_Type;

  procedure Push (E : in ET) is
  begin
     Prepend (Stack, New_Item => E);
  end;

  function Top return ET is
  begin
     return First_Element (Stack);
  end;

  procedure Pop is
  begin
     Delete_First (Stack);
  end;

Note that we can't use the back end as the top of the stack, because
there's no Delete_Last.  Compare this to the vector example.

!end example


procedure Append
  (Container : in out Container_Type;
   New_Item  : in     Element_Type);

Equivalent to Insert_After (Container, Last (Container), New_Item).  The
time complexity of Append is O(1).

!example

The fact that Append and Last have unit time complexity means you can
use a single list to implement a queue:

  package Queues is new Lists.Single.Unbounded (ET);
  use Queues;

  Queue : Queues.Container_Type;

  procedure Push (E : in ET) is
  begin
     Append (Queue, New_Item => E);  -- make new foot
  end;

  function Head return ET is
  begin
     return First_Element (Queue);
  end;

  function Foot return ET is
  begin
     return Last_Element (Queue);
  end;

  procedure Pop is
  begin
     Delete_First (Queue); -- make new head
  end;

!end example


procedure Insert_After
  (Container : in out Container_Type;
   Position  : in     Iterator_Type;
   New_Item  : in     Element_Type;
   Iterator  :    out Iterator_Type);

Allocates a new node whose element is initialized to the value New_Item,
and inserts it immediately following Position.  The Iterator returned
designates the newly-allocated node.  If Position equals Back
(Container) (which is the same as Null_Iterator), the element is
inserted at the front.  Any exceptions raised during allocation are
propagated, and Container is not modified.  If Position does not
designate a node that belongs to Container, program execution is
erroneous.  The time complexity of this operation is O(1).


procedure Insert_After
  (Container : in out Container_Type;
   Position  : in     Iterator_Type;
   New_Item  : in     Element_Type);

Equivalent to Insert_After (Container, Position, New_Item, Iterator),
with the difference that the iterator parameter is omitted.


procedure Insert_After
  (Container : in out Container_Type;
   Position  : in     Iterator_Type;
   Iterator  :    out Iterator_Type);

Equivalent to Insert_After (Container, Position, New_Item, Iterator),
with the difference that the element of the node is unitialized, except
for any controlled initialization or default-initialized components.


procedure Delete_After
  (Container : in out Container_Type;
   Position  : in     Iterator_Type);

Removes the successor of the node designated by Position from Container.
If Position equals Last (Container), the operation has no effect.  If
Position equals Back (Container) (which is the same as Null_Iterator),
the front-most node is deleted.  Any errors raised during deallocation
of internal storage are propagated.  The time complexity of this
operation is O(1).  If Position does not designate a node that belongs
to Container, program execution is erroneous.


procedure Delete_First
  (Container : in out Container_Type);

Equivalent to Delete_After (Container, Back (Container)).  The time
complexity of this operation is O(1).


procedure Reverse_Container
  (Container : in out Container_Type);

Reverses the order of nodes in Container.


generic
   with function "<"
     (Left, Right : Element_Type) return Boolean is <>;
procedure Generic_Sort (Container : in out Container_Type);

Orders the nodes in container according to the relation specified as the
generic formal less-than operator.  The sort is stable.  Any exceptions
raised during evaluation of less-than are propagated, immediately
terminating the iteration.


generic
   with function "<"
     (Left, Right : Element_Type) return Boolean is <>;
procedure Generic_Merge
  (Container : in out Container_Type;
   Source    : in out Container_Type);

If Source is an alias of Container, the operation has no effect.
Otherwise the elements of Source are spliced into Container.  Both
Container and Source are assumed to be sorted in the order specified as
the generic formal.  Elements in Source that are less than elements in
Container are spliced in before the elements already in Container.
Elements in Source that are equal to or greater than elements in
Container are spliced in after the elements already in Container.  Any
exceptions raised during evaluation of less-than are propagated,
immediately terminating the iteration.


procedure Splice_After
  (Container : in out Container_Type;
   Position  : in     Iterator_Type;
   Source    : in out Container_Type);

Moves all the nodes in Source to Container, immediately following
Position.  If Source is an alias of Container, the operation has no
effect.  If Position equals Back (Container) (the same value as
Null_Iterator), the nodes are moved to the front of the list.  The
length of Container is incremented by the number of nodes in Source, and
the length of Source is set to 0.  If Position does not designate a node
that belongs to Container, program execution is erroneous.


procedure Splice_After
  (Container : in out Container_Type;
   Position  : in     Iterator_Type;
   Source    : in out Container_Type;
   Pred      : in     Iterator_Type);

Relinks the successor of Pred so that it immediately follows Position.

If Source is an alias of Container, then if Pred equals Position, or if
Pred equals Last (Container), the operation has no effect.  If Position
equals Back (Container) (which is the same as Null_Iterator), the
successor of Pred is moved to the front of the list.  If Pred equals
Back (Container), the front-most node is moved.  If Position or Pred
does not designate a node in Container, program execution is erroneous.

Otherwise if Source is not an alias of Container, then if Pred equals
Last (Source), the operation does nothing.  If Pred equals Back
(Source), the front-most node of Source is moved to Container.  If
Position equals Back (Container), the node is moved to the front of
Container.  The length of Container is incremented, and the length of
Source is decremented.  If Position does not designate a node in
Container, or Pred does not designate a node in Source, program
execution is erroneous.


function First
  (Container : Container_Type) return Iterator_Type;

Returns an iterator that designates the front-most node.  If Container
is empty, returns Back (Container).


function First_Element
  (Container : Container_Type) return Element_Type;

Equivalent to Element (First (Container)).


function Last
  (Container : Container_Type) return Iterator_Type;

Returns an iterator that designates the back-most node.  If Container is
empty, Last returns Back (Container).  The time complexity of Last is
O(1).


function Last_Element
  (Container : Container_Type) return Element_Type;

Equivalent to Element (Last (Container)).


function Back
  (Container : Container_Type) return Iterator_Type;

Returns the value Null_Iterator.


function Element
  (Iterator : Iterator_Type) return Element_Type;

Returns the element stored on the node designated by Iterator.  If
Iterator equals Null_Iterator, the operation fails and Constraint_Error
is propagated.


generic
   type Element_Access is access all Element_Type;
function Generic_Element
  (Iterator : Iterator_Type) return Element_Access;

Returns an access value that designates a variable view of the element
stored on the node designated by Iterator.  If Iterator equals
Null_Iterator, the operation fails and Constraint_Error is propagated.


procedure Replace_Element
  (Iterator : in Iterator_Type;
   By       : in Element_Type);

Assigns By to the element stored on the node designated by Iterator.  If
Iterator equals Null_Iterator, the operation fails and Constraint_Error
is propagated.


generic
   with procedure Process
     (Element : in out Element_Type) is <>;
procedure Generic_Process_Element
  (Iterator : in Iterator_Type);

Invokes the actual subprogram bound to the generic formal Process, with
the element on the node designated by Iterator as the parameter.  If
Iterator equals Null_Iterator, the operation fails and Constraint_Error
is propagated.  Any exceptions raised by Process are propagated.


function Succ (Iterator : Iterator_Type) return Iterator_Type;

Returns an iterator that designates the node immediately following
Iterator.  If Iterator equals Null_Iterator, the operation fails and
Constraint_Error is propagated.


procedure Increment (Iterator : in out Iterator_Type);

Equivalent to Iterator := Succ (Iterator).

!example

A single list doesn't have an operation to return the predecessor of a
node (such an operation would have O(n) time complexity), but you can
implement that yourself easily enough:

   function Pred
     (Container : Container_Type;
      Iterator  : Iterator_Type) return Iterator_Type is

      Result : Iterator_Type;
   begin
      if Iterator = Back (Container) then

         Result := Last (Container);

      elsif Iterator = First (Container) then

         Result := Back (Container);

      else

         Result := First (Container);

         while Succ (Result) /= Iterator loop
            Increment (Result);
         end loop;

      end if;

      return Result;
   end Pred;

!end example


generic
   with procedure Process
     (Iterator : in Iterator_Type) is <>;
procedure Generic_Iteration
  (Container : in Container_Type);

Invokes the actual subprogram bound to Process with an iterator that
designates each element in Container.  Any exceptions raised by Process
are propagated, immediately terminating the iteration.


generic
   with procedure Process
     (Element : in out Element_Type) is <>;
procedure Generic_Process_Elements
  (Container : in Container_Type);

Invokes the actual subprogram bound to Process for each element in
Container.  Any exceptions raised by Process are propagated, immediately
terminating the iteration.


This standard specifies a bounded singly-linked list container.  It
differs from the unbounded singly-linked list container as follows:

(1) The name of the bounded singly-linked list container package is
    Ada.Containers.Lists.Single.Bounded.

(2) The package has Pure categorization, not Preelaborate.

(3) The container type is limited, and has a discrimiant, as follows:

        type Container_Type (Size : Natural) is limited private;

    Container_Type is a pass-by-reference type.  The Size discriminant
    specifies the maximum length (number of elements) for the container
    object.

    The container type is not controlled, and therefore the package may
    be instantiated at any nesting level.

(4) The Implementation Requirements are as follows.  Container_Type
    should be implemented as a stack-allocated array, comprising
    exacitly Size node components.  No heap should be used for container
    storage.

(5) The semantics of Assign differ as follows.  If Source is an alias of
    Target, the operation does nothing.  If the length of Source is
    greater than Target.Size, the operation fails and Constraint_Error
    is propagated.  Otherwise, copies the active elements of Source to
    Target.  Any exceptions raised during element assignment are
    propagated.

(6) The semantics of Clear differ as follows.  Sets the length of the
    container to 0.  The time complexity of this operation is O(1).

(7) The operation Swap is omitted.

(8) The semantics of Insert_After differ as follows:

procedure Insert_After
  (Container : in out Container_Type;
   Position  : in     Iterator_Type;
   New_Item  : in     Element_Type;
   Iterator  :    out Iterator_Type);

If Container.Size equals Length (Container), the operation fails and
Constraint_Error is propagated.  Otherwise one node of free storage is
reserved, its element is assigned the value New_Item, and then it is
relinked into the active list immediately following Position.  If
Position equals Back (Container), the element is inserted at the front.
Any exceptions raised during element assignment are propagated, and
except for free-storage manipulation Container is not modified.  The
time complexity of this operation is O(1).


(9) The semantics of Delete_After differ as follows.

procedure Delete_After
  (Container : in out Container_Type;
   Position  : in     Iterator_Type);

Relinks the node designated by Position from the active list of
Container to its free store, and decrements the container length.  If
Position equals Last (Container), the operation has no effect.  If
Position equals Back (Container), the front-most node is deleted.  The
time complexity of this operation is O(1).


(10) The signature and semantics of Splice_After differ as follows:

procedure Splice_After
  (Container : in out Container_Type;
   Position  : in     Iterator_Type;
   Pred      : in     Iterator_Type);

Relinks the successor of Pred so that it immediately follows Position.
If Pred equals Position, or if Pred equals Last (Container), the
operation has no effect.  If Position equals Back (Container), the
successor of Pred is moved to the front of the list.  If Pred equals
Back (Container), the front-most node is moved.  The time complexity of
this operation is O(1).


(11) The signature of Element differs as follows:

function Element
  (Container : Container_Type;
   Iterator  : Iterator_Type) return Element_Type;

Returns the element stored on the node designated by Iterator.  If
Iterator equals Null_Iterator, the operation fails and Constraint_Error
is propagated.


(12) The signature of Generic_Element differs as follows:

generic
   type Element_Access is access all Element_Type;
function Generic_Element
  (Container : Container_Type;
   Iterator  : Iterator_Type) return Element_Access;

Returns an access value that designates a variable view of the element
stored on the node designated by Iterator.  If Iterator equals
Null_Iterator, the operation fails and Constraint_Error is propagated.


(13) The signature of Replace_Element differs as follows:

procedure Replace_Element
  (Container : in Container_Type;
   Iterator  : in Iterator_Type;
   By        : in Element_Type);

Assigns By to the element stored on the node designated by Iterator.  If
Iterator equals Null_Iterator, the operation fails and Constraint_Error
is propagated.


(14) The signature of Generic_Process_Element differs as follows:

generic
   with procedure Process
     (Element : in out Element_Type) is <>;
procedure Generic_Process_Element
  (Container : in Container_Type;
   Iterator  : in Iterator_Type);

Invokes the actual subprogram bound to the generic formal Process, with
the element on the node designated by Iterator as the parameter.  If
Iterator equals Null_Iterator, the operation fails and Constraint_Error
is propagated.  Any exceptions raised by Process are propagated.


(15) The signature of Succ differs as follows:

function Succ
  (Container : Container_Type;
   Iterator  : Iterator_Type) return Iterator_Type;

Returns an iterator that designates the node immediately following
Iterator.  Succ (Container, Back (Container) equals First (Container),
and Succ (Container, Last (Container)) equals Back (Container).


(16) The signature of Increment differs as follows:

procedure Increment
  (Container : in     Container_Type;
   Iterator  : in out Iterator_Type);

Equivalent to Iterator := Succ (Container, Iterator).


The specification of the root of the map containers subsystem is as
follows:

package Ada.Containers.Maps is
   pragma Pure;
end Ada.Containers.Maps;


The specification of the root of the hashed map containers subsystem is
as follows:

package Ada.Containers.Maps.Hashed is
   pragma Pure;
end Ada.Containers.Maps.Hashed;


The specification of the hashed map container is as follows:

generic

   type Key_Type is private;

   type Element_Type is private;

   with function Hash
     (Key : Key_Type) return Integer'Base is <>;

   with function Is_Equal_Key
     (Left, Right : Key_Type) return Boolean is "=";

   with function "="
     (Left, Right : Element_Type) return Boolean is <>;

package Ada.Containers.Maps.Hashed.Unbounded is

   pragma Preelaborate;

   type Container_Type is private;

   type Iterator_Type is private;

   Null_Iterator : constant Iterator_Type;

   procedure Assign
     (Target : in out Container_Type;
      Source : in     Container_Type);

   function "=" (Left, Right : Container_Type) return Boolean;

   function Length (Container : Container_Type) return Natural;

   function Is_Empty (Container : Container_Type) return Boolean;

   procedure Clear (Container : in out Container_Type);

   procedure Swap (Left, Right : in out Container_Type);

   procedure Insert
     (Container : in out Container_Type;
      Key       : in     Key_Type;
      New_Item  : in     Element_Type;
      Iterator  :    out Iterator_Type;
      Success   :    out Boolean);

   procedure Insert
     (Container : in out Container_Type;
      Key       : in     Key_Type;
      New_Item  : in     Element_Type);

   procedure Replace
     (Container : in out Container_Type;
      Key       : in     Key_Type;
      New_Item  : in     Element_Type);

   procedure Insert
     (Container : in out Container_Type;
      Key       : in     Key_Type;
      Iterator  :    out Iterator_Type;
      Success   :    out Boolean);

   procedure Insert_Sans_Resize
     (Container : in out Container_Type;
      Key       : in     Key_Type;
      New_Item  : in     Element_Type;
      Iterator  :    out Iterator_Type;
      Success   :    out Boolean);

   procedure Insert_Sans_Resize
     (Container : in out Container_Type;
      Key       : in     Key_Type;
      New_Item  : in     Element_Type);

   procedure Replace_Sans_Resize
     (Container : in out Container_Type;
      Key       : in     Key_Type;
      New_Item  : in     Element_Type);

   procedure Insert_Sans_Resize
     (Container : in out Container_Type;
      Key       : in     Key_Type;
      Iterator  :    out Iterator_Type;
      Success   :    out Boolean);

   procedure Delete
     (Container : in out Container_Type;
      Key       : in     Key_Type);

   procedure Delete
     (Container : in out Container_Type;
      Iterator  : in out Iterator_Type);

   function Is_In
     (Key       : Key_Type;
      Container : Container_Type) return Boolean;

   function Find
     (Container : Container_Type;
      Key       : Key_Type) return Iterator_Type;

   function Element
     (Container : Container_Type;
      Key       : Key_Type) return Element_Type;

   generic
      with procedure Process
        (Element : in out Element_Type) is <>;
   procedure Generic_Equal_Range
     (Container : in Container_Type;
      Key       : in Key_Type);

   function Size (Container : Container_Type) return Natural;

   procedure Resize
     (Container : in out Container_Type;
      Size      : in     Natural);

   function First
     (Container : Container_Type;
      Index     : Positive) return Iterator_Type;

   function Back (Container : Container_Type) return Iterator_Type;

   function Index
     (Container : Container_Type;
      Key       : Key_Type) return Positive;

   function Succ (Iterator : Iterator_Type) return Iterator_Type;

   procedure Increment
     (Iterator : in out Iterator_Type);

   function Key (Iterator : Iterator_Type) return Key_Type;

   generic
      type Key_Access is access constant Key_Type;
   function Generic_Key
     (Iterator : Iterator_Type) return Key_Access;

   function Is_Equal_Key
     (Left, Right : Iterator_Type) return Boolean;

   function Is_Equal_Key
     (Left  : Iterator_Type;
      Right : Key_Type) return Boolean;

   function Is_Equal_Key
     (Left  : Key_Type;
      Right : Iterator_Type) return Boolean;

   function Element (Iterator : Iterator_Type) return Element_Type;

   generic
      type Element_Access is access all Element_Type;
   function Generic_Element
     (Iterator : Iterator_Type) return Element_Access;

   procedure Replace_Element
     (Iterator : in Iterator_Type;
      By       : in Element_Type);

   generic
      with procedure Process
        (Key     : in     Key_Type;
         Element : in out Element_Type) is <>;
   procedure Generic_Process_Element
     (Iterator : in Iterator_Type);

   generic
      with procedure Process
        (Iterator : in Iterator_Type) is <>;
   procedure Generic_Iteration
     (Container : in Container_Type);

   generic
      with procedure Process
        (Key     : in     Key_Type;
         Element : in out Element_Type) is <>;
   procedure Generic_Process_Elements
     (Container : in Container_Type);

private

   -- not specified by this standard

end Ada.Containers.Maps.Hashed.Unbounded;

In addition, the hashed map container package has one generic child
procedure:

generic
   type Key_Access is access all Key_Type;
function Ada.Containers.Maps.Hashed.Unbounded.Unchecked_Modification
  (Iterator : Iterator_Type) return Key_Access;

pragma Preelaborate  --TODO: verify this with ARG
  (Ada.Containers.Maps.Hashed.Unbounded.Unchecked_Modification);


Container_Type is a pass-by-reference type.

In the absence of any explicit initialization, objects of type
Iterator_Type are default-initialized to the value Null_Iterator.


procedure Assign
  (Target : in out Container_Type;
   Source : in     Container_Type);

Assigns the key/elements pairs in Source to Target.  If Source is an
alias of Target, the operation does nothing.  Any exceptions raised
during internal allocation or deallocation are propagated.

AARM NOTE

Here and elsewhere we might decide to impose the req'mt that if there's
an exception during Assign, then Target is not modified.  We can make
that guarantee for a list, but it's not clear whether we can or even
should make it for hashed containers, too.


function "=" (Left, Right : Container_Type) return Boolean;

The equality operator for hashed maps.  If Left and Right refer to the
same container, then the function returns immediately with the value
True.  If Left and Right have different lengths, then the function
returns immediately with the value False.  Otherwise, elements (and
*only* elements -- keys do not participate in the computation of
container equality) are compared in canonical order using the equality
operator supplied as the generic actual.  Any exception raised during
evaluation of element equality is propagated, immediately terminating
the iteration.


function Length (Container : Container_Type) return Natural;

Returns the number of key/element pairs in the container.


function Is_Empty (Container : Container_Type) return Boolean;

Equivalent to Length (Container) = 0.


procedure Clear (Container : in out Container_Type);

Sets the length of the container to 0.  The size of the container is not
affected.  Any exceptions raised during deallocation are propagated.

AARM NOTE
This doesn't delete the buckets array -- it just deletes the nodes
hanging off the buckets array.  If you want to delete the buckets array
too (thus setting the map's size to 0), then you can Swap with a
temporary map object.  (Map objects are default-initialized with a
zero-length buckets array.)


procedure Swap (Left, Right : in out Container_Type);

Exchanges the internal nodes and buckets array of Left and Right.
Key/element pairs that were in the Left vector are now in the Right
vector, and vice versa.  The time complexity of Swap is O(1).


procedure Insert
  (Container : in out Container_Type;
   Key       : in     Key_Type;
   New_Item  : in     Element_Type;
   Iterator  :    out Iterator_Type;
   Success   :    out Boolean);

If Length (Container) equals Size (Container), then Container is resized
to some larger value as per Resize.  The position of the new node in the
buckets array is computed from the hash value of Key and Size
(Container).  The actual subprogram bound to Is_Equal_Key is used to
compare Key to the key of each node in the list at that index of the
buckets array.  If a key matches, Success returns False and Iterator
designates the (first) node with the matching key.  Otherwise, a new
node is allocated whose key and element are initialized to Key and
New_Item, respectively, and inserted at the head of the list.  Success
returns True and Iterator designates the newly-inserted node.  Any
exceptions raised during allocation are propagated, and Container is not
modified.  The time complexity of this operation is O(1) average case.


procedure Insert
  (Container : in out Container_Type;
   Key       : in     Key_Type;
   New_Item  : in     Element_Type);

Equivalent to Insert (Container, Key, New_Item, Iterator, Success), with
the difference that the iterator and success parameters are omitted.


procedure Replace
  (Container : in out Container_Type;
   Key       : in     Key_Type;
   New_Item  : in     Element_Type);

Equivalent to Insert (Container, Key, New_Item), with the difference
that if Key is already in the map, then New_Item is assigned to the
element associated with that key.  Any exceptions raised during the
assignment are propagated.


procedure Insert
  (Container : in out Container_Type;
   Key       : in     Key_Type;
   Iterator  :    out Iterator_Type;
   Success   :    out Boolean);

Inserts the Key into Container as per Insert (Container, Key, New_Item,
Iterator, Success), with the difference that a newly-inserted element is
uninitialized, except for any controlled initialization or
default-initialized components.


procedure Insert_Sans_Resize
  (Container : in out Container_Type;
   Key       : in     Key_Type;
   New_Item  : in     Element_Type;
   Iterator  :    out Iterator_Type;
   Success   :    out Boolean);

Inserts the Key/New_Item pair into Container as per Insert (Container,
Key, New_Item, Iterator, Success), with the difference that there is no
resize prior to the insertion proper.  If the container size is 0 the
operation fails and Constraint_Error is propagated.

AARM NOTE

This allows a developer to resize the container at the time most
suitable for his application, independently of the time of insertion.
It also allows the developer explicit control of the load factor.


procedure Insert_Sans_Resize
  (Container : in out Container_Type;
   Key       : in     Key_Type;
   New_Item  : in     Element_Type);

Equivalent to Insert_Sans_Resize (Container, Key, New_Item, Iterator,
Success), with the difference that the Iterator and Success parameters
are omitted.

!example

It's often the case that you know apriori the total number of elements
you intend to insert into the map.  Under these circumstances you should
always Resize the map first, and then perform the insertions.  This
avoids any automatic rehashing that occurs during normal insertion to
preserve the load factor.  For example:

   procedure Op (N : Natural) is
      Map : Map_Types.Container_Type;  -- Size = 0
   begin
      Resize (Map, Size => N);  -- Size >= N

      for I in 1 .. N loop
         Insert_Sans_Resize  -- no resizing necessary
           (Container => Map,
            Key       => Get_Key (I),
            New_Item  => Get_Element (I));
      end loop;

      ...
   end;

!end example


procedure Replace_Sans_Resize
  (Container : in out Container_Type;
   Key       : in     Key_Type;
   New_Item  : in     Element_Type);

Equivalent to Insert_Sans_Resize (Container, Key, New_Item), with the
difference that if the Key is already in the map, then New_Item is
assigned to the element associated with that key.  Any exceptions raised
during the assignment are propagated.


procedure Insert_Sans_Resize
  (Container : in out Container_Type;
   Key       : in     Key_Type;
   Iterator  :    out Iterator_Type;
   Success   :    out Boolean);

Equivalent to Insert_Sans_Resize (Container, Key, New_Item, Iterator,
Success), with the difference that a newly-inserted element is
uninitialized.


procedure Delete
  (Container : in out Container_Type;
   Key       : in     Key_Type);

Deletes the node that matches Key.  If Length (Container) equals 0, then
Delete does nothing.  Otherwise the index is computed from the hash
value of Key and Size (Container).  Any exception raised during
invokation of Hash is propagated.  The nodes in the list at that index
of the buckets array are compared to Key using Is_Equal_Key.  The node
that matches Key is removed from the list and then deallocated.  Any
exceptions raised by Is_Equal_Key or during the deallocation are
propagated.

AARM NOTE

Here and elsewhere, even if deallocation fails, Container must remain
consistent.  Here its state must reflect the fact that the node was
removed from the map, even if it wasn't successfully deallocated.


procedure Delete
  (Container : in out Container_Type;
   Iterator  : in out Iterator_Type);

If Iterator equals Null_Iterator, the operation has no effect.  If
Iterator does not designate a node that belongs to Container, program
execution is erroneous.  Otherwise the buckets index is computed from
the hash value of the key on the node designated by Iterator and Size
(Container).  Any exception raised during invokation of Hash is
propagated.  The node is removed from the map and then deallocated.  Any
exceptions raised by during the deallocation are propagated.  The
Iterator returned designates the node that followed Iterator prior to
the deletion; if this is the end of the list then Null_Iterator is
returned.


function Is_In
  (Key       : Key_Type;
   Container : Container_Type) return Boolean;

Equivalent to Find (Container, Key) /= Null_Iterator.


function Find
  (Container : Container_Type;
   Key       : Key_Type) return Iterator_Type;

If Length (Container) equals 0, then the function returns immediately
with the value Null_Iterator.  Otherwise the buckets index is computed
from the hash value of Key and the size of Container.  If the list at
that index is empty, then Null_Iterator is returned.  Otherwise the list
is traversed and the key of each node on the list is compared to Key
using Is_Equal_Key.  If Is_Equal_Key returns True, an iterator
designating the first node (really, the only node) with the matching key
is returned.  Otherwise, if no node in the list matches Key, it returns
Null_Iterator.


function Element
  (Container : Container_Type;
   Key       : Key_Type) return Element_Type;

Equivalent to Element (Find (Container, Key)).


generic
   with procedure Process
     (Element : in out Element_Type) is <>;
procedure Generic_Equal_Range
  (Container : in Container_Type;
   Key       : in Key_Type);

Attempts to find a matching key as per Find (Container, Key).  If a
matching key is found, it invokes the generic actual bound to Process
with the element associated with the key as the parameter.

!example

You could do this with Find:

   procedure Op (M : in Map_Subtype; K : in Key_Subtype) is
      I : Iterator_Type := Find (M, K);
   begin
      if I /= Null_Iterator then
         Do_Something (Element (I));
      end if;
   end;

but this locution happens often enough that it might as well be a single
operation:

   procedure Op is
      new Generic_Equal_Range (Do_Something);

Its presence here also provides compatability with the same operation in
the multimap, where it allows you to not have to walk the sublist of
equal keys manually.

!end example


function Size (Container : Container_Type) return Natural;

Returns the length of the internal buckets array.  The size of a
default-initialized map container is 0.


procedure Resize
  (Container : in out Container_Type;
   Size      : in     Natural);

If Size (Container) is greater than or equal to Size, the operation has
no effect.  Otherwise, a new buckets array is allocated whose length is
a least the value specified.  If the allocation fails, the exception is
propagated, and Container is not modified.  Nodes in the container are
then rehashed from the old buckets array onto the new one.  If Hash
fails, the exception is propagated, and the nodes that were moved onto
the new buckets array are lost.


function First
  (Container : Container_Type;
   Index     : Positive) return Iterator_Type;

If Index is outside the range 1 .. Size (Container), then
Constraint_Error is propagated.  Otherwise, an iterator that designates
the node at the list at Index is returned.  If there is no list at that
Index, then Null_Iterator is returned.


function Back (Container : Container_Type) return Iterator_Type;

Returns the value Null_Iterator.


function Index
  (Container : Container_Type;
   Key       : Key_Type) return Positive;

If Size (Container) equals 0, the operation fails and Constraint_Error
is propagated.  Otherwise, returns the value of the expression Hash
(Key) mod Size (Container).

AARM NOTE
We could relax the precondition, and simply return 1 if size is 0.


function Succ (Iterator : Iterator_Type) return Iterator_Type;

If Iterator equals Null_Iterator, the operation fails and
Constraint_Error is propagated.  Otherwise, it returns an iterator that
designates the successor node in the same list.  If Iterator designates
the last node of the list, it returns the value Null_Iterator.


procedure Increment (Iterator : in out Iterator_Type);

Equivalent to Iterator := Succ (Iterator).


function Key (Iterator : Iterator_Type) return Key_Type;

If Iterator equals Null_Iterator, the operation fails and
Constraint_Error is propagated.  Otherwise, it returns the key component
of the designated node.


generic
   type Key_Access is access constant Key_Type;
function Generic_Key
  (Iterator : Iterator_Type) return Key_Access;

If Iterator equals Null_Iterator, the operation fails and
Constraint_Error is propagated.  Otherwise, it returns an access value
that designates a constant view of the key component of the node
designated by Iterator.


function Is_Equal_Key
  (Left, Right : Iterator_Type) return Boolean;

Equivalent to Is_Equal_Key (Key (Left), Key (Right)).


function Is_Equal_Key
  (Left  : Iterator_Type;
   Right : Key_Type) return Boolean;

Equivalent to Is_Equal_Key (Key (Left), Right).


function Is_Equal_Key
  (Left  : Key_Type;
   Right : Iterator_Type) return Boolean;

Equivalent to Is_Equal_Key (Left, Key (Right)).


function Element (Iterator : Iterator_Type) return Element_Type;

If Iterator equals Null_Iterator, the operation fails and
Constraint_Error is propagated.  Otherwise, it returns the element
component of the designated node.


generic
   type Element_Access is access all Element_Type;
function Generic_Element
  (Iterator : Iterator_Type) return Element_Access;

If Iterator equals Null_Iterator, the operation fails and
Constraint_Error is propagated.  Otherwise, it returns an access value
that designates a variable view of the element component of the node
designated by Iterator.


procedure Replace_Element
  (Iterator : in Iterator_Type;
   By       : in Element_Type);

Assigns the value By to the element stored on the node designated by
Iterator.  If Iterator equals Null_Iterator, the operation fails and
Constraint_Error is propated.  Any exceptions raised during assignment
are propagated.


generic
   with procedure Process
     (Key     : in     Key_Type;
      Element : in out Element_Type) is <>;
procedure Generic_Process_Element
  (Iterator : in Iterator_Type);

Invokes the actual subprogram bound to Process with the key and element
of the node designated by Iterator.  If Iterator equals Null_Iterator,
the operation fails and Constraint_Error is propagated.  Any exceptions
raised by Process are propagated.


generic
   with procedure Process
     (Iterator : in Iterator_Type) is <>;
procedure Generic_Iteration
  (Container : in Container_Type);

Generic_Iteration calls the actual subprogram bound to Process once for
each node in the Container.  Any exceptions raised during Process are
propagated, immediately terminating the iteration.


generic
   with procedure Process
     (Key     : in     Key_Type;
      Element : in out Element_Type) is <>;
procedure Generic_Process_Elements
  (Container : in Container_Type);

Calls the actual subprogram bound to Process once for each key/element
pair in Container.  Any exception raised during Process is propagated,
immediately terminating the iteration.


generic
   type Key_Access is access all Key_Type;
function Ada.Containers.Maps.Hashed.Unbounded.Unchecked_Modification
  (Iterator : Iterator_Type) return Key_Access;

If Iterator equals Null_Iterator, the operation fails and
Constraint_Error is propagated.  Otherwise, it returns an access value
that designates a variable view of the key component on the node
designated by Iterator.

AARM NOTE

It would be easy to break a map abstraction if keys were modified,
especially if it were easy to do so, when the modification might be
accidental.  However, under some circumstances it may make sense to
modify a key object in a benign way, such that it doesn't change the
value returned by Is_Equal_Key.

!example

The simplest and fastest way to iterate over all the elements in the map
is to use one of the passive iterators:

   procedure Op (Map : in Map_Types.Container_Type) is

      procedure Process (I : in Map_Types.Iterator_Type) is ...;

      procedure Iterate is
         new Generic_Iteration; -- accept default
   begin
      Iterate (Map);
   end;

You could implement this function yourself, by iterating over the list
in each bucket of the hash table:

   procedure Op (Map : in Map_Types.Container_Type) is

      procedure Process (I : in Map_Types.Iterator_Type) is ...;

      I : Map_Types.Iterator_Type;

   begin

      Iterate:
      for Index in 1 .. Size (Map) loop

         I := First (Map, Index);  -- head of list at buckets[index]

         while I /= Null_Iterator loop
            Process (I);
            Increment (I);
         end loop;

      end loop Iterate;

   end Op;

If you want to walk the entire map in the more traditional way, then you
can do it like this:

   procedure Op (Map : in Map_Types.Container_Type) is

      Index : Positive := 1;
      Iter  : Iterator_Type;

      I : Positive := 1;

   begin

      if Length (Map) = 0 then
         return;
      end if;

      Find_First_Iter:
      loop
         Iter := First (Map, Index);
         exit when Iter /= Null_Iterator;
         Index := Index + 1;
      end loop Find_First_Iter;

      Iterate:
      loop

         Process (Iter);  -- process I'th

         I := I + 1;

         exit when I > Length (Map);

         Increment (Iter);

         while Iter = Null_Iterator loop
            Index := Index + 1;
            Iter := First (Map, Index);
         end loop;

      end loop Iterate;

   end Op;

This latter locution allows you to walk a hashed container while you're
simultaneously walking another container.

Generic algorithms are typically written to work with iterators this way:

   generic
      type IT is private;
      with function Succ (I : IT) return IT is <>;
      with procedure Process (I : IT) is <>;
      with function "=" (L, R : IT) return Boolean is <>;
   procedure Generic_Algorithm (First, Back : in IT);

The implementation would look something like this:

   procedure Generic_Algorithm (First, Back : in IT) is
      I : IT := First;
   begin
      while I /= Back loop
         ...
         Process (I);
         ...
         I := Succ (I);
      end loop;
   end Generic_Algorithm;

The benefit is that this algorithm will work with any "sequence of
items," which just means any container with an iterator having the
requisite properties, as specified in the generic formal region.  The
virtue of this approach is that it abstracts-away the container.  The
generic algorithm above (and others like it) works with all the
containers in the library -- it even works for built-in array types.

To make this work with a map, we need only to override the default
successor operation for map iterators and keep around some context:

   procedure Op (Map : in Map_Types.Container_Type) is

      Index : Positive := 1;
      I     : Positive := 1;

      function Succ (Iter : Iterator_Type) return Iterator_Type is
         Result : Iterator_Type := Map_Types.Succ (Iter);
      begin
         I := I + 1;

         if I > Length (Map) then
            return Result;
         end if;

         while Result = Null_Iterator loop
            Index := Index + 1;
            Result := First (Map, Index);
         end loop;

         return Result;
      end Succ;

      procedure Process (Iter : Iterator_Type) is ...;

      procedure Algorithm is
         new Generic_Algorithm (Iterator_Type); -- accept defaults

      First_Iter : Iterator_Type;

   begin -- Op

      if Length (Map) = 0 then
         return;
      end if;

      Find_First_Iter:
      loop
         First_Iter := First (Map, Index);
         exit when First_Iter /= Null_Iterator;
         Index := Index + 1;
      end loop Find_First_Iter;

      Algorithm (First => First_Iter, Back => Null_Iterator);

   end Op;


!end example


The specification of the root of the string-key hashed map containers
subsystem is as follows:

package Ada.Containers.Maps.Hashed.Strings is
   pragma Pure;
end Ada.Containers.Maps.Hashed.Strings;

The specification of the unbounded string-key hashed map container
differs from the unbounded hashed map container in the following manner:

(1) There is no formal Key_Type, as the key type is String.  The generic
    formal region and package name are as follows:

generic

   type Element_Type is private;

   with function Hash
     (Key : String) return Integer'Base is <>;

   with function Is_Equal_Key
     (Left, Right : String) return Boolean is "=";

   with function "="
     (Left, Right : Element_Type) return Boolean is <>;

package Ada.Containers.Maps.Hashed.Strings.Unbounded is ...;


(2) Everywhere where the formal type Key_Type is specified, it is
    systematically replaced by type String.

(3) The Generic_Key function is omitted.

(4) The following function is defined:

function Key_Length (Iterator : Iterator_Type) return Natural;

Equivalent to Key (Iterator)'Length.

(5) The generic child procedure Unchecked_Modification is omitted.


The specification of the root of the wide_string-key hashed map containers
subsystem is as follows:

package Ada.Containers.Maps.Hashed.Wide_Strings is
   pragma Pure;
end Ada.Containers.Maps.Hashed.Wide_Strings;

The specification of the unbounded wide_string-key hashed map container
differs from the unbounded string-key hashed map container in the
following manner:

(1) The name of the package is
    Ada.Containers.Maps.Hashed.Wide_Strings.Unbounded;

(2) Everywhere where the formal type String is specified, it is
    systematically replaced by type Wide_String.

!example

Hashed maps with type String as the key are nearly ubiquitous.  The
canonical example is of course the word-frequency problem, in which
"words" (using some suitable definition of delimiter) are counted as
they are scanned in the input stream.

We can solve this problem easily using a map with string as the key and
subtype Natural as the element:

   with Ada.Containers.Hash_String;

   package Wordcount_Maps is
      new Ada.Containers.Maps.Hashed.Strings.Unbounded
        (Element_Type => Natural,
         Hash         => Ada.Containers.Hash_String); -- case-sensitive

   Map : Wordcount_Maps.Container_Type;

Here we've specified the hash function for strings provided by the
library.  The scanning phase looks like this:

   Open (File, In_File, Name);

   Scan_Lines:
   while not End_Of_File (File) loop

      Get_Line (File, Line, Line_Last);

      Line_First := Line'First;

      Scan_Line:
      loop

         Find_Token (Line (Line_First .. Line_Last), ...);

         exit when Word_Last = 0;

         --THIS IS THE OP WE CARE ABOUT:
         Insert (Word => Line (Word_First .. Word_Last));

         Line_First := Word_Last + 1;

      end loop Scan_Line;

   end loop Scan_Lines;

Now all we have to do is implement Insert.  That function looks like
this:

   procedure Insert (Word : String) is

      Iter    : Wordcount_Maps.Iterator_Type;
      Success : Boolean;

      type Count_Access is access all Natural;

      function To_Access is
        new Wordcount_Maps.Generic_Element (Count_Access);

   begin -- Insert

      Insert
        (Container => Map,
         Key       => To_Lower (Word),
         New_Item  => 0,  -- yes
         Iterator  => Iter,
         Success   => Success);

      declare
         N : Natural renames To_Access (Iter).all;  -- the element
      begin
         N := N + 1;
      end;

   end Insert;

Map (and set) insertion works conditionally.  It searches the container
(here, in the list of the buckets array at the index computed from the
hash-value of the key), to determine whether there is an equal key
already in the map.

Note that in the example above, the New_Item parameter has the value 0.
This is deliberate.  What happens is that if the word is already in the
map, then the insertion "fails" in the sense that no new node is
allocated.  Rather, Insert reports the fact that the key was already in
the map (by returning the value False for Success), and an iterator that
designates the node with the matching key.

But not inserting a new node is exactly the behavior we want.  In the
case of a word already in the map, the iterator returned designates an
existing word/count pair, whose count is non-zero.  When we alias the
count object, we simply increment its value.

However, the word might not be in the map, in which case the insertion
"succeeds," which means a new node is inserted whose element is
initialized to the value of New_Item, which here is 0.  Iterator
designates the newly-inserted element (really, it designates the node
containing that key/element pair).  When we alias the element, the count
has the value 0, and so by incrementing it the count gets set to the
value 1.

Conditional insertion is a necessary feature of any efficient map
abstraction.  It makes no sense to search for the element (via Find,
say) to determine whether it's in the map, and if it's not in the map
call a separate operation to insert it.  This would be horribly
inefficient because the lookup done by insert would only duplicate the
lookup just done by the search.

To dump the contents of the map, you can just use one of the passive
iterators:

   declare
      procedure Process
        (Word  : in     String;
         Count : in out Natural) is
      begin
         Put (Word); Put (':'); Put (Count); New_Line;
      end;

      procedure Dump is
        new Wordcount_Maps.Generic_Process_Elements; -- "Process"
  begin
     Dump (Map);
  end;

This would display the words in their order in the hashed map.  That's
probably not what you want (especially for a well-performing hash table,
which would scatter keys everywhere), which is to display them in order
by frequency.  I showed how to do that in the doubly-linked bounded list
example.  What we did was to populate the list with iterators that
designate the map entries, and then sort the list:

   declare
      List : List_Subtype;

      procedure Process (I : Wordcount_Maps.Iterator_Type) is
      begin
         Append (List, New_Item => I);
      end;

      procedure Populate_List is
         new Wordcount_Maps.Generic_Iteration;  -- "Process"
   begin
      Populate_List (Map);
      ... -- see doubly-linked bounded list
   end;

As with all containers, it's usually simpler and more efficient to use
the passive iterators if you're going to traverse all the elements in
the container.  The library is often used by making a lot of little
on-the-fly instantiations, as in the examples above.

!end example


!example

When a client connects to a streaming media server to play a video, the
server opens the file and then transmits frames to the client.
Ultra-efficient file I/O is usually done by memory-mapping the sections
on disk.  Typically, a server maintains its own file cache, so that many
clients streaming the same file can share the memory-mapped sections.

When the client request comes in, the server must look up the file by
name in order to see if it's in cache.  If it's in cache, then we can
increment a reference count.  If it's not, we create some context for
the file and create a new entry for it in the cache.

Suppose the context type looks like this:

  type Context_Type is limited record
     File      : File_Type;
     Ref_Count : Natural;
     ...;
  end record;

  type Context_Access is access Context_Type;

Our cache is just a map, indexed by file name:

   package File_Cache_Types is
      new Ada.Containers.Maps.Hashed.Strings.Unbounded
        (Element_Type => Context_Access,
         Hash         => Ada.Containers.Hash_String_Case_Insensitive,
         Is_Equal_Key => Ada.Containers.Equal_String_Case_Insensitive);

   File_Cache : File_Cache_Types.Container_Type;

The naive way to implement the lookup is:

   procedure Setup (Name : in String) is

      Iter : File_Cache_Types.Iterator_Type :=
         File (File_Cache, Key => Name);

      Context : Context_Access;

   begin -- Setup

      if Iter = Null_Iterator then

         Context := new Context_Type;
         Context.Ref_Count := 0;
         ... -- init Context

         Insert (File_Cache, Key => Name, New_Item => Context);

      else

         Context := Element (Iter);

      end if;

      Context.Ref_Count := Context.Ref_Count + 1;
      ... -- use Context

   end Setup;

The problem is that we're duplicating work: we first searched for the
file context in the cache, and if it wasn't found we insert a new entry,
which just searches again.  The correct way to do this is as follows:

   procedure Setup (Name : in String) is

      Iter         : File_Cache_Types.Iterator_Type;
      Not_In_Cache : Boolean;
      Context      : Context_Access;

   begin

      Insert
        (Container => File_Cache,
         Key       => Name,
         New_Item  => null,  -- yes
         Iterator  => Iter,
         Success   => Not_In_Cache);

      if Not_In_Cache then

         pragma Assert (Element (Iter) = null);

         Context := new Context_Type;
         Context.Ref_Count := 0;
         ... -- init context

         Replace_Element (Iter, By => Context);

      else

         Context := Element (Iter);

      end if;

      Context.Ref_Count := Context.Ref_Count + 1;
      ... -- use context

   end Setup;

Here we make an insertion attempt, by trying to insert a null context
into the map.  If it's already in the map, then the insertion fails, but
that's just what we want to happen, because we wish to share the file
already in cache.  If it's not in the map, the insertion succeeds, by
creating a slot for this file (the context is null), which we just fill
in with a newly-allocated context object.

!end example


The specification of the root of the sorted map containers subsystem is
as follows:

package Ada.Containers.Maps.Sorted is
   pragma Pure;
end Ada.Containers.Maps.Sorted;


The specification of the sorted map container is as follows:

generic

   type Key_Type is private;

   type Element_Type is private;

   with function "<"
     (Left, Right : Key_Type) return Boolean is <>;

   with function "="
     (Left, Right : Element_Type) return Boolean is <>;

package Ada.Containers.Maps.Sorted.Unbounded is

   pragma Preelaborate;

   type Container_Type is private;

   type Iterator_Type is private;

   Null_Iterator : constant Iterator_Type;

   procedure Assign
     (Target : in out Container_Type;
      Source : in     Container_Type);

   function "=" (Left, Right : Container_Type) return Boolean;

   function Length (Container : Container_Type) return Natural;

   function Is_Empty (Container : Container_Type) return Boolean;

   procedure Clear (Container : in out Container_Type);

   procedure Swap (Left, Right : in out Container_Type);

   procedure Insert
     (Container : in out Container_Type;
      Key       : in     Key_Type;
      New_Item  : in     Element_Type;
      Iterator  :    out Iterator_Type;
      Success   :    out Boolean);

   procedure Insert
     (Container : in out Container_Type;
      Key       : in     Key_Type;
      New_Item  : in     Element_Type);

   procedure Replace
     (Container : in out Container_Type;
      Key       : in     Key_Type;
      New_Item  : in     Element_Type);

   procedure Insert
     (Container : in out Container_Type;
      Key       : in     Key_Type;
      Iterator  :    out Iterator_Type;
      Success   :    out Boolean);

   procedure Insert
     (Container : in out Container_Type;
      Position  : in     Iterator_Type;
      Key       : in     Key_Type;
      New_Item  : in     Element_Type;
      Iterator  :    out Iterator_Type;
      Success   :    out Boolean);

   procedure Insert
     (Container : in out Container_Type;
      Position  : in     Iterator_Type;
      Key       : in     Key_Type;
      Iterator  :    out Iterator_Type;
      Success   :    out Boolean);

   procedure Delete
     (Container : in out Container_Type;
      Key       : in     Key_Type);

   procedure Delete
     (Container : in out Container_Type;
      Iterator  : in out Iterator_Type);

   procedure Delete_Sans_Increment
     (Container : in out Container_Type;
      Iterator  : in out Iterator_Type);

   procedure Delete_First (Container : in out Container_Type);

   procedure Delete_Last (Container : in out Container_Type);

   function Is_In
     (Key       : Key_Type;
      Container : Container_Type) return Boolean;

   function Find
     (Container : Container_Type;
      Key       : Key_Type) return Iterator_Type;

   function Element
     (Container : Container_Type;
      Key       : Key_Type) return Element_Type;

   function Lower_Bound
     (Container : Container_Type;
      Key       : Key_Type) return Iterator_Type;

   function Upper_Bound
     (Container : Container_Type;
      Key       : Key_Type) return Iterator_Type;

   generic
      with procedure Process
        (Element : in out Element_Type) is <>;
   procedure Generic_Equal_Range
     (Container : in Container_Type;
      Key       : in Key_Type);

   function First (Container : Container_Type) return Iterator_Type;

   function First_Key (Container : Container_Type) return Key_Type;

   function First_Element (Container : Container_Type) return Element_Type;

   function Last (Container : Container_Type) return Iterator_Type;

   function Last_Key (Container : Container_Type) return Key_Type;

   function Last_Element (Container : Container_Type) return Element_Type;

   function Back (Container : Container_Type) return Iterator_Type;

   function Succ
     (Iterator : Iterator_Type) return Iterator_Type;

   function Pred
     (Iterator : Iterator_Type) return Iterator_Type;

   procedure Increment (Iterator : in out Iterator_Type);

   procedure Decrement (Iterator : in out Iterator_Type);

   function Key (Iterator : Iterator_Type) return Key_Type;

   generic
      type Key_Access is access constant Key_Type;
   function Generic_Key
     (Iterator : Iterator_Type) return Key_Access;

   function "<"
     (Left, Right : Iterator_Type) return Iterator;

   function "<"
     (Left  : Iterator_Type;
      Right : Key_Type) return Boolean;

   function "<"
     (Left  : Key_Type;
      Right : Iterator_Type) return Boolean;

   function Element (Iterator : Iterator_Type) return Element_Type;

   generic
      type Element_Access is access all Element_Type;
   function Generic_Element
     (Iterator : Iterator_Type) return Element_Access;

   procedure Replace_Element
     (Iterator : Iterator_Type;
      By       : Element_Type);

   generic
      with procedure Process
        (Key     : in     Key_Type;
         Element : in out Element_Type) is <>;
   procedure Generic_Process_Element
     (Iterator : in Iterator_Type);

   generic
      with procedure Process
        (Iterator : in Iterator_Type) is <>;
   procedure Generic_Iteration
     (Container : in Container_Type);

   generic
      with procedure Process
        (Iterator : in Iterator_Type) is <>;
   procedure Generic_Reverse_Iteration
     (Container : in Container_Type);

   generic
      with procedure Process
        (Key     : in     Key_Type;
         Element : in out Element_Type) is <>;
   procedure Generic_Process_Elements
     (Container : in Container_Type);

   generic
      with procedure Process
        (Key     : in     Key_Type;
         Element : in out Element_Type) is <>;
   procedure Generic_Reverse_Process_Elements
     (Container : in Container_Type);

private

   -- not specified by this standard

end Ada.Containers.Maps.Sorted.Unbounded;


The following generic child subprogram is defined:

generic
   type Key_Access is access all Key_Type;
function Ada.Containers.Maps.Sorted.Unbounded.Unchecked_Modification
  (Iterator : Iterator_Type) return Key_Access;

pragma Preelaborate -- TODO: check this
  (Ada.Containers.Maps.Sorted.Unbounded.Unchecked_Modification);


!discussion

A sorted map orders keys in the order implied by the relational operator
passed as the generic actual.  It is implemented as a balanced tree.
The time complexity of insertion, deletion, and find is O(log N) in the
worst case.

When a sorted map container object elaborates, a sentinel node belonging
to the container is allocated automatically.  Its key and element are
uninitialized, except for any controlled initialization or
default-initialized components.  It is not included in the count of
elements in the container, and it cannot be deleted.

!end discussion


Container_Type is a pass-by-reference type.

In the absence of any explicit initialization, objects of type
Iterator_Type are default-initialized to the value Null_Iterator.


procedure Assign
  (Target : in out Container_Type;
   Source : in     Container_Type);

Assigns the key/element pairs in Source to Target.  If Source is an
alias of Target, the operation does nothing.  Any exceptions raised
during internal allocation or deallocation are propagated.

Note that the sentinel doesn't count as an element, so it is never
copied.  The sentinel is not affected by Assign, and so following Assign
the Target has the same sentinel as it did prior to Assign.


function "=" (Left, Right : Container_Type) return Boolean;

The equality operator for sorted maps.  If Left and Right refer to the
same container, then the function returns immediately with the value
True.  If Left and Right have different lengths, then the function
returns immediately with the value False.  Otherwise, elements (and
*only* elements -- keys do not participate in the computation of
container equality) are compared sequentially using the equality
operator supplied as the generic actual.  Any exception raised during
evaluation of element equality is propagated, immediately terminating
the iteration.


function Length (Container : Container_Type) return Natural;

Returns the number of key/element pairs in the container.


function Is_Empty (Container : Container_Type) return Boolean;

Equivalent to Length (Container) = 0.


procedure Clear (Container : in out Container_Type);

Sets the length of the container to 0.  Any exceptions raised during
deallocation are propagated.


procedure Swap (Left, Right : in out Container_Type);

Exchanges the internal nodes Left and Right.  Key/element pairs that
were in the Left map are now in the Right map, and vice versa.  The time
complexity of Swap is O(1).


procedure Insert
  (Container : in out Container_Type;
   Key       : in     Key_Type;
   New_Item  : in     Element_Type;
   Iterator  :    out Iterator_Type;
   Success   :    out Boolean);

Inserts the pair Key/New_Item in Container.  The Key is compared to
other keys already in the map using the subprogram bound to the generic
formal "<" operator.  If the key is already in the map, Success is False
and Iterator designates the node containing the equivalent key.
Otherwise, a new node is allocated and initialized to the values Key and
New_Item, Success returns True and Iterator designates the
newly-allocated node.  Any exceptions raised during evaluation of the
relational operator or during allocation are propagated, and Container
is not modified.  The time complexity of insertion is O(log N) worst
case.

The equality operator "=" for Key_Type is never used.  Instead, a pair
of keys K1 and K2 match if they are "equivalent," which is defined as
"not (K1 < K2) and not (K2 < K1)".


procedure Insert
  (Container : in out Container_Type;
   Key       : in     Key_Type;
   New_Item  : in     Element_Type);

Equivalent to Insert (Container, Key, New_Item, Iterator, Success), with
the difference that the iterator and success parameters are omitted.


procedure Replace
  (Container : in out Container_Type;
   Key       : in     Key_Type;
   New_Item  : in     Element_Type);

Equivalent to Insert (Container, Key, New_Item), with the difference
that if the Key is already in the map, then New_Item is assigned to the
element associated with that key.  Any exceptions raised during the
assignment are propagated.


procedure Insert
  (Container : in out Container_Type;
   Key       : in     Key_Type;
   Iterator  :    out Iterator_Type;
   Success   :    out Boolean);

Inserts a new key/item pair as per Insert (Container, Key, New_Item,
Iterator, Success), with the difference that the element of the
newly-inserted node is uninitialized, except for any controlled
initialization or default-initialized components.


procedure Insert
  (Container : in out Container_Type;
   Position  : in     Iterator_Type;
   Key       : in     Key_Type;
   New_Item  : in     Element_Type;
   Iterator  :    out Iterator_Type;
   Success   :    out Boolean);

Equivalent to Insert (Container, Key, New_Item, Iterator, Success), with
the addition of a Position parameter, which specifies a hint about where
to insert the new key.  If Position equals Null_Iterator, this operation
is equivalent to the Position-less version of Insert.  If Position does
not designate an iterator that belongs to Container, program execution
is erroneous.  If the hint is useful, the time complexity of this
operation is amortized O(1).


procedure Insert
  (Container : in out Container_Type;
   Position  : in     Iterator_Type;
   Key       : in     Key_Type;
   Iterator  :    out Iterator_Type;
   Success   :    out Boolean);

Equivalent to Insert (Container, Position, Key, New_Item, Iterator,
Success), with the difference that the element of the new node is
uninitialized.


procedure Delete
  (Container : in out Container_Type;
   Key       : in     Key_Type);

Deletes the node that matches Key.  Any exceptions raised by "<" are
propagated, and Container is not modified.  Any exception raised during
deallocation is propagated.  The time complexity of this operation is
O(log N), worst case.


procedure Delete
  (Container : in out Container_Type;
   Iterator  : in out Iterator_Type);

If Iterator equals Null_Iterator or Back (Container), the operation does
nothing.  If Iterator does not designate a node that belongs to
Container, program execution is erroneous.  Otherwise, the node
designated by Iterator is removed from the tree.  Any exception raised
during deallocation is propagated.  The iterator value returned
designates the successor of the node deleted.


procedure Delete_Sans_Increment
  (Container : in out Container_Type;
   Iterator  : in out Iterator_Type);

Equivalent to Delete (Container, Iterator), with the difference that the
iterator value returned equals Null_Iterator.


procedure Delete_First (Container : in out Container_Type);

If Container is empty, the operation does nothing.  Otherwise the node
designated by First (Container) is deleted.


procedure Delete_Last (Container : in out Container_Type);

If Container is empty, the operation does nothing.  Otherwise the node
designated by Last (Container) is deleted.


function Is_In
  (Key       : Key_Type;
   Container : Container_Type) return Boolean;

Equivalent to Find (Container, Key) /= Null_Iterator.


function Find
  (Container : Container_Type;
   Key       : Key_Type) return Iterator_Type;

Finds the node whose key is equivalent to Key, and returns an iterator
that designates that node.  If there are no equivalent keys, the
iterator value Null_Iterator is returned.  Any exceptions raised during
evaluation of "<" are propagated.  The time complexity of this operation
is O(log N) in the worst case.


function Element
  (Container : Container_Type;
   Key       : Key_Type) return Element_Type;

Equivalent to Element (Find (Container, Key)).


function Lower_Bound
  (Container : Container_Type;
   Key       : Key_Type) return Iterator_Type;

Returns an iterator that designates the node containing the smallest key
not less than Key.  If there is no key already in the map that is
equivalent to or greater than to Key, then it returns Back (Container).


function Upper_Bound
  (Container : Container_Type;
   Key       : Key_Type) return Iterator_Type;

Returns an iterator that designates the node containing the smallest key
greater than Key.  If there is no key already in the map that is greater
than Key, then it returns Back (Container).


generic
   with procedure Process
     (Element : in out Element_Type) is <>;
procedure Generic_Equal_Range
  (Container : in Container_Type;
   Key       : in Key_Type);

Attempts to find a matching key as per Find (Container, Key).  If a
matching key is found, it invokes the generic actual bound to Process
with the element associated with the key as the parameter.


function First (Container : Container_Type) return Iterator_Type;

Returns an iterator that designates the node containing the smallest
key.  If Container is empty, it returns the iterator value Back
(Container).  This operation has O(1) time complexity.


function First_Key (Container : Container_Type) return Key_Type;

If Container is empty, the operation fails and Constraint_Error is
raised.  Otherwise, returns the key on the node designated by First
(Container).


function First_Element (Container : Container_Type) return Element_Type;

If Container is empty, the operation fails and Constraint_Error is
raised.  Otherwise, returns the element on the node designated by First
(Container).


function Last (Container : Container_Type) return Iterator_Type;

Returns an iterator that designates the node containing the greatest
key.  If Container is empty, it returns the iterator value Back
(Container).  This operation has O(1) time complexity.


function Last_Key (Container : Container_Type) return Key_Type;

If Container is empty, the operation fails and Constraint_Error is
raised.  Otherwise, returns the key on the node designated by Last
(Container).


function Last_Element (Container : Container_Type) return Element_Type;

If Container is empty, the operation fails and Constraint_Error is
raised.  Otherwise, returns the element on the node designated by Last
(Container).


function Back (Container : Container_Type) return Iterator_Type;

Returns an iterator that designates the sentinel node.  This operation
has O(1) time complexity.


function Succ (Iterator : Iterator_Type) return Iterator_Type;

Returns the successor of the node designated by Iterator.  If Iterator
equals Null_Iterator, the operation fails and Constraint_Error is
propagated.

AARM NOTE

A sorted map has wrap-around semantics similar to an unbounded
doubly-linked list.  Therefore the successor of Last is Back, and the
successor of Back is First.  If Container is empty, then the iterator
values returned by First, Last, Back all designate the sentinel.


function Pred (Iterator : Iterator_Type) return Iterator_Type;

Returns the predecessor of the node designated by Iterator.  If Iterator
equals Null_Iterator, the operation fails and Constraint_Error is
propagated.

AARM NOTE

Pred is symmetric to Succ.  Therefore the predecessor of Back is Last,
and the predecessor of First is Back.


procedure Increment (Iterator : in out Iterator_Type);

Equivalent to Iterator := Succ (Iterator).


procedure Decrement (Iterator : in out Iterator_Type);

Equivalent to Iterator := Pred (Iterator).


function Key (Iterator : Iterator_Type) return Key_Type;

Returns the value of the key component of the node designated by
Iterator.  If Iterator equals Null_Iterator, the operation fails and
Constraint_Error is propagated.


generic
   type Key_Access is access constant Key_Type;
function Generic_Key
  (Iterator : Iterator_Type) return Key_Access;

If Iterator equals Null_Iterator, the operation fails and
Constraint_Error is propagated.  Otherwise, it returns an access value
that designates a constant view of the the key component of the node
designated by Iterator.


function "<"
  (Left, Right : Iterator_Type) return Iterator;

Equivalent to Key (Left) < Key (Right).


function "<"
  (Left  : Iterator_Type;
   Right : Key_Type) return Boolean;

Equivalent to Key (Left) < Right.


function "<"
  (Left  : Key_Type;
   Right : Iterator_Type) return Boolean;

Equivalent to Key < Key (Right).


function Element (Iterator : Iterator_Type) return Element_Type;

If Iterator equals Null_Iterator, the operation fails and
Constraint_Error is propagated.  Otherwise, it returns the element
component of the designated node.


generic
   type Element_Access is access all Element_Type;
function Generic_Element
  (Iterator : Iterator_Type) return Element_Access;

If Iterator equals Null_Iterator, the operation fails and
Constraint_Error is propagated.  Otherwise, it returns an access value
that designates a variable view of the the element component of the node
designated by Iterator.


procedure Replace_Element
  (Iterator : Iterator_Type;
   By       : Element_Type);

Assigns the value By to the element stored on the node designated by
Iterator.  If Iterator equals Null_Iterator, the operation fails and
Constraint_Error is propated.


generic
   with procedure Process
     (Key     : in     Key_Type;
      Element : in out Element_Type) is <>;
procedure Generic_Process_Element
  (Iterator : in Iterator_Type);

Invokes the actual subprogram bound to Process with the key and element
of the node designated by Iterator.  If Iterator equals Null_Iterator,
the operation fails and Constraint_Error is propagated.


generic
   with procedure Process
     (Iterator : in Iterator_Type) is <>;
procedure Generic_Iteration
  (Container : in Container_Type);

Generic_Iteration calls the actual subprogram bound to Process once for
each node in the Container (exluding the sentinel, of course).  Any
exceptions raised during Process are propagated, immediately terminating
the iteration.


generic
   with procedure Process
     (Iterator : in Iterator_Type) is <>;
procedure Generic_Reverse_Iteration
  (Container : in Container_Type);

Iterates through the nodes in Container as per Generic_Iteration, with
the difference that the nodes are traversed in reverse order.


generic
   with procedure Process
     (Key     : in     Key_Type;
      Element : in out Element_Type) is <>;
procedure Generic_Process_Elements
  (Container : in Container_Type);

Calls the actual subprogram bound to Process once for each key/element
pair in Container (excluding the sentinel, of course).  Any exception
raised during Process is propagated, immediately terminating the
iteration.


generic
   with procedure Process
     (Key     : in     Key_Type;
      Element : in out Element_Type) is <>;
procedure Generic_Reverse_Process_Elements
  (Container : in Container_Type);

Iterates through the nodes in Container as per Generic_Process_Elements,
with the difference that the elements are traversed in reverse order.


generic
   type Key_Access is access all Key_Type;
function Ada.Containers.Maps.Sorted.Unbounded.Unchecked_Modification
  (Iterator : Iterator_Type) return Key_Access;

If Iterator equals Null_Iterator, the operation fails and
Constraint_Error is propagated.  Otherwise, it returns an access value
that designates a variable view of the key component of the node
designated by Iterator.


!example

In a POSIX OS that supports real-time signals, the OS will deliver a
payload-carrying signal to the app.  In the case of a socket, when I/O
completes asynchronously, the OS delivers an RT signal that specifies
the file descriptor of the socket whose I/O completed.

The problem is that I typically declare the socket as part the
representation of some abstraction that gets allocated dynamically, and
therefore I have no idea which object the socket belonged to, so I have
no idea how to act on the information the OS is providing me.

The abstraction I have in mind looks like this:

   package Sessions is

      type Session_Type (<>) is limited private;

      function Session_Access is access all Session_Type;

      function Setup_Session return Session_Type;

      procedure Play
        (Session : access Session_Type;
         Stream  : in     String);
      ...
      procedure IO_Completion (Session : access Session_Type);

   private

      type Session_Type is limited record
         Socket : Socket_Type;
         ...;
      end Session_Type;

   end Sessions;


What I need to do is correlate the file descriptor reported in the RT
signal to a session object.  With a map it's almost trivial.  In the
body I can instantiate the map like this:

   function "<" (L, R : Session_Access) return Boolean is
   begin
      return L.all'Address < R.all'Address;
   end;

   package FD_Map_Types is
      new Ada.Containers.Maps.Sorted.Unbounded
        (Key_Type     => C.int,
         Element_Type => Session_Access); -- accept "<" default

Now I can declare a map object in the body:

   package Sessions is
      ...
      FD_Map : FD_Map_Types.Container_Type;

When I allocate a new session object, this opens the socket.  A socket
object has a selector function to return its file descriptor.  I use
this as the key to insert the session object into the map:

   function Setup_Session return Session_Access is
     Session : constant Session_Access := new Session_Type;
   begin
      Open (Session.Socket, ...);

      Insert
        (Container => FD_Map,
         Key       => FD (Session.Socket),
         New_Item  => Session);
      ...
      return Session;
   end;

Now that the session object has inserted itself into the map, I can use
map lookup to find that session when I receive a signal.  Something
like:

   procedure Handle_Signal (Siginfo : in Siginfo_Type)
      FD   : constant C.int := Siginfo.FD;
      Iter : constant Iterator_Type := Find (FD_Map, Key => FD);
   begin
      if Iter /= Null_Iterator then
         IO_Completion (Element (Iter));
      end if;
   end Handle_Signal;

and then the session object reacts to the I/O completion accordingly.

Signals come in fast and furious, and I need to handle them quickly.
Searching a sorted map has O(log N) time complexity, which is ideal
because I might have hundreds or even thousands of session objects in
existence.  (As you can probably I guess, I'm a server writer.)

!end example


!example

As usual if you need to iterate over all the items in a sorted map, you
should use a passive iterator.  It's faster and less error-prone.
However, let's assume you need to perform active iteration.  It's much
simpler to (actively) iterate over all the elements in a sorted map than
in a hashed map, because in a sorted container every node is reachable
from any other node.  For example:

   procedure Op (Map : in Map_Types.Container_Type) is
      I : Iterator_Type := First (Map);
      J : constant Iterator_Type := Back (Map);
   begin
      while I /= J loop
         Process (I);
         Increment (I);
      end loop;
   end loop;

To use our little generic algorithm from the earlier example, there's
nothing special we need to do to instantiate it:

   procedure Op (Map : in Map_Subtype) is

      procedure Process (I : in Iterator_Type) is ...;

      procedure Algorithm is
         new Generic_Algorithm (Iterator_Type); -- accept defaults
   begin
      Algorithm (First (Map), Back (Map));
   end;

!end example


The specification of the root of the string-key sorted map containers
subsystem is as follows:

package Ada.Containers.Maps.Sorted.Strings is
   pragma Pure;
end Ada.Containers.Maps.Sorted.Strings;

The specification of the unbounded string-key sorted map container
differs from the unbounded sorted map container in the following manner:

(1) There is no formal Key_Type, as the key type is String.  The generic
    formal region and package name are as follows:

generic

   type Element_Type is private;

   with function "<"
     (Left, Right : String) return Boolean is <>;

   with function "="
     (Left, Right : Element_Type) return Boolean is <>;

package Ada.Containers.Maps.Sorted.Strings.Unbounded is ...;


(2) Everywhere where the formal type Key_Type is specified, it is
    systematically replaced by type String.

(3) The Generic_Key function is omitted.

(4) The following function is defined:

function Key_Length (Iterator : Iterator_Type) return Natural;

Equivalent to Key (Iterator)'Length.

(5) The generic child procedure Unchecked_Modification is omitted.


The specification of the root of the wide_string-key sorted map containers
subsystem is as follows:

package Ada.Containers.Maps.Sorted.Wide_Strings is
   pragma Pure;
end Ada.Containers.Maps.Sorted.Wide_Strings;

The specification of the unbounded wide_string-key sorted map container
differs from the unbounded string-key sorted map container in the
following manner:

(1) The name of the package is
    Ada.Containers.Maps.Sorted.Wide_Strings.Unbounded.

(2) Everywhere where the formal type String is specified, it is
    systematically replaced by type Wide_String.


!example

In the RTSP protocol, requests are sent to a server in (clear) text.  To
create a session, a client connects to the server and sends it a SETUP
request, e.g.

SETUP rtsp://mheaney/thematrix.avi RTSP/1.0
Transport: ...

Each RTSP session has a "session id," which is a random string of
characters at least 8 characters long.  When a SETUP request doesn't
specify a session id, this is interpreted to mean that the client wishes
to create a new session.

On the server side, it allocates a session object (described above), and
generates a session id for it.  The representation of the session object
looks like this:

   type Session_Type is limited record
      Id : String (1 .. 8);
      ...;
   end record;

And the session constructor looks like:

   function Setup_Session return Session_Access is
      Session : constant Session_Access := new Session_Type;
   begin
      Generate_Id (Session.Id);
      ... -- see below
   end;

The server responds by including the session id in the response:

RTSP/1.0 200 OK
Session: HAiye8-r

And thereafter, a client sends requests to a session by specifying a
session id header:

PLAY rtsp://mheaney/thematrix.avi RTSP/1.0
Range: npt=101.42-
Session: HAiye8-r

The server-side problem is this.  When the server receives the request
from the client, it parses the request and looks for a session id.  The
problem then becomes finding the session object that is associated with
that unique id.  There might very well be hundreds of session objects,
so whatever method we use has to run fast.  (The is a real-time
streaming server, and RTSP request/response overhead must be kept to a
minimum.)

What we do is declare a string-key map object that uses the session id
as the key, and a Session_Access as the element, like this:

   package Id_Maps is
      new Ada.Containers.Maps.Sorted.Strings.Unbounded
        (Element_Type => Session_Access);
        -- accept "<" default for type String

   Id_Map : Id_Maps.Container_Type;
   use Id_Maps;

When the session is allocated, we insert the id/session pair into the
map like this:

   function Setup_Session return Session_Access is
      Session : constant Session_Access := new Session_Type;
   begin
      Generate_Id (Session.Id);

      Insert
        (Container => Id_Map,
         Key       => Session.Id,
         New_Item  => Session);
      ...
      return Session;
   end;


When a client issues a request, we parse out the session id, and then
forward the command to the session object associated with that session
id key:

   procedure Play
     (Session_Id  : in     String;
      NPT_Range   : in     NPT_Range_Type;
      RTSP_Status :    out RTSP_Status_Type) is

      Iter : constant Iterator_Type :=
        Find (Id_Map, Key => Session_Id);

      Session : Session_Access;
   begin
      if Iter = Null_Iterator then
         RTSP_Status := RTSP.Session_Not_Found;
         return;
      end if;

      Session := Element (Iter);

      Play (Session, NPT_Range, RTSP_Status);
   end;

Both insertion and find execute fast: O(log N) in the worst case.
Having these kinds of performance guarantees is important in real-time
programs.

Actually, this example could have been written just as easily using the
string-key hashed map.  The only difference would be the instantiation
of the map container package, but otherwise the rest of the code would
be identical.

!end example


!example

The Id map we instantiated in the previous example takes type
Session_Access as the element type.  The constructor (repeated here)
looks like this:

   function Setup_Session return Session_Access is
      Session : constant Session_Access := new Session_Type;
   begin
      Generate_Id (Session.Id);

      Insert
        (Container => Id_Map,
         Key       => Session.Id,
         New_Item  => Session);
      ...
      return Session;
   end;

Here we first allocate a session object, and then separately (in Insert)
allocate a node (in the container) whose element is the pointer to the
session object.  If you think about it, this means there are actually
two separate allocations: one for the element (here, a session object),
and another for the node.

If a container were to support element types that were limited, then we
could rewrite the example like this:

   package Id_Maps is
      new Ada.Containers.Maps.Sorted.Strings.Unbounded_Limited
        (Element_Type => Session_Type);   -- not an access type

   Id_Map : Id_Maps.Container_Type;

That would allow use to allocate just a node, and then implement the
constructor to return a pointer that designates the session object
element on the node itself:

   function To_Access is
      new Id_Maps.Generic_Element (Session_Access);

   function Setup_Session return Session_Access is

      I : Id_Maps.Iterator_Type;
      B : Boolean;

      Id : Id_Subtype;
      Session : Session_Access;  -- no allocation here

   begin

      Generate_Id (Id);

      Insert
        (Container => Id_Map,
         Key       => Id,  -- specify key only; no element
         Iterator  => I,
         Success   => B;

      Session := To_Access (I);  -- point to node's element
      ...
      return Session;

   end Session_Setup;

It doesn't make any difference to the caller of Setup_Session how the
session object it returns is allocated.  Indeed the whole point of that
function is to hide that very detail.

In the interest of keeping things small, this API does not specify any
containers that support limited elements.  But here you get a taste of
how containers that do support limited elements would work.  It's nice
because we can push the responsibility for "deallocating" the element
onto the container (when the node is deleted, it contains the element,
so the element goes away too).  Compare this to our other examples,
which require explicit action by the client to deallocate the object
(because the container element is just an access object that designates
the "real" object).

!end example


!example

We have been silent about the operation of Generate_Id, which makes a
session id for us comprising a sequence of 8 random characters.  One of
our requirements for a session id is that it must be unique among all
the sessions currently being streamed by this server.

Even if we use a random number generator to synthesize characters, we
still must check to ensure that this session id is unique.  The obvious
way is to use Find to see if it's in the already:

   procedure Generate_Id (Id : out Id_Subtype) is
      Iter : Id_Maps.Iterator_Type;
   begin
      loop
         Synthesize_Random_String (Id);
         Iter := Find (Id_Map, Key => Id);
         exit when Iter = Null_Iterator;  -- good: not found
      end loop;
   end Generate_Id;

The constructor for session objects generates a unique session id, and
then uses the id as the key when inserting the new session object:

   function Setup_Session return Session_Access is

      I : Id_Maps.Iterator_Type;
      B : Boolean;

      Id : Id_Subtype;
   begin
      Generate_Id (Id);

      Insert
        (Container => Id_Map,
         Key       => Id,
         Iterator  => I,
         Success   => B;
      ...
   end Setup_Session;

This works, but we can do better.  The problem is that Insert duplicates
the work done just earlier by Find, when checking the newly-synthesized
id for uniqueness.

Find is a bit of a sledgehammer: it tells us that the key isn't already
in the map, but it doesn't tell us anything else.  What would like to
ask the container is, if the key isn't in the map, can you tell me who
its neighbor would be?

The function we're interested in is called Lower_Bound, which searches
the container for the smallest key in the map not less than some key.
It satisfies the invariant that

   Key <= Lower_Bound (Container, Key)

The result of Lower_Bound is an iterator that designates the node that
matches Key, if indeed Key is already in the map, or an iterator that
designates its successor, if Key is not in the map.  To disambiguate
which case we have we only need to make a simple test:

   Iter := Lower_Bound (Container, Key);

   if Key < Iter then
      ... -- no match: Key is not in the map
   else
      ... -- match: Key is equivalent to Key (Iter)
   end if;

Let's rewrite our Generate_Id procedure so that it returns this extra
information:

   procedure Generate_Id
     (Id   : out Id_Subtype;
      Hint : out Id_Maps.Iterator_Type) is
   begin
      loop
         Synthesize_Random_String (Id);
         Iter := Lower_Bound (Id_Map, Key => Id);
         exit when Hint = Back (Id_Map);  -- no match
         exit when Id < Hint;             -- no match
      end loop;
   end Generate_Id;

Now we can rewrite our constructor to use this new information during
insertion:

   function Setup_Session return Session_Access is

      I : Id_Maps.Iterator_Type;
      B : Boolean;

      Id   : Id_Subtype;
      Hint : Id_Maps.Iterator_Type;
   begin
      Generate_Id (Id, Hint);

      Insert
        (Container => Id_Map,
         Position  => Hint,
         Key       => Id,
         Iterator  => I,
         Success   => B;

      pragma Assert (B);
      pragma Assert (Succ (I) = Hint);
      ...
   end Setup_Session;

Here we use the insert-with-hint procedure, which specifies where to
being searching for a matching Id.  Since Hint is the result of
Lower_Bound, we know the hint is useful and therefore the insertion has
amortized constant time.  It's hard to beat that!

This is a great because we can test whether insert will succeed, and
then reuse the results our test so that the insertion itself has
essentially no cost.  Thus we have a way to first search for a key and
then insert it later, without incuring any penalty.

!end example


!example

When a client issues a play request, he can specify the time from which
play begins.  This lets him jump to any position in the file and play
from that point.  Let's assume a video frame index is implemented as a
sorted set, with elements that look like this:

   0   9  34  42  50 78  85 ...

where the numbers above represent the time of the leading edge of each
video frame in the file.

If the play request specifies a time, have to find the frame that "goes
with" that time, which means the frame whose leading edge comes at or
before the time specified.

This is kind of like a floor function.  You might think you could use
Lower_Bound to do it, like this:

   function Floor (Time : Time_Type) return Iterator_Type is
   begin
      return Lower_Bound (Video_Index, Key => Time);
   end;

This would work if the key matched a value already in the index, for
example:

   Floor (42) = Lower_Bound (VI, 42) = 42

The problem is if the key isn't in the index.  Lower_Bound returns the
smallest key in the container equal to or greater than a key, e.g.

   Floor (45) = Lower_Bound (VI, 45) = 50

But this isn't what we want.  The "floor" of 45 is 42, not 50.

The solution is to use Upper_Bound, which returns the smallest key in the
container greater than a key, and then back up one position:

   function Floor (Time : Time_Type) return Iterator_Type is
   begin
      return Pred (Upper_Bound (VI, Key => Time));
   end;

This works for all key values:

   Floor (42) = Pred (UB (VI, 42)) = Pred (50) = 42
   Floor (45) = Pred (UB (VI, 45)) = Pred (50) = 42

And now all is well.  Note that it doesn't matter if you specify some
large key value that is greater than every key in the set.  In that case
Upper_Bound returns Back, and the Pred of Back is Last, which is just
what we want.

!end example


!example

When we create a session object, we insert a pointer to the session
object in the Id_Map.  The complementary problem is how to handle
deletion of the session object.  Suppose we have a function like this:

   procedure Free (X : in out Session_Access);

What's the best way to remove the object from the map?  Since the
session knows its own Id, it can use the key-form of Delete:

   procedure Free (X : in out Session_Access) is
   begin
      if X /= null then
         Delete (Id_Map, Key => X.Id);
         Deallocate (X);
      end if;
   end;

This takes care of removing the session from the map.  But consider
this: to delete a node, you must find the node, and here that means
traversing the internal tree from its root downward to find the node
whose key is equivalent to Key (here, the session id).

Another option for us is to use the iterator-form of Delete.  This is
going to more efficient, because no tree traversal is necessary to find
the node.  You already have the node -- it's what is designated by the
iterator.

What we can do is cache the iterator returned by Insert as part of the
state of the session object:

   function Setup_Session return Session_Access is
      Session : constant Session_Access := new Session_Type;
      Success : Boolean;
   begin
      Generate_Id (Session.Id);

      Insert
        (Container => Id_Map,
         Key       => Session.Id,
         New_Item  => Session,
         Iterator  => Session.Id_Map_Iter,
         Success   => Success);

      pragma Assert (Success);
      pragma Assert (Key (Session.Id_Map_Iter) = Session.Id);
      pragma Assert (Element (Session.Id_Map_Iter) = Session);
      ...

      return Session;
   end Setup_Session;

Now we can implement Free this way:

   procedure Free (X : in out Session_Access) is
   begin
      if X /= null then
         Delete (Id_Map, Iterator => X.Id_Map_Iter);  -- faster
         Deallocate (X);
      end if;
   end;

This turns out to be a very common idiom.  In the body of a package that
declares a type, you declare a set or map container to keep track of
instances of the type.  As part of its representation, the type includes
an iterator that designates the node that contains this instance.  When
the object is finalized, it deletes itself from the container.

!end example

!example

When the server shuts down, it tears down all the session objects.  But
where are the session objects?  They're all in the Id_Map, of course!
Given the Free operation defined above, we can implement a session
shutdown operation like this:

   Id_Map : Id_Map_Types.Container_Type;
   ...

   procedure Shutdown_Sessions is
      Session : Session_Access;
   begin
      while not Is_Empty (Id_Map) loop
         Session := First_Element (Id_Map);
         Free (Session);  -- removes this session from id_map
      end loop;
   end Shutdown_Sessions;

Since a session object removes itself from the map during Free, this
decrements the length of the map, which means that the loop iteration
will eventually terminate.

In the examples we've seen, the session object inserts itself into the
map during the constructor function Setup_Session, and deletes itself
during the destructor operation Free.  If the type is controlled,
another possibility is to do the insertion during Initialize, and the
deletion during Finalize.  This technique would work even for
stack-allocated objects.

!end example

!example

Let's pretend Free doesn't remove the designated session object from the
map, but rather has its traditional semantics of merely deallocating the
object.  To shutdown all the sessions, we could do this instead:

   Id_Map : Id_Map_Types.Container_Type;
   ...

   procedure Shutdown_Sessions is

      procedure Process
        (Id      : in     String;
         Session : in out Session_Access) is
      begin
         Free (Session);
      end;

      procedure Free_Sessions is
         new Id_Map_Types.Generic_Process_Elements; -- accept default

   begin -- Shutdown_Sessions

      Free_Sessions (Id_Map);

      Clear (Id_Map);

   end Shutdown_Sessions;

The passive iterator visits all the sessions in the map and Free's them.
That leaves us with a map with all of its elements set to null.  We then
Clear the map, which sets its length to 0.

Clear is probably going to be faster than deletion one-element-at-a-time
as in our previous example.  The reason is that a sorted map is
implemented as a balanced tree, and when you delete a single item, the
tree must be fixed-up to keep it balanced.

With Clear, however, it can simply deallocate all the nodes in one fell
swoop.  It doesn't need to rebalance the tree because the tree itself
is being deleted.

!end example

!example

Suppose we have a key declared this way:

   type Key_Type is record
      X : Integer;
      Y : Integer;
   end record;

It's going to be used to instantiate a map container.  The sorted key
relation is defined as follows:

   function "<" (L, R : Key_Type) return Boolean is
   begin
      return L.X < R.X;
   end;

Note that the key relation only depends on the value of the X component.
We instantiate the map in the normal way:

   package Map_Types is
      new Ada.Containers.Maps.Sorted.Unbounded
        (Key_Type,
         Element_Type);
         -- assume "<" is directly visible

   Map : Map_Types.Container_Type;

Now let's insert a key into the map:

   procedure Insert_42_1776 (I : out Iterator_Type) is
      K : Key_Type := (X => 42, Y => 1776);
      B : Boolean;
   begin
      Insert (Map, K, I, B);
   end;

Now let's suppose we need to change the Y component of our key.  One way
to do that would be to delete it from the map, change the value, and
then (re)insert it, like this:

   procedure Insert_42_2001 (I : in out Iterator_Type) is
      K : constant Key_Type := Key (I);
      E : constant Element_Type := Element (I);
      B : Boolean;
   begin
      Delete_Sans_Increment (Map, I);
      K.Y := 2001;      -- modify key
      Insert (Map, K, E, I, B);
   end;

Here we used the Delete_Sans_Increment op, which is more efficient than
Delete because it doesn't perform a tree traversal to find the
successor of the node deleted.

However, we can do better.  The issue in the fragment above it that to
insert a new key/element pair, you must traverse the tree starting from
its root, in order to find the correct insertion position.  Let's use
the normal form of delete, and then use hint-form of insert:

   procedure Insert_42_2001_v2 (I : in out Iterator_Type) is
      K : constant Key_Type := Key (I);
      E : constant Element_Type := Element (I);
      B : Boolean;
   begin
      Delete (Map, I);  -- I now designates successor
      K.Y := 2001;      -- modify key
      Insert (Map, I, K, E, I, B);  -- supply hint
   end;

The Delete operation returns the successor of the node deleted.  We can
use this information when we re-insert the key into the map.  Since the
iterator I really does designate the successor of key K, the cost of
insertion is less, because we don't have to perform a tree traversal.

However, we can do better still.  What we did was make a copy of the
key/element in the map, then delete the node containing the original
key/element pair, then turn around an immediately insert the key/element
back into the map.  This is silly.  What we really wanted to do was
modify the key object in place.

The library provides such a parachute function.  It's the generic child
procedure Unchecked_Modification.  It returns an access value that
designates a variable view of the key:

   with Ada.Containers.Maps.Sorted.Unbounded.Unchecked_Modification;

   type Key_Access is access all Key_Type;
   for Key_Access'Storage_Size use 0;

   package Map_Types is ...;

   function Modify_Key is
      new Map_Types.Unchecked_Modification (Key_Type);

Now that we have this modifier, we can rewrite our little op to be much
more efficient:

   procedure Insert_42_2001_v3 (I : in out Iterator_Type) is
      K : Key_Type renames Modify_Key (I).all;
   begin
      K.Y := 2001; -- that's all!
   end;

Clearly, modifying keys indiscriminantly could break the map.  However,
sometimes we have the need to modify a key, and here it's perfectly
safe, because we only change Y, not X.  So this is a benign change as
far as the map is concerned.

!end example


The specification of the root of the multimaps subsystem is as follows:

package Ada.Containers.Multimaps is
   pragma Pure;
end Ada.Containers.Multimaps;


The specification of the root of the hashed multimap containers
subsystem is as follows:

package Ada.Containers.Multimaps.Hashed is
   pragma Pure;
end Ada.Containers.Multimaps.Hashed;


The specification of the unbounded hashed multimap container differs
from the unbounded hashed (unique-key) map container as follows:

(1) The name of the package is
    Ada.Containers.Multimaps.Hashed.Unbounded.

(2) The signature and semantics of the operations named Insert change as
    follows:

procedure Insert
  (Container : in out Container_Type;
   Key       : in     Key_Type;
   New_Item  : in     Element_Type;
   Iterator  :    out Iterator_Type);

If Length (Container) equals Size (Container), then the size is
increased as per Resize.  The position of the new node in the buckets
array is computed from the hash value of Key and Size (Container).  The
actual subprogram bound to Is_Equal_Key is used to compare Key to the
key of each node in the list at that index of the buckets array.  If the
predicate returns True, then a new node is allocated with the first
matching node as its successor; otherwise, a new node is allocated at
the head of the list.  Iterator designates the newly-inserted node.  Any
exceptions raised during allocation are propagated, and Container is not
modified (except for any possible resizing).


procedure Insert
  (Container : in out Container_Type;
   Key       : in     Key_Type;
   New_Item  : in     Element_Type);

Equivalent to Insert (Container, Key, New_Item, Iterator), with the
difference that the iterator parameter is omitted.


procedure Insert
  (Container : in out Container_Type;
   Key       : in     Key_Type;
   Iterator  :    out Iterator_Type);

Inserts the Key into Container as per Insert (Container, Key, New_Item,
Iterator), with the difference that the element is uninitialized, except
for any controlled initialization or default-initialized components.

(3) The signature and semantics of the operations named
    Insert_Sans_Resize change as follows:

procedure Insert_Sans_Resize
  (Container : in out Container_Type;
   Key       : in     Key_Type;
   New_Item  : in     Element_Type;
   Iterator  :    out Iterator_Type);

Inserts the Key/New_Item pair into Container as per Insert (Container,
Key, New_Item, Iterator), with the difference that there is no resize
prior to the insertion proper.  If Size (Container) equals 0, the
operation fails and Constraint_Error is propagated.


procedure Insert_Sans_Resize
  (Container : in out Container_Type;
   Key       : in     Key_Type;
   New_Item  : in     Element_Type);

Equivalent to Insert_Sans_Resize (Container, Key, New_Item, Iterator),
with the difference that the iterator parameter is omitted.


procedure Insert_Sans_Resize
  (Container : in out Container_Type;
   Key       : in     Key_Type;
   Iterator  :    out Iterator_Type);

Equivalent to Insert_Sans_Resize (Container, Key, New_Item, Iterator),
with the difference that the element is uninitialized.


(4) The operations Replace and Replace_Sans_Resize are omitted.

(5) The semantics of Delete (Container, Key) change as follows:

procedure Delete
  (Container : in out Container_Type;
   Key       : in     Key_Type);

Deletes all the nodes that match Key.  If Length (Container) equals 0,
then Delete does nothing.  Otherwise the buckets array index is computed
from the hash value of Key and Size (Container).  Any exception raised
during invokation of Hash is propagated.  The nodes in the list at that
index are compared to Key using Is_Equal_Key.  Any exceptions raised by
Is_Equal_Key or during the deallocation are propagated, immediately
terminating the iteration.

(6) The generic child procedure is defined as follows:

generic
   type Key_Access is access all Key_Type;
function Ada.Containers.Multimaps.Hashed.Unbounded.Unchecked_Modification
  (Iterator : Iterator_Type) return Key_Access;

pragma Preelaborate  --OK?
  (Ada.Containers.Multimaps.Hashed.Unbounded.Unchecked_Modification);

This operation has the same semantics as the corresponding hashed map
procedure.

!example

Find returns an iterator that designates the first node in the subrange
of matching keys.  We can then walk the sublist to find all the matching
keys:

   procedure Do_Something
     (M : Map_Subtype;
      K : Key_Subtype) is

      I : Iterator := Find (M, K);
   begin
      if I = Null_Iterator then  -- no matching keys
         return;
      end if;

      loop
         Process (Element (I));
         Increment (I);
         exit when I = Null_Iterator;
         exit when not Is_Equal_Key (I, Key);
      end loop;
   end;

Of course, a simpler way to do this is to use Generic_Equal_Range:

   procedure Do_Something is
      new Multimap_Types.Generic_Equal_Range (Process);

This avoids having to walk the sublist manually.

!end example

!example

There's no predefined operation for counting the number of matching
keys, but we can implement such a function easily enough:

   function Count
     (Map : in Multimap_Types.Container_Type;
      Key : in Key_Subtype) return Natural is

      Result : Integer'Base := 0;

      I : Iterator_Type := Find (Map, Key);

   begin -- Count

      if I /= Null_Iterator then

         loop
            Result := Result + 1;
            Increment (I);
            exit when I = Null_Iterator;
            exit when not Is_Equal_Key (I, Key);
         end loop;

      end if;

      return Result;

   end Count;

Compare this to the sorted multimap example.

!end example


The specification of the root of the string-key hashed multimap containers
subsystem is as follows:

package Ada.Containers.Multimaps.Hashed.Strings is
   pragma Pure;
end Ada.Containers.Multimaps.Hashed.Strings;

The specification of the unbounded string-key hashed multimap container
differs from the unbounded hashed multimap container in the following
manner:

(1) There is no formal Key_Type, as the key type is String.  The generic
    formal region and package name are as follows:

generic

   type Element_Type is private;

   with function Hash
     (Key : String) return Integer'Base is <>;

   with function Is_Equal_Key
     (Left, Right : String) return Boolean is "=";

   with function "="
     (Left, Right : Element_Type) return Boolean is <>;

package Ada.Containers.Multimaps.Hashed.Strings.Unbounded is ...;


(2) Everywhere where the formal type Key_Type is specified, it is
    systematically replaced by type String.

(3) The Generic_Key function is omitted.

(4) The following function is defined:

function Key_Length (Iterator : Iterator_Type) return Natural;

Equivalent to Key (Iterator)'Length.

(5) The generic child procedure Unchecked_Modification is omitted.


The specification of the root of the wide_string-key hashed multimap
containers subsystem is as follows:

package Ada.Containers.Multimaps.Hashed.Wide_Strings is
   pragma Pure;
end Ada.Containers.Multimaps.Hashed.Wide_Strings;

The specification of the unbounded wide_string-key hashed multimap
container differs from the unbounded string-key hashed multimap
container in the following manner:

(1) The name of the package is
    Ada.Containers.Multimaps.Hashed.Wide_Strings.Unbounded;

(2) Everywhere where the formal type String is specified, it is
    systematically replaced by type Wide_String.


The specification of the root of the sorted multimap containers
subsystem is as follows:

package Ada.Containers.Multimaps.Sorted is
   pragma Pure;
end Ada.Containers.Multimaps.Sorted;


The specification of the unbounded sorted multimap container differs
from the unbounded sorted (unique-key) map as follows:

(1) The name of the package is
    Ada.Containers.Multimaps.Sorted.Unbounded.

(2) The signature and semantics of the Insert operations differ as
    follows:

procedure Insert
  (Container : in out Container_Type;
   Key       : in     Key_Type;
   New_Item  : in     Element_Type;
   Iterator  :    out Iterator_Type);

Inserts the pair Key/New_Item in Container.  The Key is compared to
other keys already in the container using the subprogram bound to the
generic formal "<" operator.  The Key/New_Item pair is inserted after
equivalent keys already in the container.  Any exceptions raised during
evaluation of the relational operator or during allocation are
propagated, and Container is not modified.

The equality operator "=" for Key_Type is never used.  Instead, a pair
of keys K1 and K2 match if they are "equivalent," which is defined as
"not (K1 < K2) and not (K2 < K1)".


procedure Insert
  (Container : in out Container_Type;
   Key       : in     Key_Type;
   New_Item  : in     Element_Type);

Equivalent to Insert (Container, Key, New_Item, Iterator), with the
difference that the iterator parameter is omitted.


procedure Insert
  (Container : in out Container_Type;
   Key       : in     Key_Type;
   Iterator  :    out Iterator_Type);

Inserts a new key/item pair as per Insert (Container, Key, New_Item,
Iterator), with the difference that the element of the newly-inserted
node is uninitialized (except for any controlled initialization or
default-initialized components).


procedure Insert
  (Container : in out Container_Type;
   Position  : in     Iterator_Type;
   Key       : in     Key_Type;
   New_Item  : in     Element_Type;
   Iterator  :    out Iterator_Type);

Equivalent to Insert (Container, Key, New_Item, Iterator), with the
addition of a Position parameter, which specifies a hint about where to
begin searching for equivalent keys.  If Position equals Null_Iterator,
this operation is equivalent to the Position-less version of Insert.  If
Position does not designate a node that belongs to Container, program
execution is erroneous.

Insert first assumes Position is a potential successor.  If Position
equals Back (Container) or Key is less than or equal to the key
designated by Position, and Position equals First (Container) or the
predecessor of Position is less than or equivalent to Key, then the
Key/New_Item pair is inserted immediately prior to Position.

If Position is not a successor, then Insert assumes Position is a
potential predecessor.  If Position is less than or equivalent to Key,
and Key is less than or equivalent to the successor of Position, then
the Key/New_Item pair is inserted immediately following Position.

Otherwise, the Key/New_Item pair is inserted as per the Position-less
version of Insert.


procedure Insert
  (Container : in out Container_Type;
   Position  : in     Iterator_Type;
   Key       : in     Key_Type;
   Iterator  :    out Iterator_Type);

Equivalent to Insert (Container, Position, Key, New_Item, Iterator),
with the difference that the element of the new node is uninitialized
(except for any controlled initialization or default-initialized
components).


(3) The semantics of Delete (Container, Key) differs as follows:

procedure Delete
  (Container : in out Container_Type;
   Key       : in     Key_Type);

Deletes all the nodes whose keys are equivalent to Key.  Any exceptions
raised by "<" or during deallocation are propagated.


(4) The semantics of Find change as follows.

Finds the front-most node whose key is equivalent to Key, and returns an
iterator that designates that node.  If there are no equivalent keys,
the iterator value Null_Iterator is returned.  Any exceptions raised
during evaluation of "<" are propagated.  The time complexity of this
operation is O(log N) worst case.


(5) The generic child procedure is defined as follows:

generic
   type Key_Access is access all Key_Type;
function Ada.Containers.Multimaps.Sorted.Unbounded.Unchecked_Modification
  (Iterator : Iterator_Type) return Key_Access;

pragma Preelaborate  --TODO: verify this with ARG
  (Ada.Containers.Multimaps.Sorted.Unbounded.Unchecked_Modification);

This operation has the same semantics as the corresponding hashed
multimap procedure.


!example

Here's how to count the equivalent keys in a sorted multimap:

   function Count
     (Map : in Multimap_Types.Container_Type;
      Key : in Key_Subtype) return Natural is

      Result : Integer'Base := 0;

      I : Iterator_Type := Lower_Bound (M, K);
      J : constant Iterator_Type := Upper_Bound (M, K);

   begin -- Count

      while I /= J loop
         Result := Result + 1;
         Increment (I);
      end loop;

      return Result;

   end Count;

Compare this to the hashed multimap example.

!end example


The specification of the root of the string-key sorted multimap containers
subsystem is as follows:

package Ada.Containers.Multimaps.Sorted.Strings is
   pragma Pure;
end Ada.Containers.Multimaps.Sorted.Strings;

The specification of the unbounded string-key sorted multimap container
differs from the unbounded sorted multimap container in the following
manner:

(1) There is no formal Key_Type, as the key type is String.  The generic
    formal region and package name are as follows:

generic

   type Element_Type is private;

   with function "<"
     (Left, Right : String) return Boolean is <>;

   with function "="
     (Left, Right : Element_Type) return Boolean is <>;

package Ada.Containers.Multimaps.Sorted.Strings.Unbounded is ...;


(2) Everywhere where the formal type Key_Type is specified, it is
    systematically replaced by type String.

(3) The key of the sentinel node has a length of 0.

(3) The Generic_Key function is omitted.

(4) The following function is defined:

function Key_Length (Iterator : Iterator_Type) return Natural;

Equivalent to Key (Iterator)'Length.

(5) The generic child procedure Unchecked_Modification is omitted.


The specification of the root of the wide_string-key sorted multimap
containers subsystem is as follows:

package Ada.Containers.Multimaps.Sorted.Wide_Strings is
   pragma Pure;
end Ada.Containers.Multimaps.Sorted.Wide_Strings;

The specification of the unbounded wide_string-key sorted multimap
container differs from the unbounded string-key sorted multimap
container in the following manner:

(1) The name of the package is
    Ada.Containers.Multimaps.Sorted.Wide_Strings.Unbounded;

(2) Everywhere where the formal type String is specified, it is
    systematically replaced by type Wide_String.

The following library-level subprograms are defined:

function Ada.Containers.Hash_String
   (Key : String) return Integer'Base;
pragma Pure (Ada.Containers.Hash_String);

function Ada.Containers.Hash_String_Case_Insensitive
   (Key : String) return Integer'Base;
pragma Pure (Ada.Containers.Hash_String_Case_Insensitive);

function Ada.Containers.Equal_String_Case_Insensitive
   (Left, Right : String) return Boolean;
pragma Pure (Ada.Containers.Equal_String_Case_Insensitive);

function Ada.Containers.Less_String_Case_Insensitive
   (Left, Right : String) return Boolean;
pragma Pure (Ada.Containers.Less_String_Case_Insensitive);

function Ada.Containers.Hash_Wide_String
   (Key : Wide_String) return Integer'Base;
pragma Pure (Ada.Containers.Hash_Wide_String);

function Ada.Containers.Hash_Wide_String_Case_Insensitive
   (Key : Wide_String) return Integer'Base;
pragma Pure (Ada.Containers.Hash_Wide_String_Case_Insensitive);

function Ada.Containers.Equal_Wide_String_Case_Insensitive
   (Left, Right : Wide_String) return Boolean;
pragma Pure (Ada.Containers.Equal_Wide_String_Case_Insensitive);

function Ada.Containers.Less_Wide_String_Case_Insensitive
   (Left, Right : Wide_String) return Boolean;
pragma Pure (Ada.Containers.Less_Wide_String_Case_Insensitive);

DESIGN NOTE:
I think we should have hash functions for all predefined types, such
Integer, Long_Integer, etc.

It might even be nice to have a predefined attribute for built-in types
to return a hash value, e.g.

   package File_Cache_Types is
      new Ada.Containers.Maps.Hashed.Strings.Unbounded
        (Element_Type => Context_Access,
         Hash         => String'Hash_Case_Insensitive,
         Is_Equal_Key => String'Equal_Case_Insensitive);

or something like that.  Or to have an extensible mechanism a la T'Read
and T'Write.


The specification of the root of the set containers subsystem is as
follows:

package Ada.Containers.Sets is
   pragma Pure;
end Ada.Containers.Sets;


The specification of the root of the hashed set containers subsystem is
as follows:

package Ada.Containers.Sets.Hashed is
   pragma Pure;
end Ada.Containers.Sets.Hashed;


The specification of the unbounded hashed set container is as follows:

generic

   type Element_Type is private;

   with function Hash
     (Item : Element_Type) return Integer'Base is <>;

   with function Is_Equal_Key
     (Left, Right : Element_Type) return Boolean is "=";

   with function "="
     (Left, Right : Element_Type) return Boolean is <>;

package Ada.Containers.Sets.Hashed.Unbounded is

   pragma Preelaborate;

   type Container_Type is private;

   type Iterator_Type is private;

   Null_Iterator : constant Iterator_Type;

   procedure Assign
     (Target : in out Container_Type;
      Source : in     Container_Type);

   function "=" (Left, Right : Container_Type) return Boolean;

   function Length (Container : Container_Type) return Natural;

   function Is_Empty (Container : Container_Type) return Boolean;

   procedure Clear (Container : in out Container_Type);

   procedure Swap (Left, Right : in out Container_Type);

   procedure Insert
     (Container : in out Container_Type;
      New_Item  : in     Element_Type;
      Iterator  :    out Iterator_Type;
      Success   :    out Boolean);

   procedure Insert
     (Container : in out Container_Type;
      New_Item  : in     Element_Type);

   procedure Insert_Sans_Resize
     (Container : in out Container_Type;
      New_Item  : in     Element_Type;
      Iterator  :    out Iterator_Type;
      Success   :    out Boolean);

   procedure Insert_Sans_Resize
     (Container : in out Container_Type;
      New_Item  : in     Element_Type);

   procedure Delete
     (Container : in out Container_Type;
      Item      : in     Element_Type);

   procedure Delete
     (Container : in out Container_Type;
      Iterator  : in out Iterator_Type);

   function Is_In
     (Item      : Element_Type;
      Container : Container_Type) return Boolean;

   function Find
     (Container : Container_Type;
      Item      : Element_Type) return Iterator_Type;

   generic
      with procedure Process
        (Element : in Element_Type) is <>;
   procedure Generic_Equal_Range
     (Container : in Container_Type;
      Item      : in Element_Type);

   function Size (Container : Container_Type) return Natural;

   procedure Resize
     (Container : in out Container_Type;
      Size      : in     Natural);

   function First
     (Container : Container_Type;
      Index     : Positive) return Iterator_Type;

   function Index
     (Container : Container_Type;
      Item      : Element_Type) return Positive;

   function Back (Container : Container_Type) return Iterator_Type;

   function Succ (Iterator : Iterator_Type) return Iterator_Type;

   procedure Increment (Iterator : in out Iterator_Type);

   function Is_Equal_Key
     (Left, Right : Iterator_Type) return Boolean;

   function Is_Equal_Key
     (Left  : Iterator_Type;
      Right : Element_Type) return Boolean;

   function Is_Equal_Key
     (Left  : Element_Type;
      Right : Iterator_Type) return Boolean;

   function Element (Iterator : Iterator_Type) return Element_Type;

   generic
      type Element_Access is access constant Element_Type;
   function Generic_Element
     (Iterator : Iterator_Type) return Element_Access;

   generic
      with procedure Process
        (Element : in Element_Type) is <>;
   procedure Generic_Process_Element
     (Iterator : in Iterator_Type);

   generic
      with procedure Process
        (Iterator : in Iterator_Type) is <>;
   procedure Generic_Iteration
     (Container : in Container_Type);

   generic
      with procedure Process
        (Element : in Element_Type) is <>;
   procedure Generic_Process_Elements
     (Container : in Container_Type);


   generic

      type Key_Type (<>) is limited private;

      with function Hash (Key : Key_Type) return Integer'Base is <>;

      with function Is_Equal_Key
        (Element : Element_Type;
         Key     : Key_Type) return Boolean is <>;

   package Generic_Keys is

      function Is_In
        (Key       : Key_Type;
         Container : Container_Type) return Boolean;

      function Find
        (Container : Container_Type;
         Key       : Key_Type) return Iterator_Type;

      function Element
        (Container : Container_Type;
         Key       : Key_Type) return Element_Type;

      generic
         with procedure Process
           (Element : in Element_Type) is <>;
      procedure Generic_Equal_Range
        (Container : in Container_Type;
         Key       : in Key_Type);

      function Index
        (Container : Container_Type;
         Key       : Key_Type) return Positive;

      procedure Delete
        (Container : in out Container_Type;
         Key       : in     Key_Type);

      function Is_Equal_Key
        (Left  : Iterator_Type;
         Right : Key_Type) return Boolean;

      function Is_Equal_Key
        (Left  : Key_Type;
         Right : Iterator_Type) return Boolean;

      generic

         with procedure Set_Key
           (Element : in out Element_Type;
            Key     : in     Key_Type) is <>;

      package Generic_Insertion is

         procedure Insert
           (Container : in out Container_Type;
            Key       : in     Key_Type;
            Iterator  :    out Iterator_Type;
            Success   :    out Boolean);

         procedure Insert_Sans_Resize
           (Container : in out Container_Type;
            Key       : in     Key_Type;
            Iterator  :    out Iterator_Type;
            Success   :    out Boolean);

      end Generic_Insertion;

   end Generic_Keys;

private

   -- not specified by this standard

end Ada.Containers.Sets.Hashed.Unbounded;


In addition, the hashed set container package has one generic child
procedure:

generic
   type Element_Access is access all Element_Type;
function Ada.Containers.Sets.Hashed.Unbounded.Unchecked_Modification
  (Iterator : Iterator_Type) return Element_Access;

pragma Preelaborate
  (Ada.Containers.Sets.Hashed.Unbounded.Unchecked_Modification);


Container_Type is a pass-by-reference type.

In the absence of any explicit initialization, objects of type
Iterator_Type are default-initialized to the value Null_Iterator.


procedure Assign
  (Target : in out Container_Type;
   Source : in     Container_Type);

Assigns the elements in Source to Target.  If Source is an alias of
Target, the operation does nothing.  Any exceptions raised during
internal allocation or deallocation are propagated.


function "=" (Left, Right : Container_Type) return Boolean;

The equality operator for hashed sets.  If Left and Right refer to the
same container, then the function returns immediately with the value
True.  If Left and Right have different lengths, then the function
returns immediately with the value False.  Otherwise, elements are
compared in canonical order using the equality operator supplied as the
generic actual.  Any exception raised during evaluation of element
equality is propagated, immediately terminating the iteration.


function Length (Container : Container_Type) return Natural;

Returns the number of elements in the container.


function Is_Empty (Container : Container_Type) return Boolean;

Equivalent to Length (Container) = 0.


procedure Clear (Container : in out Container_Type);

Deletes all the elements in Container.  The size of the container is not
affected.  Any exceptions raised during deallocation are propagated.


procedure Swap (Left, Right : in out Container_Type);

Exchanges the internal nodes and buckets array of Left and Right.  The
elements that were in the Left set are now in the Right set, and vice
versa.  The time complexity of Swap is O(1).


procedure Insert
  (Container : in out Container_Type;
   New_Item  : in     Element_Type;
   Iterator  :    out Iterator_Type;
   Success   :    out Boolean);

If Length (Container) equals Size (Container), then Container is resized
to some larger size as per Resize.  The index in the buckets array is
then computed from the hash value of New_Item and the size of Container.
The actual subprogram bound to Is_Equal_Key is then used to compare
New_Item to each node in the list of the buckets array at that index.
If the predicate returns True, then Success returns False and Iterator
designates the (first and only) matching node.  Otherwise, a new node is
allocated whose element is initialized to New_Item and inserted at the
head of the list.  Success returns True and Iterator designates the
newly-inserted node.  Any exceptions raised during allocation are
propagated, and Container is not modified (except for any possible
resizing).  The time complexity of this operation is O(1) average case.


procedure Insert
  (Container : in out Container_Type;
   New_Item  : in     Element_Type);

Equivalent to Insert (Container, New_Item, Iterator, Success), with the
difference that the iterator and success parameters are omitted.


procedure Insert_Sans_Resize
  (Container : in out Container_Type;
   New_Item  : in     Element_Type;
   Iterator  :    out Iterator_Type;
   Success   :    out Boolean);

Inserts the New_Item into Container as per Insert (Container, New_Item,
Iterator, Success), with the difference that there is no resize prior to
the insertion proper.  If Size (Container) equals 0, the operation fails
and Constraint_Error is propagated.


procedure Insert_Sans_Resize
  (Container : in out Container_Type;
   New_Item  : in     Element_Type);

Equivalent to Insert_Sans_Resize (Container, New_Item, Iterator,
Success), with the difference that the iterator and success parameters
are omitted.


procedure Delete
  (Container : in out Container_Type;
   Item      : in     Element_Type);

Deletes the node whose element matches Item.  If Length (Container)
equals 0, then Delete does nothing.  Otherwise the buckets array index
is computed from the hash value of Item and the size of Container.  Any
exception raised during invokation of Hash is propagated.  The nodes of
the list at that index in the buckets array are compared to Item using
Is_Equal_Key.  The node that matches Item is removed from the set and
then deallocated.  Any exceptions raised by Is_Equal_Key or during the
deallocation are propagated.


procedure Delete
  (Container : in out Container_Type;
   Iterator  : in out Iterator_Type);

If Iterator equals Null_Iterator, the operation has no effect.  If
Iterator does not designate a node in Container, program execution is
erroneous.  Otherwise the buckets index is computed from the hash value
of the element on the node designated by Iterator and the size of
Container.  Any exception raised during invokation of Hash is
propagated.  The node is removed from the set and then deallocated.  Any
exceptions raised by during the deallocation are propagated.  The
Iterator returned designates the node that followed Iterator prior to
the deletion; if this is the end of the list then Null_Iterator is
returned.


function Is_In
  (Item      : Element_Type;
   Container : Container_Type) return Boolean;

Equivalent to Find (Container, Item) /= Null_Iterator.


function Find
  (Container : Container_Type;
   Item      : Element_Type) return Iterator_Type;

If Length (Container) equals 0, then the function returns immediately
with the value Null_Iterator.  Otherwise the buckets index is computed
from the hash value of Item and the size of Container.  If the list at
that index is empty, then Null_Iterator is returned.  Otherwise the list
is traversed and the element of each node on the list is compared to
Item using Is_Equal_Key.  If Is_Equal_Key returns True, it returns an
iterator designating the (first) node with the matching key.  Otherwise
if no node in the list matches Key, it returns the value Null_Iterator.


generic
   with procedure Process
     (Element : in Element_Type) is <>;
procedure Generic_Equal_Range
  (Container : in Container_Type;
   Item      : in Element_Type);

Attempts to find a matching element as per Find (Container, Item).  If a
matching element is found, it invokes the generic actual bound to
Process with the matching element as the parameter.


function Size (Container : Container_Type) return Natural;

Returns the length of the internal buckets array.


procedure Resize
  (Container : in out Container_Type;
   Size      : in     Natural);

If Size (Container) is greater than or equal to Length, the operation
has no effect.  Otherwise, a new buckets array is allocated whose length
is at least the value specified.  If allocation of the new buckets array
fails, the exception is propagated and Container is not modified.  Nodes
already in the set are then rehashed onto the new buckets array.  If
Hash fails, the exception is propagated, and the nodes that were moved
onto the new buckets array are lost.


function First
  (Container : Container_Type;
   Index     : Positive) return Iterator_Type;

If Index is outside the range 1 .. Size (Container), then
Constraint_Error is propagated.  Otherwise, it returns an iterator that
designates the head of the list of the buckets array at Index.  If there
is no list at that Index, then it returns Null_Iterator.


function Index
  (Container : Container_Type;
   Item      : Element_Type) return Positive;

If Size (Container) equals 0, the operation fails and Constraint_Error
is propagated.  Otherwise, returns the value of the expression Hash
(Item) mod Size (Container).


function Back (Container : Container_Type) return Iterator_Type;

Returns the value Null_Iterator.


function Succ (Iterator : Iterator_Type) return Iterator_Type;

If Iterator equals Null_Iterator, the operation fails and
Constraint_Error is propagated.  Otherwise, it returns an iterator that
designates the successor node in the same list.  If Iterator designates
the last node of the list, then it returns Null_Iterator.


procedure Increment (Iterator : in out Iterator_Type);

Equivalent to Iterator := Succ (Iterator).


function Is_Equal_Key
  (Left, Right : Iterator_Type) return Boolean;

Equivalent to Is_Equal_Key (Element (Left), Element (Right)).


function Is_Equal_Key
  (Left  : Iterator_Type;
   Right : Element_Type) return Boolean;

Equivalent to Is_Equal_Key (Element (Left), Right).


function Is_Equal_Key
  (Left  : Element_Type;
   Right : Iterator_Type) return Boolean;

Equivalent to Is_Equal_Key (Right, Left).


function Element (Iterator : Iterator_Type) return Element_Type;

If Iterator equals Null_Iterator, the operation fails and
Constraint_Error is propagated.  Otherwise, it returns the element
component of the designated node.


generic
   type Element_Access is access constant Element_Type;
function Generic_Element
  (Iterator : Iterator_Type) return Element_Access;

If Iterator equals Null_Iterator, the operation fails and
Constraint_Error is propagated.  Otherwise, it returns an access value
that designates a constant view of the element component of the node
designated by Iterator.


generic
   with procedure Process
     (Element : in Element_Type) is <>;
procedure Generic_Process_Element
  (Iterator : in Iterator_Type);

Invokes the actual subprogram bound to Process with the element of the
node designated by Iterator.  If Iterator equals Null_Iterator, the
operation fails and Constraint_Error is propagated.


generic
   with procedure Process
     (Iterator : in Iterator_Type) is <>;
procedure Generic_Iteration
  (Container : in Container_Type);

Generic_Iteration calls the actual subprogram bound to Process once for
each node in the Container.  Any exceptions raised during Process are
propagated, immediately terminating the iteration.


generic
   with procedure Process
     (Element : in Element_Type) is <>;
procedure Generic_Process_Elements
  (Container : in Container_Type);

Calls the actual subprogram bound to Process once for each element in
Container.  Any exception raised during Process is propagated,
immediately terminating the iteration.


function Is_In
  (Key       : Key_Type;
   Container : Container_Type) return Boolean;

Equivalent to Find (Container, Key) /= Null_Iterator.


function Find
  (Container : Container_Type;
   Key       : Key_Type) return Iterator_Type;

If Length (Container) equals 0, then the function returns immediately
with the value Null_Iterator.  Otherwise the buckets index is computed
from the hash value of Key and the size of Container.  If the list at
that index is empty, then it returns Null_Iterator.  Otherwise the list
is traversed and the element of each node on the list is compared to Key
using Generic_Keys.Is_Equal_Key.  If Is_Equal_Key returns True, it
returns an iterator designating the (first) node with the matching
element.  Otherwise if no node in the list matches Key, it returns the
value Null_Iterator.


function Element
  (Container : Container_Type;
   Key       : Key_Type) return Element_Type;

Equivalent to Element (Find (Container, Key)).


generic
   with procedure Process
     (Element : Element_Type) is <>;
procedure Generic_Equal_Range
  (Container : in Container_Type;
   Key       : in Key_Type);

Attempts to find a matching element as per Find (Container, Key).  If a
matching element is found, it invokes the generic actual bound to
Process with the matching element as the parameter.


function Index
  (Container : Container_Type;
   Key       : Key_Type) return Positive;

If Size (Container) equals 0, the operation fails and Constraint_Error
is propagated.  Otherwise, returns the value of the expression Hash
(Key) mod Size (Container).


procedure Delete
  (Container : in out Container_Type;
   Key       : in     Key_Type);

Deletes the node that matches Key.  If Length (Container) equals 0, then
Delete does nothing.  Otherwise it computes the index of the buckets
array from the hash value of Key and the size of Container.  Any
exception raised during invokation of Hash is propagated.  The nodes of
the list at that index in the buckets array are compared to Key using
Generic_Keys.Is_Equal_Key.  The node that matches Key is removed from
the set and then deallocated.  Any exceptions raised by Is_Equal_Key or
during the deallocation are propagated.


function Is_Equal_Key
  (Left  : Iterator_Type;
   Right : Key_Type) return Boolean;

Equivalent to Is_Equal_Key (Element (Left), Right).


function Is_Equal_Key
  (Left  : Key_Type;
   Right : Iterator_Type) return Boolean;

Equivalent to Is_Equal_Key (Right, Left).


procedure Insert
  (Container : in out Container_Type;
   Key       : in     Key_Type;
   Iterator  :    out Iterator_Type;
   Success   :    out Boolean);

If Length (Container) equals Size (Container), then Container is resized
to some greater size as per Resize.  The index in the buckets array is
then computed from the hash value of Key and the size of Container.  The
actual subprogram bound to Generic_Keys.Is_Equal_Key is used to compare
Key to the element of each node in the list at that index of the buckets
array.  If an element compares True, then Success returns False and
Iterator designates the (first) matching node.  Otherwise a new node is
allocated and then Generic_Insertion.Set_Key is immediately invoked with
the (uninitialized) element of the new node and Key as the parameters.
Any exception raised during the allocation is propagated and Container
is not modified.  If allocation succeeds but initialization fails, then
the node is deallocated and the exception is propagated, and Container
is not modified.  Otherwise, the new node is inserted at the head of the
list, Success returns True, and Iterator designates the newly-inserted
node.


procedure Insert_Sans_Resize
  (Container : in out Container_Type;
   Key       : in     Key_Type;
   Iterator  :    out Iterator_Type;
   Success   :    out Boolean);

Inserts a new node per Insert (Container, Key, Iterator, Success), with
the difference that Container is not resized.  If Size (Container)
equals 0, the operation fails and Constraint_Error is propagated.


generic
   type Element_Access is access all Element_Type;
function Ada.Containers.Sets.Hashed.Unbounded.Unchecked_Modification
  (Iterator : Iterator_Type) return Element_Access;

If Iterator equals Null_Iterator, the operation fails and
Constraint_Error is propagated.  Otherwise, it returns an access value
that designates a variable view of the element on the node designated by
Iterator.


The specification of the root of the sorted set containers subsystem is
as follows:

package Ada.Containers.Sets.Sorted is
   pragma Pure;
end Ada.Containers.Sets.Sorted;

The specification of the unbounded sorted set container is as follows:

generic

   type Element_Type is private;

   with function "<"
     (Left, Right : Element_Type) return Boolean is <>;

   with function "="
     (Left, Right : Element_Type) return Boolean is <>;

package Ada.Containers.Sets.Sorted.Unbounded is

   pragma Preelaborate;

   type Container_Type is private;

   type Iterator_Type is private;

   Null_Iterator : constant Iterator_Type;

   procedure Assign
     (Target : in out Container_Type;
      Source : in     Container_Type);

   function "=" (Left, Right : Container_Type) return Boolean;

   function "<" (Left, Right : Container_Type) return Boolean;

   function "<=" (Left, Right : Container_Type) return Boolean;

   function ">" (Left, Right : Container_Type) return Boolean;

   function ">=" (Left, Right : Container_Type) return Boolean;

   function Length (Container : Container_Type) return Natural;

   function Is_Empty (Container : Container_Type) return Boolean;

   procedure Clear (Container : in out Container_Type);

   procedure Swap (Left, Right : in out Container_Type);

   procedure Insert
     (Container : in out Container_Type;
      New_Item  : in     Element_Type;
      Iterator  :    out Iterator_Type;
      Success   :    out Boolean);

   procedure Insert
     (Container : in out Container_Type;
      New_Item  : in     Element_Type);

   procedure Insert
     (Container : in out Container_Type;
      Position  : in     Iterator_Type;
      New_Item  : in     Element_Type;
      Iterator  :    out Iterator_Type;
      Success   :    out Boolean);

   procedure Delete
     (Container : in out Container_Type;
      Item      : in     Element_Type);

   procedure Delete
     (Container : in out Container_Type;
      Iterator  : in out Iterator_Type);

   procedure Delete_Sans_Increment
     (Container : in out Container_Type;
      Iterator  : in out Iterator_Type);

   procedure Delete_First (Container : in out Container_Type);

   procedure Delete_Last (Container : in out Container_Type);

   function Is_In
     (Item      : Element_Type;
      Container : Container_Type) return Boolean;

   function Find
     (Container : Container_Type;
      Item      : Element_Type) return Iterator_Type;

   function Lower_Bound
     (Container : Container_Type;
      Item      : Element_Type) return Iterator_Type;

   function Upper_Bound
     (Container : Container_Type;
      Item      : Element_Type) return Iterator_Type;

   generic
      with procedure Process
        (Element : in Element_Type) is <>;
   procedure Generic_Equal_Range
     (Container : in Container_Type;
      Item      : in Element_Type);

   function First
     (Container : Container_Type) return Iterator_Type;

   function First_Element
     (Container : Container_Type) return Element_Type;

   function Last
     (Container : Container_Type) return Iterator_Type;

   function Last_Element
     (Container : Container_Type) return Element_Type;

   function Back
     (Container : Container_Type) return Iterator_Type;

   function Succ
     (Iterator : Iterator_Type) return Iterator_Type;

   function Pred
     (Iterator : Iterator_Type) return Iterator_Type;

   procedure Increment
     (Iterator : in out Iterator_Type);

   procedure Decrement
     (Iterator : in out Iterator_Type);

   function "<" (Left, Right : Iterator_Type) return Boolean;

   function "<"
     (Left  : Iterator_Type;
      Right : Element_Type) return Boolean;

   function "<"
     (Left  : Element_Type;
      Right : Iterator_Type) return Boolean;

   function Element (Iterator : Iterator_Type) return Element_Type;

   generic
      type Element_Access is access constant Element_Type;
   function Generic_Element
     (Iterator : Iterator_Type) return Element_Access;

   generic
      with procedure Process
        (Element : in Element_Type) is <>;
   procedure Generic_Process_Element
     (Iterator : in Iterator_Type);

   generic
      with procedure Process
        (Iterator : in Iterator_Type) is <>;
   procedure Generic_Iteration
     (Container : in Container_Type);

   generic
      with procedure Process
        (Iterator : in Iterator_Type) is <>;
   procedure Generic_Reverse_Iteration
     (Container : in Container_Type);

   generic
      with procedure Process
        (Element : in Element_Type) is <>;
   procedure Generic_Process_Elements
     (Container : in Container_Type);

   generic
      with procedure Process
        (Element : in Element_Type) is <>;
   procedure Generic_Reverse_Process_Elements
     (Container : in Container_Type);

   generic

      type Key_Type (<>) is limited private;

      with function "<"
        (Left  : Element_Type;
         Right : Key_Type) return Boolean is <>;

      with function "<"
        (Left  : Key_Type;
         Right : Element_Type) return Boolean is <>;

   package Generic_Keys is

      function Is_In
        (Key       : Key_Type;
         Container : Container_Type) return Boolean;

      function Find
        (Container : Container_Type;
         Key       : Key_Type) return Iterator_Type;

      function Element
        (Container : Container_Type;
         Key       : Key_Type) return Element_Type;

      function Lower_Bound
        (Container : Container_Type;
         Key       : Key_Type) return Iterator_Type;

      function Upper_Bound
        (Container : Container_Type;
         Key       : Key_Type) return Iterator_Type;

      generic
         with procedure Process
           (Element : in Element_Type) is <>;
      procedure Generic_Equal_Range
        (Container : in Container_Type;
         Key       : in Key_Type);

      procedure Delete
        (Container : in out Container_Type;
         Key       : in     Key_Type);

      function "<"
        (Left  : Iterator_Type;
         Right : Key_Type) return Boolean;

      function "<"
        (Left  : Key_Type;
         Right : Iterator_Type) return Boolean;

      generic

         with procedure Set_Key
           (Element : in out Element_Type;
            Key     : in     Key_Type) is <>;

      package Generic_Insertion is

         procedure Insert
           (Container : in out Container_Type;
            Key       : in     Key_Type;
            Iterator  :    out Iterator_Type;
            Success   :    out Boolean);

         procedure Insert
           (Container : in out Container_Type;
            Position  : in     Iterator_Type;
            Key       : in     Key_Type;
            Iterator  :    out Iterator_Type;
            Success   :    out Boolean);

      end Generic_Insertion;


   end Generic_Keys;


private

   -- not specified by this standard

end Ada.Containers.Sets.Sorted.Unbounded;


In addition, the sorted set container package has one generic child
procedure:

generic
   type Element_Access is access all Element_Type;
function Ada.Containers.Sets.Sorted.Unbounded.Unchecked_Modification
  (Iterator : Iterator_Type) return Element_Access;

pragma Preelaborate
  (Ada.Containers.Sets.Sorted.Unbounded.Unchecked_Modification);


Container_Type is a pass-by-reference type.

In the absence of any explicit initialization, objects of type
Iterator_Type are default-initialized to the value Null_Iterator.

AARM NOTE
When a sorted set container object elaborates, a sentinel node belonging
to the container is allocated automatically.  Its element is
uninitialized, except for any controlled initialization or
default-initialized components.  It is not included in the count of
elements in the container, and it cannot be deleted.


procedure Assign
  (Target : in out Container_Type;
   Source : in     Container_Type);

Assigns the elements in Source to Target.  If Source is an alias of
Target, the operation does nothing.  Any exceptions raised during
internal allocation or deallocation are propagated.

AARM NOTE
The sentinel is not affected by Assign, and its element is never copied.
The sentinel Target had before the Assign is the same sentinel has after
the Assign.


function "=" (Left, Right : Container_Type) return Boolean;

The equality operator for sorted sets.  If Left and Right refer to the
same container, then the function returns immediately with the value
True.  If Left and Right have different lengths, then the function
returns immediately with the value False.  Otherwise, elements are
compared sequentially using the equality operator supplied as the
generic actual.  Any exception raised during evaluation of element
equality is propagated, immediately terminating the iteration.


function "<" (Left, Right : Container_Type) return Boolean;

Performs a lexicographical comparison of the elements in Left and Right.
If Left is an alias of Right, then the operation returns immediately
with the value False.  Otherwise, elements are compared sequentially
using the generic formal less-than operator for elements.  Any exception
raised during evaluation of less-than is propagated, immediately
terminating the iteration.


function "<=" (Left, Right : Container_Type) return Boolean;

Equivalent to not (Left > Right).


function ">" (Left, Right : Container_Type) return Boolean;

Equivalent to Right < Left.


function ">=" (Left, Right : Container_Type) return Boolean;

Equivalent to not (Left < Right).


function Length (Container : Container_Type) return Natural;

Returns the number of elements in the container.


function Is_Empty (Container : Container_Type) return Boolean;

Equivalent to Length (Container) = 0.


procedure Clear (Container : in out Container_Type);

Deletes all the elements in Container.  Any exceptions raised during
deallocation are propagated.


procedure Swap (Left, Right : in out Container_Type);

Exchanges the internal nodes of Left and Right.  Elements that were in
the Left set are now in the Right set, and vice versa.  The time
complexity of Swap is O(1).


procedure Insert
  (Container : in out Container_Type;
   New_Item  : in     Element_Type;
   Iterator  :    out Iterator_Type;
   Success   :    out Boolean);

Inserts the New_Item into Container.  The New_Item is compared to
elements already in the set using the subprogram bound to the generic
formal "<" operator.  If the element is already in the set, Success is
False and Iterator designates the node containing the "equivalent"
element.  Otherwise, a new node is allocated and initialized to the
value of New_Item, Success returns True and Iterator designates the
newly-allocated node.  Any exceptions raised during evaluation of the
relational operator or during allocation are propagated, and Container
is not modified.

The equality operator "=" for Element_Type is only used to compute the
value of container equality; it is not used to compute the result of any
other operation.  During insertion elements are compared not for
equality but rather for "equivalence," which for elements E1 and E2 is
defined as "not (E1 < E2) and not (E2 < E1)".


procedure Insert
  (Container : in out Container_Type;
   New_Item  : in     Element_Type);

Equivalent to Insert (Container, New_Item, Iterator, Success), with the
difference that the iterator and success parameters are omitted.


procedure Insert
  (Container : in out Container_Type;
   Position  : in     Iterator_Type;
   New_Item  : in     Element_Type;
   Iterator  :    out Iterator_Type;
   Success   :    out Boolean);

Equivalent to Insert (Container, New_Item, Iterator, Success), with the
addition of a Position parameter, which specifies a hint about where to
insert the new element.  If Position equals Null_Iterator, the operation
is equivalent to the Position-less version of Insert.  If Position does
not designate a node that belongs to Container, program execution is
erroneous.  For a hint to be useful, it should designate a node
containing an element that would be adjacent to New_Item, were it in the
container.


procedure Delete
  (Container : in out Container_Type;
   Item      : in     Element_Type);

Deletes the node that matches Item.  Any exceptions raised by "<" or
during deallocation are propagated.


procedure Delete
  (Container : in out Container_Type;
   Iterator  : in out Iterator_Type);

If Iterator equals Null_Iterator or Back (Container), the operation does
nothing.  If Iterator does not designate a node that belongs to
Container, program execution is erroneous.  Otherwise, the node
designated by Iterator is removed from the tree.  Any exception raised
during deallocation is propagated.  The iterator value returned
designates the successor of the node deleted.


procedure Delete_Sans_Increment
  (Container : in out Container_Type;
   Iterator  : in out Iterator_Type);

Equivalent to Delete (Container, Iterator), with the difference that the
iterator value returned equals Null_Iterator.


procedure Delete_First (Container : in out Container_Type);

If Container is empty, the operation does nothing.  Otherwise the node
designated by First (Container) is deleted.


procedure Delete_Last (Container : in out Container_Type);

If Container is empty, the operation does nothing.  Otherwise the node
designated by Last (Container) is deleted.


function Is_In
  (Item      : Element_Type;
   Container : Container_Type) return Boolean;

Equivalent to Find (Container, Item) /= Null_Iterator.


function Find
  (Container : Container_Type;
   Item      : Element_Type) return Iterator_Type;

Finds the node whose element is equivalent to Item, and returns an
iterator that designates that node.  If there is no equivalent element,
the iterator value Null_Iterator is returned.  Any exceptions raised
during evaluation of "<" are propagated.


function Lower_Bound
  (Container : Container_Type;
   Item      : Element_Type) return Iterator_Type;

Returns an iterator that designates the node containing the smallest
element not less than Item.  If there is no element already in the set
that is equivalent to or greater than to Item, then it returns Back
(Container).


function Upper_Bound
  (Container : Container_Type;
   Item      : Element_Type) return Iterator_Type;

Returns an iterator that designates the node containing the smallest
element greater than Item.  If there is no element already in the set
that is greater than Item, then it returns Back (Container).


generic
   with procedure Process
     (Element : in Element_Type) is <>;
procedure Generic_Equal_Range
  (Container : in Container_Type;
   Item      : in Element_Type);

Searches for the node containing the element that is equivalent to Item
as per Find.  If the search is successful, it invokes the actual
subprogram bound to Process with the equivalent element as the
parameter.


function First
  (Container : Container_Type) return Iterator_Type;

Returns an iterator that designates the node containing the smallest
element.  If Container is empty, the iterator value Back (Container) is
returned.  The time complexity of this operation is O(1).


function First_Element
  (Container : Container_Type) return Element_Type;

If Container is empty, the operation fails and Constraint_Error is
propagated.  Otherwise, it returns the element on the node designated by
First (Container).


function Last
  (Container : Container_Type) return Iterator_Type;

Returns an iterator that designates the node containing the greatest
element.  If Container is empty, the iterator value Back (Container) is
returned.  The time complexity of this operation is O(1).


function Last_Element
  (Container : Container_Type) return Element_Type;

If Container is empty, the operation fails and Constraint_Error is
propagated.  Otherwise, it returns the element on the node designated by
Last (Container).


function Back
  (Container : Container_Type) return Iterator_Type;

Returns an iterator that designates the sentinel node.


function Succ
  (Iterator : Iterator_Type) return Iterator_Type;

Returns the successor of the node designated by Iterator.  If Iterator
equals Null_Iterator, the operation fails and Constraint_Error is
propagated.


function Pred
  (Iterator : Iterator_Type) return Iterator_Type;

Returns the predecessor of the node designated by Iterator.  If Iterator
equals Null_Iterator, the operation fails and Constraint_Error is
propagated.


procedure Increment
  (Iterator : in out Iterator_Type);

Equivalent to Iterator := Succ (Iterator).


procedure Decrement
  (Iterator : in out Iterator_Type);

Equivalent to Iterator := Pred (Iterator).


function "<" (Left, Right : Iterator_Type) return Boolean;

Equivalent to Element (Left) < Element (Right).


function "<"
  (Left  : Iterator_Type;
   Right : Element_Type) return Boolean;

Equivalent to Element (Left) < Right.


function "<"
  (Left  : Element_Type;
   Right : Iterator_Type) return Boolean;

Equivalent to Left < Element (Right).


function Element
  (Iterator : Iterator_Type) return Element_Type;

If Iterator equals Null_Iterator, the operation fails and
Constraint_Error is propagated.  Otherwise, it returns the element
component of the designated node.


generic
   type Element_Access is
      access constant Element_Type;
function Generic_Element
  (Iterator : Iterator_Type) return Element_Access;

If Iterator equals Null_Iterator, the operation fails and
Constraint_Error is propagated.  Otherwise, it returns an access value
that designates a constant view of the the element on the node
designated by Iterator.


generic
   with procedure Process
     (Element : in Element_Type) is <>;
procedure Generic_Process_Element
  (Iterator : in Iterator_Type);

Invokes the actual subprogram bound to Process with the element of the
node designated by Iterator.  If Iterator equals Null_Iterator, the
operation fails and Constraint_Error is propagated.


generic
   with procedure Process
     (Iterator : in Iterator_Type) is <>;
procedure Generic_Iteration
  (Container : in Container_Type);

Invokes the actual subprogram bound to Process with an iterator that
designates each node in Container.


generic
   with procedure Process
     (Iterator : in Iterator_Type) is <>;
procedure Generic_Reverse_Iteration
  (Container : in Container_Type);

Iterates over the nodes in Container as per Generic_Iteration, with the
difference that the nodes are traversed in reverse order.


generic
   with procedure Process
     (Element : in Element_Type) is <>;
procedure Generic_Process_Elements
  (Container : in Container_Type);

Invokes the actual subprogram bound to Process for each element in
Container.


generic
   with procedure Process
     (Element : in Element_Type) is <>;
procedure Generic_Reverse_Process_Elements
  (Container : in Container_Type);

Iterates over the elements in Container as per Generic_Process_Elements,
with the difference that the elements are traversed in reverse order.


function Is_In
  (Key       : Key_Type;
   Container : Container_Type) return Boolean;

Equivalent to Find (Container, Key) /= Null_Iterator.


function Find
  (Container : Container_Type;
   Key       : Key_Type) return Iterator_Type;

Finds the node whose element is equivalent to Key, and returns an
iterator that designates that node.  If there is no equivalent element,
it returns value Null_Iterator.  Any exceptions raised during evaluation
of "<" are propagated.


function Element
  (Container : Container_Type;
   Key       : Key_Type) return Element_Type;

Equivalent to Element (Find (Container, Key)).


function Lower_Bound
  (Container : Container_Type;
   Key       : Key_Type) return Iterator_Type;

Returns an iterator that designates the node containing the smallest
element not less than Key.  If there is no element already in the set
that is equivalent to or greater than Key, it returns Back (Container).


function Upper_Bound
  (Container : Container_Type;
   Key       : Key_Type) return Iterator_Type;

Returns an iterator that designates the node containing the smallest
element greater than Key.  If there is no element already in the set
that is greater than Key, it returns Back (Container).


generic
   with procedure Process
     (Element : in Element_Type) is <>;
procedure Generic_Equal_Range
  (Container : in Container_Type;
   Key       : in Key_Type);

Searches for the node containing the element that is equivalent to Key
as per Find.  If the search is successful, it invokes the actual
subprogram bound to Process with the equivalent element as the
parameter.


procedure Delete
  (Container : in out Container_Type;
   Key       : in     Key_Type);

Deletes the node containing the element equivalent to Key.  Any
exceptions raised by "<" or during deallocation are propagated.


function "<"
  (Left  : Iterator_Type;
   Right : Key_Type) return Boolean;

Equivalent to Element (Left) < Right.


function "<"
  (Left  : Key_Type;
   Right : Iterator_Type) return Boolean;

Equivalent to Left < Element (Right).


procedure Insert
  (Container : in out Container_Type;
   Key       : in     Key_Type;
   Iterator  :    out Iterator_Type;
   Success   :    out Boolean);

Key is compared to elements already in the set using the subprogram
bound to the Generic_Insertion generic formal "<" operators.  If an
element that is equivalent to Key is already in the set, Success is
False and Iterator designates the node containing the equivalent
element.  Otherwise, a new node is allocated with an uninitialized
element, and then Generic_Insertion.Set_Key is immediately invoked with
the element and Key as the parameters.  If Set_Key raises an exception,
the node is deallocated and the exception is propagated without
modifying Container.  Otherwise, the node is inserted into the tree,
Success returns True and Iterator designates the newly-allocated,
key-initialized node.  Any exceptions raised during evaluation of the
relational operator or during allocation are propagated, and Container
is not modified.


procedure Insert
  (Container : in out Container_Type;
   Position  : in     Iterator_Type;
   Key       : in     Key_Type;
   Iterator  :    out Iterator_Type;
   Success   :    out Boolean);

Equivalent to Insert (Container, Key, Iterator, Success), with the
addition of a Position parameter, which specifies a hint about where to
begin searching for Key.  If Position equals Null_Iterator then this
operation is equivalent to the Position-less version of Insert.  If
Position does not designate a node that belongs to Container then
program execution is erroneous.


generic
   type Element_Access is access all Element_Type;
function Ada.Containers.Sets.Sorted.Unbounded.Unchecked_Modification
  (Iterator : Iterator_Type) return Element_Access;

If Iterator equals Null_Iterator, the operation fails and
Constraint_Error is propagated.  Otherwise, it returns an access value
that designates a variable view of the element on the node designated by
Iterator.

!example

Mario Alves suggested this example to me.  Suppose we have a set and
want to copy the elements from the set into an array.  Here's one way to
do it:

   procedure Op (S : in Integer_Sets.Container_Type) is

      A : Integer_Array (1 .. Length (S));
      I : Integer_Sets.Iterator_Type := First (S);
      J : Positive := A'First;
   begin
      while I /= Back (S) loop
         A (J) := Element (I);
         Increment (I);
         J := J + 1;
      end loop;
      ...
   end Op;

Here we're incrementing both the array index and the set iterator
manually.  However, when you're iterating over two containers
simultaneously, you can often let one or the other drive the iteration,
so that only one position needs to be incremented manually.

We can change the example use a built-in for loop:

   procedure Op (S : in Integer_Sets.Container_Type) is

      A : Integer_Array (1 .. Length (S));
      I : Integer_Sets.Iterator_Type := First (S);
   begin
      for J in A'Range loop
         A (J) := Element (I);
         Increment (I);
      end loop;
      ...
   end Op;

This lets the array drive the iteration.  I have said elsewhere that you
should use a passive iterator in preference to an explicit loop.  The
algorithm above might run faster if we do this:

   procedure Op (S : in Integer_Sets.Container_Type) is

      A : Integer_Array (1 .. Length (S));
      J : Positive := A'First;

      procedure Process (E : in Integer) is
      begin
         A (J) := E;
         J := J + 1;
      end;

      procedure Fill_Array_A is
         new Integer_Sets.Generic_Process_Elements;  -- accept default
   begin
      Fill_Array_A (S);
      ...
   end Op;

This lets the set drive the iteration.

!end example


!example

Let's continue the streaming server examples from earlier.  In TCP, a
client "connects" to a server, who is "listening" on a port known to the
client.  When the server "accepts" the connection, this establishes a
dedicated connection with that client.  We can reify this in code as follows:

   package Connections is

      type Connection_Type (<>) is limited private;

      type Connection_Access is access Connection_Type;

      function Accept_Connection (Socket : Socket_Type)
         return Connection_Access;  -- the ctor for connection objects
      ...
   end Connections;

We have a need to keep track of all of our current connections.  So when
we create a new connection object, what we do is insert it in a set:

   package body Connections is

      function "<" (L, R : Connection_Access) return Boolean is
      begin
         return L.all'Address < R.all'Address;
      end;

      package Connection_Sets is
         new Ada.Containers.Sets.Sorted.Unbounded (Connection_Access);

      Connection_Set : Connection_Sets.Container_Type;

      function Accept_Connection (Socket : Socket_Type)
         return Connection_Access is

         Connection : constant Connection_Access :=
            new Connect_Type;
      begin
         Insert (Connection_Set, New_Item => Connection);
         ...
         return Connection;
      end;
      ...
   end Connections;

When a new connection object is allocated, it is inserted into the
Connection_Set.  Insertions should always succeed because each allocated
object has a unique access value, so here we don't bother checking.

Now let's suppose we want to shutdown a specific connection.  We can do
it like this:

   procedure Shutdown (X : in out Connection_Access) is
   begin
      if X /= null then
         Delete (Connection_Set, Item => X);
         Free (X);
      end if;
   end Shutdown;

Here we use the item-form of Delete to simply remove the item from the
set, and then deallocate it.

Now let's suppose we want to shutdown the entire system, and so we need
to clear out all of the connection objects.  We could do it like this:

   procedure Shutdown_Connections is
      X : Connection_Access;
   begin
      while not Is_Empty (Connection_Set) loop
         X := First_Element (Connection_Set);
         Shutdown (X);
      end loop;
   end Shutdown_Connections;

Another technique would be to use an active iterator, like this:

   procedure Shutdown_Connections is
      I : Iterator_Type := First (Connection_Set);
      J : constant Iterator_Type := Back (Connection_Set);

      X : Connection_Access;
   begin
      while I /= J loop
         X := Element (I);
         Delete (Connection_Set, Iterator => I);
         Free (X);
      end loop;
   end Shutdown_Connections;

Here we use the iterator-form of Delete.  Delete returns the successor
of the iterator deleted, so eventually the loop terminates.

!end example

!example

One of the canonical container examples is the set-of-employees.
Suppose we have an employee type defined this way:

   type Employee_Type is record
      SSN          : SSN_Type;   -- social security no.
      Name         : Name_Type;
      Home_Address : Home_Address_Type;
      ...;
   end record;

To make a set, we need to establish an order relationship for its
elements.  Since each social security number is presumably unique
(unless your identity has been stolen), we can use that to define an
order for Employee_Type:

   function "<" (L, R : Employee_Type) return Boolean is
   begin
      return L.SSN < R.SSN;
   end;

This allows us to instantiate the set in the normal way:

   package Employee_Sets is
      new Ada.Containers.Sets.Sorted.Unbounded (Employee_Type);

   Employees : Employee_Sets.Container_Type;

When someone gets a job at our firm, we add them to our little database
as follows:

   procedure Hire (Name : Name_Type; SSN : SSN_Type; ...) is

      Employee : Employee_Type;
   begin
      Employee.SSN := SSN;
      Employee.Name := Name;
      ...
      Insert (Employees, New_Item => Employee);
   end;

Now let's suppose that we need to modify some information for an
employee.  Like a map, a set orders its elements in key order, except
that for a set the element is its own key.  In the example here, the key
is really the SSN part of the employee object.

Suppose we only know the employee's social security number.  How do we
find that employee?  Remember that the "key" of a set is just the
element itself.  One way is to synthesize a dummy employee object, and
then look it up by element type:

   procedure Change_Address
     (SSN      : SSN_Type;
      New_Home : Home_Address_Type) is

      Iter : Iterator_Type;
   begin
      declare
         Dummy : Employee_Type;
      begin
         Dummy.SSN := SSN;
         Iter := Find (Employees, Item => Dummy);
      end;

      if Iter /= Null_Iter then ...;
   end;

But this is kind of a hack.  I don't really want to make a dummy element
just to look up the real element.  For many types synthesizing a dummy
object this way might not even be possible.

A much more elegant technique is to use the nested generic package
Generic_Keys, which allows you to explicitly name the key-part of the
element.  We can instantiate that package like this:

   function "<"
     (Employee : Employee_Type;
      SSN      : SSN_Type) return Boolean is
   begin
      return Employee.SSN < SSN;
   end;

   function "<"
     (SSN      : SSN_Type;
      Employee : Employee_Type) return Boolean is
   begin
      return SSN < Employee.SSN;
   end;

   package SSN_Keys is
      new Employee_Sets.Generic_Keys (SSN_Type);

With this new package we can now look up the employee by his SSN
directly:

   procedure Change_Address
     (SSN      : SSN_Type;
      New_Home : Home_Address_Type) is

      Iter : Iterator_Type := Find (Employees, Key => SSN);
   begin
      if Iter /= Null_Iter then ...;
   end;

OK.  That problem was solved.  Here's our next problem.  We want to
modify the employee object this way:

   declare
      E : Employee_Type renames To_Access (Iter).all;
   begin
      E.Home_Address := New_Home;
   end;

This is exactly how we'd do it if we were using a map instead of a set.
The To_Access function is an instantiation of the Generic_Element
selector function:

   type Employee_Access is access all Employee_Type

   function To_Access is
     new Employee_Sets.Generic_Element (Employee_Access);

However, this code fragment won't compile, because Generic_Element only
accepts an access constant type, like this:

   type Employee_Constant_Access is access constant Employee_Type

   function To_Constant_Access is
     new Employee_Sets.Generic_Element (Employee_Constant_Access);

This now compiles, but it's not what we want either, because we need a
variable view of the employee object in order to modify it.  After all,
we're trying to change his address.

The reason the set abstraction doesn't let you modify elements (very
easily, anyway -- keep reading) is that elements have a strict order,
and if you were to change an element's value such that it was no longer
in order then you'd break the set.

However, here we have a legitimate reason to modify an element in place.
We're not trying to change his social security number (which is what
defines the "order" for employees in this set), just his address, so we
should be allowed to make this modification.

To accomodate this need each of the associative containers has a generic
child subprogram to permit the key to be modified.  It's up the the
developer to ensure that his modifications are benign.  The new
instantion looks like this:

   with Ada.Containers.Sets.Sorted.Unbounded.Unchecked_Modification;

   package Employee_Sets is ...;

   type Employee_Access is access all Employee_Type

   function To_Access is
     new Employee_Sets.Unchecked_Modification (Employee_Access);

This is a sharp knife, but if we promise to be careful then we're
allowed to say:

   declare
      E : Employee_Type renames To_Access (Iter).all;
   begin
      E.Home_Address := New_Home;  -- OK
   end;

And now all is well.

Now let's say that the employee's wallet was stolen, which contained his
social security card.  In order to prevent identity theft, he needs to
apply for a new social security number, and then change his entry in the
database.

Clearly we can't use the technique we just showed, because changing his
SSN would break the database.  In this case we need to copy him out of
the set, change the value of the SSN field, and then (re)insert him:

   procedure Change_SSN
     (Old_SSN : SSN_Type;
      New_SSN : SSN_Type) is

      Iter : Iterator_Type := Find (Employees, Key => Old_SSN);
   begin
      if Iter /= Null_Iterator then
         declare
            Employee : Employee_Type := Element (Iter);
         begin
            Employee.SSN := New_SSN;
            Insert (Employees, New_Item => Employee);
         end;
      end if;
   end Change_SSN;

Suppose now we want a list all the employees in the firm.  One way to do
it is like this:

   procedure Display is

      procedure Process (Employee : in Employee_Type) is
      begin
         Put ("Name: ");  Put (Employee.Name);
         Put ("SSN: "); Put (Employee.SSN);
         ...;
      end;

      procedure Print is
         new Employee_Sets.Generic_Process_Elements; -- "Process"
   begin
      Print (Employees);
   end;

However, this will list employees in order of their social security
number.  This is probably not what we want, which is to print employees
in order of name.

One way would be to copy the elements into some other container, which
is sorted according to the criterion we desire.  However, if elements
are large or otherwise not easily copied, then this is not not really an
option.

A much better way is not to copy elements directly but rather to copy
iterators that designate those elements.  Here we use a singly-linked
bounded list, which comes with its own sort operation:

   procedure Display is

      package List_Types is  -- nested instantiation
         new Ada.Containers.Lists.Single.Bounded
           (Element_Type => Employee_Sets.Iterator_Type);

      List : List_Types.Container_Type (Size => Length (Employees));

   begin -- Display

      declare
         procedure Process (I : Employee_Sets.Iterator_Type) is
         begin
            Append (List, New_Item => I);
         end;

         procedure Populate_List is
            new Employee_Sets.Generic_Iteration; -- "Process"
      begin
         Populate_List (Employees);
      end;

      declare
         function "<" (L, R : Employee_Sets.Iterator_Type)
           return Boolean is

           EL : Employee_Type renames To_Constant_Access (L).all;
           ER : Employee_Type renames To_Constant_Access (R).all;
         begin
           return EL.Name < ER.Name;
         end;

         procedure Sort is
            new List_Types.Generic_Sort;  -- "<"
      begin
         Sort (List);
      end;

      declare
         procedure Process (I : Employee_Sets.Iterator_Type) is
            E : Employee_Type renames To_Constant_Access (I).all;
         begin
            Put ("Name: ");  Put (E.Name);
            Put ("SSN: "); Put (E.SSN);
            ...;
         end;

         procedure Print_Employees is
            new List_Types.Generic_Process_Elements; -- "Process"
      begin
         Print_Employees (List);
      end;

   end Display;

First we use the passive iterator for sets to insert an iterator
designating every employee into the bounded linked-list.  We used a
bounded linked-list because we know the maximum number of elements,
which is just the number of elements in the set.

Next we define an order for relation for list elements, which here are
just set iterators.  We wish to print employees in order of name, so the
order relation for iterators is defined in terms of the names of the
employees designated by the iterators.

Note carefully that we didn't use the element selector for iterators:

   EL : Employee_Type := Element (L); -- wrong!

This would make a copy of the employee designated by the iterator, which
is not what we want at all.  That would undermine our reason for sorting
set iterators instead of set elements.  So we used our element access
selector, which returns an access object.  Here we just use the plain
selector, that returns an access object with a constant view, since we
don't need to modify the employee -- only read it.

Now that the employees (really, iterators that designate the employees)
have been sorted, we use the passive iterator for lists to traverse all
the set iterators, and print each employee (in name order) in turn.

!end example


!example

In you were paying very careful attention to the Id_Map example earlier,
you might have realized that since the session object (which was the
element of the map) had an Id field, we were in fact duplicating the Id
object, since it's also stored as the key-part of the map entry.

In turns out we didn't really need to use a map.  We could have used a
set, and instantiated the Generic_Keys nested package using type String
as the formal Key_Type.

   function "<" (L, R : Session_Access) return Boolean is
   begin
      return L.Id < R.Id;
   end;

   package Session_Set_Types is
      new Ada.Containers.Sets.Sorted.Unbounded (Session_Access);

   -- instead of Id_Map, use a set to store sessions:
   Session_Set : Session_Set_Types.Container_Type;

   function "<"
     (Session : Session_Access;
      Id      : String) return Boolean is
   begin
      return Session.Id < Id;
   end;

   function "<"
     (Id      : String;
      Session : Session_Access) return Boolean is
   begin
      return Id < Session.Id;
   end;

   package Id_Keys is
      new Session_Set_Types.Generic_Keys (String);

This lets us perform session lookups based on the session identifier:

   procedure Play
     (Session_Id  : in     String;
      NPT_Range   : in     NPT_Range_Type;
      RTSP_Status :    out RTSP_Status_Type) is

      Iter : constant Session_Set_Types.Iterator_Type :=
        Find (Session_Set, Key => Session_Id);

      Session : Session_Access;
   begin
      if Iter = Null_Iterator then
         RTSP_Status := RTSP.Session_Not_Found;
         return;
      end if;

      Session := Element (Iter);

      Play (Session, NPT_Range, RTSP_Status);
   end;

We can insert a session object into the set in the normal way:

   function Setup_Session return Session_Access is
      Session : constant Session_Access := new Session_Type;
   begin
      Generate_Id (Session.Id);
      Insert (Sessions_Set, New_Item => Session);
      ...
      return Session;
   end;

An alternate way to do this is to use the key-form of insertion:

   procedure Set_Key
     (Session : in out Session_Access;
      Id      : in     String) is
   begin
      Session := new Session_Type;
      Session.Id = Id;
   end;

   procedure Insertion is
      new Id_Types.Generic_Insertion;  -- accept default

   function Setup_Session return Session_Access is
      Id      : Id_Subtype;
      Iter    : Session_Set_Types.Iterator_Type;
      Success : Boolean;
      Session : Session_Access;
   begin
      Generate_Id (Id);
      Insert (Sessions_Set, Id, Iter, Success);
      Session := Element (Iter);
      ...
      return Session;
   end;

This example also illustrates that sets and maps are essentially the
same.  The only difference is where the key lives.

!end example


The specification of the root of the multisets subsystem is as follows:

package Ada.Containers.Multisets is
   pragma Pure;
end Ada.Containers.Multisets;

The specification of the root of the hashed multiset containers
subsystem is as follows:

package Ada.Containers.Multisets.Hashed is
   pragma Pure;
end Ada.Containers.Multisets.Hashed;


The specification of the hashed multiset container differs from the
hashed (unique-element) set container as follows:

(1) The package is named Ada.Containers.Multisets.Hashed.Unbounded.

(2) The signature and semantics of the operations named Insert differ as
    follows:

procedure Insert
  (Container : in out Container_Type;
   New_Item  : in     Element_Type;
   Iterator  :    out Iterator_Type);

If Length (Container) equals Size (Container), then Container is resized
to some larger size as per Resize.  The index in the buckets array is
then computed from the hash value of New_Item and the size of Container.
The actual subprogram bound to Is_Equal_Key is then used to compare
New_Item to each node in the list of the buckets array at that index.
If the predicate returns True, then a new node is allocated with the
first matching node as its successor; otherwise, if there are no
elements that match key, a new node is allocated at the head of the
list.  Iterator designates the newly-inserted node.  Any exceptions
raised during allocation are propagated, and Container is not modified.


procedure Insert
  (Container : in out Container_Type;
   New_Item  : in     Element_Type);

Equivalent to Insert (Container, New_Item, Iterator), with the
difference that the iterator parameter is omitted.


(3) The signature and semantics of the operations named
    Insert_Sans_Resize differ as follows:

procedure Insert_Sans_Resize
  (Container : in out Container_Type;
   New_Item  : in     Element_Type;
   Iterator  :    out Iterator_Type);

Inserts the New_Item into Container as per Insert (Container, New_Item,
Iterator), with the difference that there is no resize prior to the
insertion proper.  If Size (Container) equals 0, the operation fails and
Constraint_Error is propagated.


procedure Insert_Sans_Resize
  (Container : in out Container_Type;
   New_Item  : in     Element_Type);

Equivalent to Insert_Sans_Resize (Container, New_Item, Iterator), with
the difference that the iterator parameter is omitted.


(4) The semantics of Delete (Container, Item) and Generic_Keys.Delete
    (Container, Key) differ as follows:

Deletes all the nodes whose elements match Item (Key).  If Length
(Container) equals 0, then Delete does nothing.  Otherwise the buckets
array index is computed from the hash value of Item (Key) and the size
of Container.  Any exception raised during invokation of Hash is
propagated.  The nodes of the list at that index in the buckets array
are compared to Item (Key) using Is_Equal_Key.  The nodes that match
Item (Key) are removed from the multiset and deallocated.  Any
exceptions raised by Is_Equal_Key or during deallocation are propagated.


(5) The semantics of Generic_Equal_Range (Container, Item) and
    Generic_Keys.Generic_Equal_Range (Container, Key) differ as follows.

Attempts to find the first matching element as per Find (Container,
Item) or Find (Container, Key).  If a matching element is found, it
invokes the generic actual bound to Process for each matching element in
the list at that index of the buckets array.  The iteration terminates
when either the list is exhausted, or a non-matching element is found.


(6) The semantics of the insertion operations in the nested generic
    package Generic_Insertion differ as follows:

procedure Insert
  (Container : in out Container_Type;
   Key       : in     Key_Type;
   Iterator  :    out Iterator_Type);

If Length (Container) equals Size (Container), then Container is resized
to some larger value as per Resize.  The index in the buckets array is
then computed from the hash value of Key and the size of Container.  The
actual subprogram bound to Generic_Keys.Is_Equal_Key is used to compare
Key to the element of each node in the list at that index of the buckets
array.  If the predicate returns True, then a new node is allocated with
the matching node as its successor; otherwise, if there are no elements
that match key, a new node is allocated at the head of the list, and
then Set_Key is invoked with Key as the parameter.  Iterator designates
the newly-inserted node.  Any exception raised during the allocation or
initialization is propagated, and Container is not modified.


procedure Insert_Sans_Resize
  (Container : in out Container_Type;
   Key       : in     Key_Type;
   Iterator  :    out Iterator_Type);

Inserts a new node per Insert (Container, Key, Iterator), with the
difference that Container is not resized.  If Size (Container) equals 0,
the operation fails and Constraint_Error is propagated.

(7) The generic child subprogram is named
    Ada.Containers.Multisets.Hashed.Unbounded.Unchecked_Modification.


!example

The canonical example for multisets is to have a database of people
objects, with the name as the key.  We can set things up like this:

   type Person_Type is record
      Family_Name : Name_Subtype;  -- some kind of String subtype
      Given_Name  : Name_Subtype;
      Gender      : Gender_Type;
      ...
   end Person_Type;

   function Hash (Person : Person_Type) return Integer'Base is
   begin
      return Hash_String_Case_Insensitive (Person.Family_Name);
   end;

   package Person_Multiset_Types is
      new Ada.Containers.Multisets.Hashed.Unbounded
           (Element_Type => Person_Type,
            Hash         => Hash,
            Is_Equal_Key => Equal_String_Case_Insensitive);

   Persons : Person_Multiset_Types.Container_Type;

Now we can add some people to our database:

   procedure Op is
      P : Person_Type;
   begin
      P.Family_Name := "Heaney";
      P.Given_Name := "Matthew";
      ...
      Insert (Persons, New_Item => P);

      P.Family_Name := "Santini";
      P.Given_Name := "Thomas";
      ...
      Insert (Persons, New_Item => P);

      P.Family_Name := "Manchester";
      P.Given_Name := "Janet";
      ...
      Insert (Persons, New_Item => P);
   end;

Now if we instantiate the nested Generic_Keys package, like this:

   function Is_Equal_Key
     (Person : Person_Type;
      Name   : String) is
   begin
      return Equal_String_Case_Insensitive (Person.Name, Name);
   end;

   package Name_Keys is
      new Person_Multiset_Types.Generic_Keys
         (Key_Type => String,
          Hash     => Hash_String_Case_Insensitive);

we can iterate over all the persons that share a family name:

   procedure Op (Family : in String) is

      procedure Print (Person : in Person_Type) is
      begin
         ... -- print this person
      end;

      procedure Print_Family is
        new Name_Keys.Generic_Equal_Range (Print);
   begin
      Print_Family (Persons, Key => Family);
   end;

Of course we could walk the sublist manually:

   procedure Op_v2 (Family : in String) is

      procedure Process (Person : in Person_Type) is
      begin
         ... -- print this person
      end;

      procedure Print is
         new Person_Multiset_Types.Generic_Process_Element;

      I : Person_Multiset_Types.Iterator_Type :=
        Find (Person, Key => Family);

   begin -- Op_v2

      if I = Null_Iterator then
         return;
      end if;

      loop
         Print (I);
         Increment (I);
         exit when I = Null_Iterator;
         exit when not Is_Equal_Key (I);
      end loop;

   end Op_v2;

!end example


The specification of the root of the sorted multiset containers subsystem is
as follows:

package Ada.Containers.Multisets.Sorted is
   pragma Pure;
end Ada.Containers.Multisets.Sorted;

The specification of the sorted multiset container differs from the
sorted (unique-element) set container as follows:

(1) The signature and semantics of Insert changes as follows:

procedure Insert
  (Container : in out Container_Type;
   New_Item  : in     Element_Type;
   Iterator  :    out Iterator_Type);

Inserts the New_Item into Container.  The New_Item is compared to
elements already in the container using the subprogram bound to the
generic formal "<" operator.  New_Item is inserted after equivalent
elements already in the container.  Any exceptions raised during
evaluation of the relational operator or during allocation are
propagated, and Container is not modified.


procedure Insert
  (Container : in out Container_Type;
   New_Item  : in     Element_Type);

Equivalent to Insert (Container, New_Item, Iterator), with the
difference that the iterator parameter is omitted.


procedure Insert
  (Container : in out Container_Type;
   Position  : in     Iterator_Type;
   New_Item  : in     Element_Type;
   Iterator  :    out Iterator_Type);

Equivalent to Insert (Container, New_Item, Iterator), with the addition
of a Position parameter, which specifies a hint about where to begin
searching for equivalent keys.  If Position equals Null_Iterator, this
operation is equivalent to the Position-less version of Insert.  If
Position does not designate a node that belongs to Container, program
execution is erroneous.

Insert first assumes Position is a potential successor.  If Position
equals Back (Container) or the element designated by Position is not
less than New_Item, and Position equals First (Container) or New_Item is
not less than the predecessor of Position, then New_Item is inserted
immediately prior to Position.

If Position is not a successor, then Insert assumes Position is a
potential predecessor.  If New_Item is not less than Position, and the
successor of Position is not less than New_Item, then New_Item is
inserted immediately following Position.

Otherwise, New_Item is inserted as per the Position-less version of
Insert.


(2) The semantics of Delete (Container, Item) and Generic_Keys.Delete
    (Container, Key) differ as follows:

Deletes all the nodes whose elements are equivalent to Item (Key).  Any
exceptions raised by "<" or during deallocation are propagated.


(3) The semantics of Find (Container, Item) and
Generic_Keys.Generic_Find (Container, Key) differ as follows:

Finds the front-most node whose element is equivalent to Item (Key), and
returns an iterator that designates that node.  If there are no
equivalent elements, the iterator value Null_Iterator is returned.  Any
exceptions raised during evaluation of "<" are propagated.


(4) The semantics of Generic_Equal_Range (Container, Item) and
    Generic_Keys.Generic_Equal_Range (Container, Key) differ as follows.

Attempts to find the front-most equivalent element as per Find
(Container, Item) or Find (Container, Key).  If the search is
successful, it invokes the generic actual bound to Process for each
equivalent element in Container.


(5) The semantics of the insertion operations in the nested generic
    package Generic_Insertion differ as follows:

procedure Insert
  (Container : in out Container_Type;
   Key       : in     Key_Type;
   Iterator  :    out Iterator_Type);

Key is compared to elements already in the set using the subprogram
bound to the Generic_Insertion generic formal "<" operators.  A new node
is allocated with an uninitialized element, and then
Generic_Insertion.Set_Key is immediately invoked with the element and
Key as the parameters.  If Set_Key raises an exception, the node is
deallocated and the exception is propagated without modifying Container.
Otherwise, the node is inserted into the tree after any elements that
are equivalent to Key, and Iterator designates the newly-allocated,
key-initialized node.  Any exceptions raised during evaluation of the
relational operator or during allocation are propagated, and Container
is not modified.


procedure Insert
  (Container : in out Container_Type;
   Position  : in     Iterator_Type;
   Key       : in     Key_Type;
   Iterator  :    out Iterator_Type);

Equivalent to Insert (Container, Key, Iterator), with the addition of a
Position parameter, which specifies a hint about where to insert the new
element.  If Position equals Null_Iterator, this operation is equivalent
to the Position-less version of Insert.  If Position does not designate
a node that belongs to Container, program execution is erroneous.

Otherwise it allocates a node and key-initializes its element as per
Insert (Container, Key, Iterator), and inserts it in the tree as per
Insert (Container, Position, New_Item, Iterator).


(6) The generic child subprogram is named
    Ada.Containers.Multisets.Sorted.Unbounded.Unchecked_Modification


!example

We could have just as easily implemented our persons database using the
sorted multiset.  To print out all the persons that share a family name,
we would instantiate the Generic_Keys generic nested package this way:

   function "<"
     (Person : Person_Type;
      Name   : String) return Boolean is
   begin
      return Less_String_Case_Insensitive (Person.Name, Name);
   end;

   function "<"
     (Name   : String;
      Person : Person_Type) return Boolean is
   begin
      return Less_String_Case_Insensitive (Name, Person.Name);
   end;

   package Name_Keys is
      new Person_Multiset_Types.Generic_Keys (String);

The syntax for using Generic_Equal_Range is the same as for the hashed
multiset.  For a sorted multiset, however, we walk the equivalent range
like this:

   procedure Op_v3 (Family : in String) is

      procedure Process (Person : in Person_Type) is
      begin
         ... -- print this person
      end;

      procedure Print is
         new Person_Multiset_Types.Generic_Process_Element;

      I : Person_Multiset_Types.Iterator_Type :=
         Lower_Bound (Persons, Key => Family);

      J : constant Person_Multiset_Types.Iterator_Type :=
         Upper_Bound (Persons, Key => Family);

   begin -- Op_v3

      while I /= J loop
         Print (I);
         Increment (I);
      end loop;

   end Op_v3;

!end example


!example

My streaming media server is essentially a main loop, which works off
"events" from a queue.  Each event has a time tag that specifies when
the event comes due.  Events that come due earlier in time are processed
sooner than events that expire later.

We basically have a priority queue, with element priority determined by
an event's time.  An event looks like this:

   type Event_Type;
   type Event_Access is access all Event_Type;

   function "<" (L, R : Event_Access) return Boolean;

A sorted multiset suits our needs exactly.  We use a multiset because
many events can come due at the same time:

   package Event_Queue_Types is
      new Ada.Containers.Multisets.Sorted.Unbounded (Event_Access);

   type Event_Type is limited record
      Time                 : Time_Type;
      Event_Queue_Iterator : Event_Queue_Types.Iterator_Type;
      ...;
   end record;

   function "<" (L, R : Event_Access) return Boolean is
   begin
      return L.Time < R.Time;
   end;

Note that here the order relation is defined in terms of the time-tag of
the events designated by each access object.  We're not comparing access
objects directly (as we've done in other examples).

In fact the session objects with which we are all by now imtimately
familiar are actually implemented something like this:

   type Session_Type is limited record
      Id        : Id_Subtype;
      RTP_Event : aliased Event_Type;
      ...
   end record;

The event queue object is declared the body the sessions package:

   package body Sessions is

      Event_Queue : Event_Queue_Types.Container_Type;
      ...

We work off events in the queue like this:

   Main:
   loop

      while Is_Empty (Event_Queue) loop
         ... -- do some work
         sleep (1);  -- delay for 1ms
      end loop;

      Curr_Time : constant Time_Type := Clock;

      loop

         Event : constant Event_Access :=
            First_Element (Event_Queue);  -- top

         exit when Event.Time > Curr_Time;

         Process (Event);  -- Event.Time <= Curr means it's due NOW

         Delete_First (Event_Queue);   -- pop

         exit when Is_Empty (Event_Queue);

      end loop;

      sleep (1);   -- be nice to other processes on this machine

   end loop Main;

Events are placed in the queue when a client issues an RTSP PLAY
request:

   procedure Play_Stream
     (Session : access Session_Type;
      Track   : in String) is

      Success : Boolean;
   begin
      ...
      Insert
        (Container => Event_Queue,
         New_Item  => Session.RTP_Event'Access,
         Iterator  => Session.RTP_Event.Event_Queue_Iterator,
         Success   => Success);
      ...
   end;

Events can leave the queue for many reasons.  One reason is when a
client issues an RTSP PAUSE request:

   procedure Pause_Stream
     (Session : access Session_Type;
      Track   : in String) is
   begin
      ...
      Delete (Event_Queue, Session.RTP_Event.Event_Queue_Iterator);
      ...
   end;

It's not good enough to use the item-form (or even the key-form) of
Delete, because many events can have the same key (remember this is a
multiset).  We need a way to uniquely specify the element we want do
delete, which is what the iterator-form does.

This example also demonstrates a nearly-ubiquitous idiom.  In the body
of a package in which a type is declared, an associative container
object is declared, which keeps track of instances of the type.

!end example


!example

An interesting question is, What is a set?  In the philosophy of this
library, the answer is "any sorted container."

Let's say we have two containers.  One is a sorted set.  The other is a
linked-list that has been sorted according the same order relation as
the set.  Suppose we want to take the union of the elements in both
containers and then print the resulting "set" in sort order.

We have stated in earlier examples that generic algorithms abstract-away
the containers.  We see the benefit of such an approach now, because we
need the union of elements in two different kinds of containers.

The union of a set and a sorted list can be done like this:

   procedure Print_Union
     (Set  : in Set_Types.Container_Type;
      List : in List_Types.Container_Type) is

      procedure Print (E : in Element_Subtype) is ...;

      procedure Process is  -- has SI as param
         new Set_Types.Generic_Process_Element (Print);

      procedure Process (LI : List_Types.Iterator_Type) is
         procedure Process (E : in out Element_Subtype) is
         begin
            Print (E);  -- change inout to just in
         end;

         procoedure Print is
            new List_Types.Generic_Process_Element; -- "Process"
      begin
         Print (LI);
      end;

      function Is_Less
        (SI : Set_Types.Iterator_Type;
         LI : List_Types.Iterator_Type) return Boolean is

         LE : Element_Subtype renames To_Access (LI).all;
      begin
         return SI < LE;  -- set has "<" ops for iter_t
      end;

      function Is_Less
        (LI : List_Types.Iterator_Type;
         SI : Set_Types.Iterator_Type) return Boolean is

         LE : Element_Subtype renames To_Access (LI).all;
      begin
         return LE < SI;
      end;

      procedure Union is
         new Generic_Set_Union_2    -- liberal definition of "set"
           (Set_Types.Iterator_Type,
            List_Types.Iterator_Type);  -- accept defaults

   begin -- Print_Union

      Union (First (Set), Back (Set), First (List), Back (List));

   end Print_Union;

The action of the union algorithm is to print all the elements in both
the set and list containers, in element-order.  The generic algorithm
itself just manipulates iterators.  It doesn't know anything about
containers or even about elements.  A very powerful idea, indeed!

!end example


*************************************************************

From: Randy Brukardt
Sent: Thursday, September 25, 2003  4:26 PM

I've posted this as AI-00302-02.
http://www.ada-auth.org/cgi-bin/cvsweb.cgi/AIs/AI-10302.TXT

Questions and comments:

(Note: I didn't read this really carefully. These are just things that jumped
out at me...)

Procedural:

The document does not have a !summary, !problem, or !proposal. While the others
are easy, !problem really needs a statement. (The last AI I submitted without a
problem statement got summarily killed - permanently. Don't make that mistake!)

The document has interspersed examples and comments. That's useful for
understanding the proposal, but it confuses what is actual wording and what is
not (and the automated tools can't handle it). For instance, it may make sense
to include examples in the standard. Similarly, some notes should be included
in the standard. It also is a good idea to identify text that should be
included in the AARM version of the document.

Some of the containers are defined by analogy. That's OK for the semantics, but
the complete package specification needs to be given.

The net effect is that the AI I created is mostly empty. The proposal is in the
!appendix.

Technical:

    All of the types are called "Container_Type". People who use use clauses
    will not be pleased, as fully qualified names are required if multiple
    containers are in use. Jeffrey Carter's original proposal was that way, and
    there was a consensus to change it to individual names like
    "Unbounded_List".

    Vectors.Unbounded has an Assign operation. However, the container type is
    private, not limited private. Thus, := is allowed. How does Assign differ
    from ":="? Are there any requirements on :=? Is Assign needed? Presuming it
    is, the write-up should explain why. (Various other types have this same
    problem.)

    Requiring C convention for certain arrays seems inappropriate. The standard
    is very careful to keep all foreign language stuff in Annex B (only putting
    necessary infrastructure into the core). Moreover, what about compilers
    that don't support convention C? (That's certainly allowed by the
    standard.) Finally, the reason for doing it doesn't seem that compelling.
    You don't want to use thin bindings directly in any case. And I see no
    reason to insist that components are directly usable in such bindings -
    build a good wrapper and use the components there if you wish.

    Are 'Read and 'Write expected to work on these components? 13.13.1(36.1/1)
    says yes. Similarly, these containers need to be task safe (presuming that
    different objects are being operated on). Any exceptions need to be noted
    in the wording.

    What precisely does "If Source is an alias of Target" mean? I don't think
    that is RM wording. Perhaps you should say "If Source denotes the same
    object as Target". It is the object that matters, (right?) not how it is
    denoted.

    When there are 18 differences between two packages (which probably have
    less then that many declarations!), it probably would be better to describe
    the second one in full and describe the similarities in a note. This
    happens, for instance, in Lists.Double.Bounded.

    Why is the formal parameter Is_Equal_Key in Maps.Hashed.Unbounded called
    that, rather than "="? It's especially weird since the default is "=". I
    presume there was a reason, and it just wasn't explained.

    There are lots of "In the absence of any explicit initialization, objects
    of type ... are default-initialized to ...". You probably should use the
    wording of Unbounded_Strings: "If an object of type ... is not otherwise
    initialized, it will be initialized to ...". A.4.5(73). Meta-rule: Always
    reuse RM wording when possible.

    In procedure Generate_Id in your maps examples, you use Hint (an out
    parameter), but never set it anywhere. Perhaps "Iter" should be "Hint"??

Technical opinions:

    The overall size of this proposal seems huge. It seems to me to be too
    large an addition for the standard.

    I have to wonder if the passive iterators are needed. Our experience with
    Ada.Directories was that even ARG members failed to understand how they
    worked. Moreover, my personal experience with using them (mainly in the
    Claw Builder) has been pretty dismal. Writing a procedure and an
    instantiation to find out if there is a child window at a point (a one-call
    operation) is a real pain. I see that the passive iterators don't provide a
    "Kill" (or stop) parameter to the processing routine. While that clearly
    simplifies the interface, it also means that they are rarely going to be
    useful, and makes it much more likely that it would become necessary to
    rewrite them under new conditions. (Most of my iterators are "Find" types,
    where once you find what you are looking for, you can - and should - quit.)

    I'm mildly confused by "Iterators". That seems to be a bizarre name for
    what typically are called "Positions" or "Accessors". When I hear
    "iterator", I expect it to do some iteration (as in the passive iterators,
    which are really "automatic" iterators), rather than these, which are used
    manually to construct an "active iterator").

    Are the bounded versions of things really necessary? We decided that they
    were in the case of strings, but what has happened in practice is that
    hardly anyone uses Bounded_Strings. Should we make that mistake again?

    The emphasis on performance is misguided in my view. The experience with
    Bounded_Strings suggests that details of implementation may not make that
    much difference in most (probably virtually all) cases. A single
    well-designed component can meet the vast majority of uses. If profiling
    shows that a particular component is too slow, it should be replaced by a
    carefully crafted custom data structure. Having a bunch of other
    implementations around might help in rare circumstances, but the problem is
    usually a combination of component overhead and trying to make a not quite
    right data structure do the job. That's just another instance of "if I have
    a hammer, everything looks like a nail". That's a common problem in CS,
    driven a lot by marketers. I've seen if for components and databases and
    OOP and on and on.

    Having this emphasis on performance leads to trying to define time bounds
    and the like on operations. That leads to saying things like "the time
    complexity of this operation is O(1)". But that is inappropriate language
    for the standard, at least until you define what you mean by "time
    complexity" and "O(1)". It could be implementation advice or a
    documentation requirement without quite so much definition, but it can't be
    normative text. We don't want untestable normative text!

*************************************************************

From: Matthew Heaney
Sent: Thursday, September 25, 2003  4:46 PM

> Procedural:
>
> The document does not have a !summary, !problem, or !proposal. While the
> others are easy, !problem really needs a statement. (The last AI I submitted
> without a problem statement got summarily killed - permanently. Don't make
> that mistake!)

OK.  I write these up.


> The document has interspersed examples and comments. That's useful for
> understanding the proposal, but it confuses what is actual wording and what
> is not (and the automated tools can't handle it). For instance, it may make
> sense to include examples in the standard. Similarly, some notes should be
> included in the standard. It also is a good idea to identify text that
> should be included in the AARM version of the document.

I can easily separate examples from non-examples.  Just tell me how you want
things organized, and that's what I'll do.  Do you want:

all container specs and descriptions, followed by all examples; or,

container spec and description, examples for that container, followed by next
container spec and description, followed by examples for that container, etc


> Some of the containers are defined by analogy. That's OK for the semantics,
> but the complete package specification needs to be given.

I was doing that for your benefit, but if it's not what you want I can put in
complete specs as you require.

How would you like me to do this: resend you the complete API, with all the
container specs, or just send you the specs for the containers I defined by
analogy?

*************************************************************

From: Randy Brukardt
Sent: Thursday, September 25, 2003  5:14 PM

> I can easily separate examples from non-examples.  Just tell me
> how you want things organized, and that's what I'll do.  Do you want:
>
> all container specs and descriptions, followed by all examples; or,
>
> container spec and description, examples for that container,
> followed by next container spec and description, followed by
> examples for that container, etc

The standard amendment AI has the following sections:

    !summary
    !problem
    !proposal
    !wording
    !discussion
    !example
    !corrigendum
    !ACATS test
    !appendix

You can leave !corrigendum to me (it isn't worth doing until this is close
to approval, it's the formatted text for the tools), and ACATS test is
trivial ("ACATS Tests will be needed for this library.") until it's done.

Wording should be what goes into the RM (and AARM). Look at the existing
Annex A for a possible layout. What I did for Jeffrey Carter's containers
proposal is probably the best layout. (I put the subheaders in, and then
used them to separate the discussion and examples so that readers could read
in roughly the intended order for each section.) See AI-00302-01
(http://www.ada-auth.org/cgi-bin/cvsweb.cgi/AIs/AI-00302.TXT).

> > Some of the containers are defined by analogy. That's OK for the semantics,
> > but the complete package specification needs to be given.
>
> I was doing that for your benefit, but if it's not what you want
> I can put in complete specs as you require.

Well, !wording should be as close to what will appear in the RM as possible.
That is, the ARG (and certainly not your editor!) doesn't want to be
word-smithing this stuff. (Don't worry, we will anyway -- see my list of
technical points. :-)

> How would you like me to do this: resend you the complete API,
> with all the container specs, or just send you the specs for the
> containers I defined by analogy?

I guess I'd prefer to get the whole AI organized in the correct order. Feel
free to clean up the wording and example glitches I noticed, too. :-)

*************************************************************

From: Matthew Heaney
Sent: Thursday, September 25, 2003  5:07 PM

>     Vectors.Unbounded has an Assign operation. However, the container type
> is private, not limited private. Thus, := is allowed. How does Assign differ
> from ":="? Are there any requirements on :=? Is Assign needed? Presuming it
> is, the write-up should explain why. (Various other types have this same
> problem.)

Mostly for consistency between the unbounded and bounded forms.  I assumed it
would make switching between forms easier should the developer need to do so.
The other reason is that I assumed it would be more efficient, since it's a
plain op instead of a controlled action.

>     Requiring C convention for certain arrays seems inappropriate. The
> standard is very careful to keep all foreign language stuff in Annex B (only
> putting necessary infrastructure into the core). Moreover, what about
> compilers that don't support convention C? (That's certainly allowed by the
> standard.) Finally, the reason for doing it doesn't seem that compelling.
> You don't want to use thin bindings directly in any case. And I see no
> reason to insist that components are directly usable in such bindings -
> build a good wrapper and use the components there if you wish.

I can only speak to my own experience.  Being able to use a C++ std::vector
when the Win32 API calls for an array vastly simplifies using the API.  I
didn't think requiring C convention would be any burden, but I'm a language
user not a compiler writer.

>     Are 'Read and 'Write expected to work on these components?
> 13.13.1(36.1/1) says yes. Similarly, these containers need to be task safe
> (presuming that different objects are being operated on). Any exceptions
> need to be noted in the wording.

I don't know.  I asked the same question in my list of outstanding issues.  If
you want containers to support T'Read and T'Write then that's what we'll do.

>     What precisely does "If Source is an alias of Target" mean? I don't
> think that is RM wording. Perhaps you should say "If Source denotes the same
> object as Target". It is the object that matters, (right?) not how it is
> denoted.

OK.  I can change the wording.

>     When there are 18 differences between two packages (which probably have
> less then that many declarations!), it probably would be better to describe
> the second one in full and describe the similarities in a note. This
> happens, for instance, in Lists.Double.Bounded.

Fair enough.  You wanted a complete spec anyway so this isn't a problem.

>     Why is the formal parameter Is_Equal_Key in Maps.Hashed.Unbounded called
> that, rather than "="? It's especially weird since the default is "=". I
> presume there was a reason, and it just wasn't explained.

Because there's already an "=" declared in the generic formal region.  You
can't say:

   package P is new GP ("=" => My_Equal);

if there are two generic formal functions with the "=" designator.

>     There are lots of "In the absence of any explicit initialization,
> objects of type ... are default-initialized to ...". You probably should use
> the wording of Unbounded_Strings: "If an object of type ... is not otherwise
> initialized, it will be initialized to ...". A.4.5(73). Meta-rule: Always
> reuse RM wording when possible.

Yes, you are correct.  I'll fix that.

>     In procedure Generate_Id in your maps examples, you use Hint (an out
> parameter), but never set it anywhere. Perhaps "Iter" should be "Hint"??

Hmmmm... I'll take a look.  It should have been set in the implementation, as
the result of Lower_Bound.  Sounds like a typo.

> Technical opinions:
>
>     The overall size of this proposal seems huge. It seems to me to be too
> large an addition for the standard.

Then I have to ask, seems huge compared to what?  It is certainly not huge
compared to the C++ std library.

Really, I have no idea how large or small an API you want.  All I can do is get
the rocket off the lauching pad, and then have you tell me where you want to
go...

>     I have to wonder if the passive iterators are needed.

Yes, they are needed.  In fact, it was Tucker who pushed for them.

> Our experience with Ada.Directories was that even ARG members failed to
> understand how they worked. Moreover, my personal experience with using them
> (mainly in the Claw Builder) has been pretty dismal. Writing a procedure
> and an instantiation to find out if there is a child window at a point (a
> one-call operation) is a real pain. I see that the passive iterators don't
> provide a "Kill" (or stop) parameter to the processing routine. While that
> clearly simplifies the
> interface, it also means that they are rarely going to be useful, and makes
> it much more likely that it would become necessary to rewrite
> them under new conditions. (Most of my iterators are "Find" types, where
> once you find what you are looking for, you can - and should - quit.)

As I show in the examples, all of this is possible using active iterators.

>     I'm mildly confused by "Iterators". That seems to be a bizarre name for
> what typically are called "Positions" or "Accessors". When I hear "iterator",
> I expect it to do some iteration (as in the passive iterators, which are
> really "automatic" iterators), rather than these, which are used
> manually to construct an "active iterator").

There's no magic here.  Just pick up any C++ book.  Or read that section of
GoF.

>     Are the bounded versions of things really necessary? We decided that
> they were in the case of strings, but what has happened in practice is that
> hardly anyone uses Bounded_Strings. Should we make that mistake again?

The bounded forms are not a mistake.  In fact the bounded container forms
correct the mistakes made by the bounded strings package.  (Perhaps if you had
the whole spec in front of you it would be more obvious.  If you can wait
another day or two I'll have the full specs, as you requested.)

>     The emphasis on performance is misguided in my view. The experience with
> Bounded_Strings suggests that details of implementation may not make that
> much difference in most (probably virtually all) cases. A single
> well-designed component can meet the vast majority of uses. If profiling
> shows that a particular component is too slow, it should be replaced by a
> carefully crafted custom data structure. Having a bunch of other
> implementations around might help in rare circumstances, but the problem is
> usually a combination of component overhead and trying to make a not quite
> right data structure do the job. That's just another instance of "if I have
> a hammer, everything looks like a nail". That's a common problem in CS,
> driven a lot by marketers. I've seen if for components and databases and OOP
> and on and on.

The library reflects the vision of its designer.  Its designer didn't think
emphasis on performance was misguided.

But like I said, all I can do is get the rocket ship off the launching pad, and
have you tell me what you want it to go...

>     Having this emphasis on performance leads to trying to define time
> bounds and the like on operations. That leads to saying things like "the
> time complexity of this operation is O(1)". But that is inappropriate
> language for the standard, at least until you define what you mean by "time
> complexity" and "O(1)". It could be implementation advice or a documentation
> requirement without quite so much definition, but it can't be normative
> text. We don't want untestable normative text!

The normative text can say as much or as little as you deem fit.

*************************************************************

From: Randy Brukardt
Sent: Thursday, September 25, 2003  7:02 PM

Matthew said responding to me:
> >     Are 'Read and 'Write expected to work on these components?
> > 13.13.1(36.1/1) says yes. Similarly, these containers need to be task safe
> > (presuming that different objects are being operated on). Any exceptions
> > need to be noted in the wording.
>
> I don't know.  I asked the same question in my list of
> outstanding issues.  If you want containers to support T'Read and
> T'Write then that's what we'll do.

Well, it's required by the language for all predefined types. So if you don't
want it, you have to say so. But I think you do want it; writing a container to
a file and loading it into another application (or a later run of the same
application) seems useful. That's how we built the search engine for AdaIC
(words are stored in a map component, which of course has to be available to
the lookup engine).

> > Technical opinions:
> >
> >     The overall size of this proposal seems huge. It seems to me to be too
> > large an addition for the standard.
>
> Then I have to ask, seems huge compared to what?  It is certainly
> not huge compared to the C++ std library.
>
> Really, I have no idea how large or small an API you want.  All I
> can do is get the rocket off the lauching pad, and then have you
> tell me where you want to go...

Well, I'm not the ARG. I labeled this section "opinions" so as to make it clear
that these aren't "facts" that necessarily needed to be addressed.

In the case of the size issue, it simply is the case that the number of pages
of change that we can afford to put into the RM is limited. 150 pages seems
over that limit to me, but YMMV.

> >     I'm mildly confused by "Iterators". That seems to be a bizarre name for
> > what typically are called "Positions" or "Accessors". When I hear
> > "iterator", I expect it to do some iteration (as in the passive iterators,
> > which are really "automatic" iterators), rather than these, which are used
> > manually to construct an "active iterator").
>
> There's no magic here.  Just pick up any C++ book.  Or read that
> section of GoF.

Because C++ has adopted a nonsense name for a concept means that we do too? I
don't follow the logic.

> >     Are the bounded versions of things really necessary? We decided that
> > they were in the case of strings, but what has happened in practice is that
> > hardly anyone uses Bounded_Strings. Should we make that mistake again?
>
> The bounded forms are not a mistake.  In fact the bounded
> container forms correct the mistakes made by the bounded strings
> package.  (Perhaps if you had the whole spec in front of you it
> would be more obvious.  If you can wait another day or two I'll
> have the full specs, as you requested.)

The primary problem with bounded strings (performance-wise) is that equality
has to compose, and you aren't allowed to restrict instantiations to library
level. Thus, everything has to be initialized in an expensive style (you can't
use controlled types). The net effect is that they don't show any performance
improvement; the only benefit is a bit more control over memory use. Thus they
aren't used much.

Since the first two rules apply to all predefined packages in Ada, I don't see
how you're going to avoid those problems with your containers. (Other language
changes might help or hinder this, of course).

*************************************************************

From: Randy Brukardt
Sent: Thursday, September 25, 2003  7:11 PM

Oops, missed one:

Matthew said responding to me:

> > I see that the passive iterators don't provide a "Kill" (or stop)
> > parameter to the processing routine. While that clearly simplifies the
> > interface, it also means that they are rarely going to be useful, and makes
> > it much more likely that it would become necessary to rewrite them under new
> > conditions. (Most of my iterators are "Find" types, where once you find what
> > you are looking for, you can - and should - quit.)
>
> As I show in the examples, all of this is possible using active iterators.

Of course. Which was my point: why do we need passive iterators? They're
unpleasant to use, and if you do use them, a change in requirements can easily
force a change to the active form anyway. And they don't allow anything that
the active form doesn't.

*************************************************************

From: Marius Amado Alves
Sent: Thursday, September 25, 2003  7:26 PM

> >     I'm mildly confused by "Iterators". That seems to be a bizarre name for
> > what typically are called "Positions" or "Accessors". When I hear
> > "iterator", I expect it to do some iteration (as in the passive iterators,
> > which are really "automatic" iterators), rather than these, which are used
> > manually to construct an "active iterator").
>
> There's no magic here.  Just pick up any C++ book.  Or read that
> section of GoF.

"Position" is a better metaphor in general. But Matthew's interators are a kind
of special case. They have special values (Back, Front), and I think some other
properties that are also not very 'positional' (I can't recall exactly which
now.) So personally I understand calling these types iterators to distinguish
them from the more pure cases of positions.

Note the most consensual name according to the Bases is indeed Position. And no
_Type suffix. Container, Element, Position.

*************************************************************

From: Marius Amado Alves
Sent: Thursday, September 25, 2003  7:40 PM

"The primary problem with bounded strings (performance-wise) is that equality
has to compose, ..."

Please refer me to an explanation of what it means for equality to compose; an
ARM, AARM or Rat reference is ok. I'm very interested in this problem.

"and you aren't allowed to restrict instantiations to library
level..."

You mean Annex A containers cannot be controlled in any way?

*************************************************************

From: Randy Brukardt
Sent: Thursday, September 25, 2003  8:44 PM

>"The primary problem with bounded strings (performance-wise) is that
>equality has to compose, ..."

>Please refer me to an explanation of what it means for equality to compose;
>an ARM, AARM or Rat reference is ok. I'm very interested in this problem.

This was in the Corrigendum, see 4.5.2(36.1/1). Also, AI-123 (which is where
the Corrigendum comes from) explains the problem in more detail.

>"and you aren't allowed to restrict instantiations to library level..."

>You mean Annex A containers cannot be controlled in any way?

Not necessarily; it certainly is OK to have controlled components (as long as
their type is declared outside of the generic). You'd probably have to use an
access discriminant or the like to implement these.

This is discussed in AI-115, which is considered a "confirmation". (That is, it
is considered obvious that the standard says this. I don't find this so
obvious, and would have preferred that this was classified as a "ramification".
But that wouldn't change the result.)

*************************************************************

From: Matthew Heaney
Sent: Thursday, September 25, 2003  9:40 PM

That isn't a problem in my submittal, because the bounded forms are all
limited.

*************************************************************

From: Randy Brukardt
Sent: Friday, September 26, 2003  5:27 PM

Well, there were two problems there. I presume you are talking about the
equality one, which indeed doesn't apply to limited types.

But certainly the 'no tagged types derived' issue (because nesting must be
supported) applies to all types, limited or not. A limited type may make it
easier to work around the problem, but you don't get to ignore it...

*************************************************************

From: Marius Amado Alves
Sent: Friday, September 26, 2003  2:31 AM

"This was in the Corrigendum, see 4.5.2(36.1/1). Also, AI-123 (which is where
the Corrigendum comes from) explains the problem in more detail." (Randy)

Got it. Thanks. Now sorry if the next question has already been answered: can't
a standard type that is required to have a controlled behaviour (generators,
containers...) have this behaviour without having anything to do with
Ada.Finalization? After all the behaviour is implementation. An implementation
surely has absolute control over these things as many others.

*************************************************************

From: Randy Brukardt
Sent: Friday, September 26, 2003  6:01 PM

It could, but there'd be a lot of screaming and gnashing of teeth. :-) At lot
of implementors write most of their RTS in Ada (we certainly do, GNAT certainly
does, etc.) It would be extremely obnoxious if these packages couldn't be
written in standard Ada.

Speaking for Janus/Ada only, our implementation of finalization depends
completely on the no-nesting properties of controlled types. And we use the
same finalization mechanism for some memory management and for task control
(adtivation, termination, protected actions, etc.) Changes here to support
declared nested controlled types would be very disruptive.

In any event, it would make no sense to force implementors to jump through
hoops and then prevent programmers from using the enhanced feature. If we wish
to do this, we need to repeal the original restrictions, not make some packages
magic. (In that case, I'm probably out of the Ada business, but that doesn't
mean that it shouldn't be considered.)

*************************************************************

From: Matthew Heaney
Sent: Friday, September 26, 2003  6:27 PM

I don't understand.  The container type is limited and bounded.  It is not
tagged, privately or otherwise.  It does not derive from Limited_Controlled,
privately or otherwise.  What is the problem that I can't ignore?

*************************************************************

From: Randy Brukardt
Sent: Saturday, September 27, 2003  5:52 PM

Huh? These rules apply to all types, not just the bounded ones. Indeed, I can
imagine how you might manage to implement the bounded ones correctly, but I
can't see how to do the unbounded ones. They're non-limited, so predefined
equality has to work right, or the type has to be tagged. But predefined
equality can't work right (to make the container expandable will require some
sort of pointer structure). But if they're tagged, the type has to be declared
somewhere else (not in the generic). But that can't be, because of the need to
parameterize by the element. Maybe some headstand with
access_to_address_conversions will work. Yuck.

*************************************************************

From: Matthew Heaney
Sent: Monday, September 29, 2003  1:32 PM

I was referring to the bounded forms specifically.  The bounded forms
are not tagged, so they aren't controlled either.

However, the whole issue is probably moot anyway.  The ARG wants a
small-ish API, so we probably don't have the budget for bounded forms
anyway.

*************************************************************

From: Matthew Heaney
Sent: Friday, September 26, 2003  7:51 AM

>     Are 'Read and 'Write expected to work on these components?
> 13.13.1(36.1/1) says yes.

I thought about that some more.  There's no reason why the container itself
can't support the stream attributes.  The real issue is that to support
container streaming, the container element has to be streamable, too.  Which I
assume means that only streamable elements can be put in a container?  If
that's the case, then does that overly restrict the generality of the container
library?

I wasn't sure whether 13.13.2 (36.1/1) applies in the generic container case.
For example, in the case of an Unbounded_String, the element is just type
Character, which that package knows about, knows that it can stream, and
therefore there's no real reason why an Unbounded_String can't stream itself
out.

With a generic container library, the situation is different, because the only
thing the container knows about elements is what's specified in the generic
formal region.

So I don't know.  If you want container streaming, then you can have it.  You
just have to live with the consequences of requiring that container elements be
streamable, too.

*************************************************************

From: Tucker Taft
Sent: Friday, September 26, 2003  8:14 AM

All non-limited types are by definition "streamable", though
there is no particular guarantee that the result is
meaningful for types involving access types.
But clearly there is no expectation that the container
can create a meaningful 'Read/'Write if the component's
existing 'Read/'Write is not meaningful.

And note that this is not something that has to appear
in the actual package spec (or even in the wording) if
the container type is non-limited.  It is a global implementation
requirement on language-defined packages.  You only need
to mention it if you want to include an implementation
permission to *not* provide a meaningful 'Read/'Write.
(The same goes for composable equality.)

*************************************************************

From: Pascal Leroy
Sent: Friday, September 26, 2003  3:33 AM

> The overall size of this proposal seems huge. It seems to
> me to be too large an addition for the standard.

I agree with all of Randy's comments, but this one is especially
important.  Such a big proposal will have to be a separate International
Standard.  I am not saying it's a bad idea, and in fact decoupling the
schedule of the container library from that of the RM might be a good
move.  But everyone should be aware that a 150-page proposal cannot
possibly be incorporated in the existing RM or in the existing schedule
for the Amendment.  (For the sake of comparison, annexes A, B and C have
140 pages in the current RM.)

*************************************************************

From: Marius Amado Alves
Sent: Friday, September 26, 2003  4:14 AM

"Such a big proposal will have to be a separate International
Standard." (Pascal)

Will this not have the effect of "just another container library out there"?

Matthew, are you willing to reduce drastically? Say to 20 pages? (As you know I
have a lot of ideas on how to do this.)

*************************************************************

From: Tucker Taft
Sent: Friday, September 26, 2003  7:52 AM

I haven't take a look a this yet, but in terms of size,
the critical thing is that the actual package specs and the
corresponding RM wording must be manageable.  If you have
provided reams of rationale and examples, that is great,
and that could be made available as a separate document.

What the ARG really needs is concise package specs,
clear, precise RM wording, a good problem statement,
and a modest amount of high level discussion.
Clearly 150 pages is too many for the RM, and too many
for the ARG to review.  If you can get it down to
something closer to 25 pages of specs and wording,
and separate out the rest as rationale and examples,
that seems doable.  Note that we have played games
in the RM with having "Wide" versions of certain
packages without actually showing the package spec.
If you had systematic substitutions like that,
you might consider such an approach.

Another approach is to determine the minimal "foundation"
set, so that it is pretty clear if someone adopts that
foundation, there will be a strong incentive for them to
adopt the rest of the packages as needed.  We also
in the past talked about establishing some standard
generic "signatures."  If there were, say, five standard generic
signatures, that might be a good start at getting community-wide
agreement on interfaces.

In my own view, the two most important abstractions are
an extendable vector package, and a hashed mapping.
(And it would be nice to have a decent standard sort,
but that is secondary.)

*************************************************************

From: Matthew Heaney
Sent: Friday, September 26, 2003  8:16 AM

> I haven't take a look a this yet, but in terms of size,
> the critical thing is that the actual package specs and the
> corresponding RM wording must be manageable.  If you have
> provided reams of rationale and examples, that is great,
> and that could be made available as a separate document.

There are indeed "reams" of rationale and examples.  I allude to this near the
top of the document.  But it's no problem to separate them out, as Randy
suggested.


> What the ARG really needs is concise package specs,
> clear, precise RM wording, a good problem statement,
> and a modest amount of high level discussion.
> Clearly 150 pages is too many for the RM, and too many
> for the ARG to review.  If you can get it down to
> something closer to 25 pages of specs and wording,
> and separate out the rest as rationale and examples,
> that seems doable.

OK.  Now I have a number.  Let me see about that.


> Note that we have played games
> in the RM with having "Wide" versions of certain
> packages without actually showing the package spec.
> If you had systematic substitutions like that,
> you might consider such an approach.

I tried to play those tricks, but Randy wanted to see all the package specs.
So which is it?  Maybe I can make the exception just for the Wide_String case,
but it seems a shame not to play the trick for type String as well, since that
spec is otherwise so close to the non-string key map container package.


> Another approach is to determine the minimal "foundation"
> set, so that it is pretty clear if someone adopts that
> foundation, there will be a strong incentive for them to
> adopt the rest of the packages as needed.  We also
> in the past talked about establishing some standard
> generic "signatures."  If there were, say, five standard generic
> signatures, that might be a good start at getting community-wide
> agreement on interfaces.

The library is designed using static polymorphism a la Sequential_IO vs.
Direct_IO, so there is a lot of overhap among operation and parameter names.
But of course, each container is different, so each one has operations to take
advantage of that container's unique properties.


> In my own view, the two most important abstractions are
> an extendable vector package, and a hashed mapping.
> (And it would be nice to have a decent standard sort,
> but that is secondary.)

That's what you told us in Vienna, and my proposal has both extendable vectors
and hashed maps.

About the sort.  The linked list packages have a generic sort operation.  And
there are sorted versions of the sets and maps.  So this might be all you need.

It sure would be nice to have sorting available for the vectors and deques,
too.  I didn't know how rich or how lean you wanted the specs to be.  I'd
rather have it.  If you want it, then we can add it.

Another possibility is to have a generic algorithm.  The Charles library has
that:

   charles.algorithms.generic_sort

Actually, it might be nice to have a few generic algorithms in the library, but
as you and others point out we have to put a boundary somewhere, so I stopped
at containers.  Let me know what you want to do.

As I have said, we just need to get the rocket ship off the launching pad.
Then you guys can tell me where you want to go.

*************************************************************

From: Randy Brukardt
Sent: Friday, September 26, 2003  5:44 PM

> I tried to play those tricks, but Randy wanted to see all the
> package specs.  So which is it?  Maybe I can make the exception
> just for the Wide_String case, but it seems a shame not to play
> the trick for type String as well, since that spec is otherwise
> so close to the non-string key map container package.

For me, the issue is that Wide_ is really just a substitution. (Look at the
differences for Wide_Text_IO in A.11 - two long sentences). There is no
semantic difference or real difference in the specification.

You have as many as 18 'differences' in some of the specs, with different
semantics. At that point, it has gotten rather confusing. My meta-rule (which
may not correspond to the one Tuck and Bob used -- they should speak up if not)
is that you have to give the actual specification unless it is a straight
substitution on types, etc. Adding/deleting parameters and routines is a no-no.
For the semantics (wording), sharing is more allowed, but keep in mind that
many places where it was done in Ada 95 have turned into major work for the
ARG: Stream_IO (tons of problems because it really isn't quite as much like
Sequential_IO as was thought); Unbounded_Strings (a bunch of minor problems).
So I'd err on the side of duplication.

To summarize, I'd probably give the specs of everything except the Wide_
versions. Feel free to share the semantic definitions, but be careful.

*************************************************************

From: Matthew Heaney
Sent: Friday, September 26, 2003  6:30 PM

Well, the string-key forms are different from the non-string key forms in only
minor ways.  I agree that 18 differences is a lot, but that's not the case with
the string vs. non-string versions.  Let me see what I can do.

*************************************************************

From: Marius Amado Alves
Sent: Friday, September 26, 2003  9:49 AM

> If you can get it down to
> something closer to 25 pages of specs and wording... (Tuck)

> OK.  Now I have a number.  (Matt)

I had said 20 before. I guess I was leaving room for my annexes, which take
exactly 5 pages :-)

*************************************************************

From: Marius Amado Alves
Sent: Friday, September 26, 2003  2:29 PM

I have to do the reformatting that Randy asked for, which will be my project
tonight and tomorrow.  Then I will evaluate whether any pruning is necessary.

*************************************************************

From: Pascal Obry
Sent: Friday, September 26, 2003  2:30 PM


amado.alves writes:
 > "Such a big proposal will have to be a separate International
 > Standard." (Pascal)

I think it would be a mistake.

 > Will this not have the effect of "just another container library out there"?

This is what I fear too.

 > Matthew, are you willing to reduce drastically? Say to 20 pages? (As you
 > know I have a lot of ideas on how to do this.)

Is that really possible ? Such a large API in 20 pages ?

*************************************************************

From: Matthew Heaney
Sent: Friday, September 26, 2003  2:48 PM

Tucker said to shoot for about 25 pages.  After I have reformatted, I will
evaluate whether the existing specification will fit within the 25 page target.

The most productive thing you can do is read the API.  It has lots of examples,
which provide the rationale for its features and design.  As you're reading,
make a note of which containers you'd be willing to give up, should pruning be
necessary.

*************************************************************

From: Pat Rogers
Sent: Friday, September 26, 2003  10:12 AM

I_noun strenuously_adjective object_verb to_preposition use_verb
of_preposition the_definite-article "_Type" suffix_noun.

It is visually noisy and usually adds nothing helpful.  There are only a
few places where this was done in previous versions of the language.
Let's not make it worse.

*************************************************************

From: David C. Hoos
Sent: Friday, September 26, 2003  1:52 PM

I agree, but respectfully point out that "strenuously" is an adverb,
not an adjective.

*************************************************************

From: Matthew Heaney
Sent: Friday, September 26, 2003  1:54 PM

The container library is modelled on the predefined I/O packages, and hence
follows the convention used there, for types Element_Type and File_Type.

I don't consider following what's in the RM "making things worse."

*************************************************************

From: Pat Rogers
Sent: Friday, September 26, 2003  3:34 PM

For generic formal parameters the suffix makes sense.  For primary abstractions
it does not, IMHO.  But you've made my point -- there are dozens of type
declarations in the rest of the language, as a quick grep will show, and the I/O
types are the exceptions that stick out like sore thumbs.  Naming consistency is
very important and the I/O packages got it wrong.

Sure, this isn't the most important issue, but it *is* an issue.  Hence I
suggest Container_Type be named Container.  Same for any others like it, except
the generic formal parameters.

*************************************************************

From: Matthew Heaney
Sent: Friday, September 26, 2003  6:43 PM

Well, changing Container_Type to Container would be silly.  The substantive
issue (as Randy mentioned) is that if you have multiple containers declared
within the same scope (very likely), then you have to explicitly quality the
type names with the name of the package.  Your suggested naming convention
doesn't fix that.  (In fact it makes things much worse because then what do you
name the parameters?).

On the other hand, some shops insist on qualifying everying (including
--gasp!-- the equality operator), so perhaps this isn't really an issue.

If there's a change (I will do as directed by the ARG), it will be to change
Container_Type to Set_Type or Unbounded_Set, and change Iterator_Type to
Set_Iterator, for example.  That mitigates the multiple-containers-in-scope
problem.

I assume you're willing to live with the names Unbounded_Set and Set_Iterator,
so perhaps that is an acceptable compromise.

*************************************************************

From: Marius Amado Alves
Sent: Sunday, September 28, 2003  4:10 AM

>"Well, changing Container_Type to Container would be silly.  The
>substantive issue (as Randy mentioned) is that if you have multiple
>containers declared within the same scope (very likely), then you have
>to explicitly quality the type names with the name of the package.  Your
>suggested naming convention doesn't fix that.  (In fact it makes things
>much worse because then what do you name the parameters?)."

Into, From...

>"On the other hand, some shops insist on qualifying everying (including
>--gasp!-- the equality operator), so perhaps this isn't really an
>issue..."

God forbids!

>"If there's a change (I will do as directed by the ARG), it will be to
>change Container_Type to Set_Type or Unbounded_Set, and change
>Iterator_Type to Set_Iterator, for example.  That mitigates the
>multiple-containers-in-scope problem."

No. This was discussed before and it was shown that all containers
should be named the same. Dig the discussion up in ASCLWG.

*************************************************************

From: Pat Rogers
Sent: Saturday, September 27, 2003  6:54 PM

> I assume you're willing to live with the names Unbounded_Set and
> Set_Iterator, so perhaps that is an acceptable compromise.

Yes, Unbounded_Set and Set_Iterator would be very much better.

You've done a lot of very good work here.  Naming, to me, is the finishing touch
that shows the polish applied.

*************************************************************

From: Pat Rogers
Sent: Saturday, September 27, 2003  7:31 PM

Indeed I think that is the optimum (in case it was not clear for my earlier
post).

*************************************************************

From: Pat Rogers
Sent: Sunday, September 28, 2003  7:35 AM

Then please don't name them all *_Type, for the same reason we shouldn't put
"_Pkg" on the end of package names or "_function" and "_procedure" on
subprograms, etc.   As I said earlier, for the generic formal types the suffix
"_Type" makes sense because a generic formal is a metalevel entity, but for the
abstractions themselves it is just highly distracting noise.

Yes, that requires changes to the names of the formal parameters used on the
primitive operations when the formal would have the same name as the type.  So
we have to think a little about names, so what?

I assume the rationale for using the same name for all the container types is to
minimize the number of textual changes required when replacing one of the
container abstractions with another.  That's certainly a defensible position for
the ancillary types such as iterators.  An iterator is an iterator, regardless
of whether it operates on sets or queues, etc.  and can be named accordingly.

Likewise, the operation names should be the same across containers, so that
changes to the containers applied can be easily accomplished.  (See Meyer's
"Reusable Software: The Base Object-Oriented Component Libraries" using Eiffel
for a discussion.)

However, for the primary container types themselves, such a change is easily
made with any decent development tool and I haven't found it to be an issue over
the years.  Hence I suggest naming the containers for what they are (Set, Queue,
etc.) because it provides so much more information to the reader.

*************************************************************

From: Jeffery Carter
Sent: Sunday, September 28, 2003  2:44 PM

Mário Amado Alves wrote:
> No. This was discussed before and it was shown that all containers
> should be named the same. Dig the discussion up in ASCLWG.

 From my experience, this is not acceptable to the ARG. Standard 
packages must be "use friendly".

*************************************************************

From: Robert A. Duff
Sent: Monday, September 29, 2003  2:17 PM

I prefer the containers to have different names, and I don't much like
the "_Type" suffic, but I don't feel strongly about either.  Note that
if all containers have the same name, it's still "use friendly" for all
the operations, which is the main thing.  Only the type names have to be
package-qualified.

*************************************************************

From: Simon J. Wright
Sent: Monday, September 29, 2003  3:06 AM

From experience of using the Booch Components, I think it would be a bad idea to put the
form (the Unbounded) in the container type name.

Of course the BCs go to some length to ensure that the operations of the various forms
are as similar as possible (there are some additions for the Bounded form, that's all);
if that's not the case with the proposed containers it's not quite so important.
 
*************************************************************

From: Matthew Heaney
Sent: Monday, September 29, 2003  1:49 PM

It all depends on what model you use.  My model has been --and continues
to be-- the convention used by the predefined I/O packages.  That means
Key_Type, Element_Type, Container_Type, and Iterator_Type.

In any event the issue now is trying to cut down the size of the API.  I
have submitted a new (shorter) version to Randy, and when he blesses it
I'll post a summary.

Let's solve the big problems first.  Right now that means reducing the
API size, by eliminating containers and/or operations.  If the ARG
rejects the API because of its size then debates about naming
conventions will have been pointless.

*************************************************************

From: Randy Brukardt
Sent: Monday, September 29, 2003  7:40 PM

>From experience of using the Booch Components, I think it would be a bad idea
>to put the form (the Unbounded) in the container type name.

I agree with this to a point. But it still makes more sense to call a set a Set_Type (or
just Set, if you prefer) and a map a Map_Type. You aren't going to be changing from a Set
(or Vector) to a Map very often, if at all, and if you do, you probably need to change
more than just the names of the container types. Changing from the Bounded to Unbounded
form or the like is more likely.

*************************************************************

From: Simon Wright
Sent: Tuesday, September 30, 2003  8:09 AM

This is what I meant to say, sorry it wasn't clear.

*************************************************************

From: Martin Dowie
Sent: Monday, September 29, 2003  2:42 AM

>     All of the types are called "Container_Type". People who use use clauses
> will not be pleased, as fully qualified names are required if multiple
> containers are in use. Jeffrey Carter's original proposal was that way, and
> there was a consensus to change it to individual names like
> "Unbounded_List".

Having used "Charles", I'd have to say I _like_ having all the
types named the same. And, yes, even though I did have to add
the occassional qualification. I'd find it a complete pain
having to change the type names just because I've changed what
sort of container it is. Also, if you have more than one
container of the same type, you can still run into similar
problems.

The solution is to choose good names for the instances - not
to change the type names.

(Although I'd be happy with a change to "Container" rather than
"Container_Type" ;-)

*************************************************************

From: Marius Amado Alves
Sent: Thursday, September 25, 2003  6:56 PM


Sorry if it is illformatted. I'm again temporarily limited to that crapy weboutlook
thingamagig with no control over format.
 
!summary

This is a proposal for a standard container library featuring indefinite elements and
persistence, conceived as an extension to A.16.

!problem

The desire for containers of indefinite elements has been amply voiced in CLA and other
fora. The case for persistent containers has also been made. See the discussion section
for specific rationalia and more information.

For several reasons, no known finished offer of a container library for Ada meets both
these requirements, and only two (maybe three) meet one of them: ACL, BC+ (and maybe
RESOLVE/Ada, I didn't had time to check this one yet). SCOPE meets both requirements, but
it's still developing.

Also for several reasons, none of the current AI-302 alternatives meet any of these
requirements--by themselves.

!proposal

This is a proposal of a standard container library featuring indefinite elements and
persistence, for Ada 2005.

Several ARM configurations are possible for this:

Containers  Indef.elms.  Persistence
------------------------------------
Optional    Optional     Absent
Optional    Absent       Optional
Optional    Optional     Optional
Mandatory   Optional     Optional
Mandatory   Mandatory    Optional
Mandatory   Optional     Mandatory
Mandatory   Mandatory    Mandatory
...         ...          ...

"Mandatory" means on Annex A. "Optional" means on a specialized needs Annex. "Absent" means
not on the standard.

This proposal is configured as mandatory-optional-optional, with respect to wording.
Namely, it pressuposes an Ada.Containers system defined in clause A.16.

Note, however, that many different configurations are easily drawable with the same basic
elements of this proposal. For example, mandatory-mandatory-mandatory is basically
promoting Annex <PC> to A.16.<PC>, with simple adjustments to the text.

This proposal chooses AI-302-02 for A.16 when a choice is required e.g. for wording.
However, this proposal is also applicable to other choices for A.16, including AI-302-01,
which also does not supply indefinite elements or persistence.

!wording

ANNEX <IE>

Containers of Indefinite Elements

This Annex provides support for containers of indefinite elements.

[Implementation Requirements]

An implementation conforming to this Annex shall have the package Ada.Indefinite_Elements
and descendants defined in this Annex.

[Static Semantics]

The specifications of the descendants of Ada.Indefinite_Elements are a copy of the
specifications of the descendants of Ada.Containers specified in A.16, with the unique
difference that, for each generic descendant of Ada.Containers that has a definite element
formal type, the corresponding descendant of Ada.Indefinite_Elements has an indefinite
formal type in its place.

[Dynamic Semantics]

The behaviour associated with each container of Ada.Indefinite_Elements is exactly like
that defined in A.16 for the corresponding container of Ada.Containers.

[Examples]

Specification of Ada.Indefinite_Elements.Double.Unbounded:
...
generic
   type Element_Type (<>) is private;
   with function "=" (L, R : Element_Type) return Boolean is <>;
package Ada.Indefinite_Elements.Lists.Double.Unbounded
...
   -- remainder of this package exactly like that of
   -- Ada.Containers.Lists.Double.Unbounded

[Implementation Advice]

If an implementation additionally conforms to Annex <PC> then it should equate each
container package in Ada.Indefinite_Elements with the corresponding core-core container
package in Ada.Persistent_Containers.

ANNEX <PC>

Persistent Containers

This Annex provides containers of multiple degrees of persistence.

[Implementation Requirements]

An implementation conforming to this Annex shall have the package Ada.Persistent_Containers
and descendants defined in this Annex.

[Static Semantics]

The basic degrees of persistence include: core, file, name, cache. These four are called
"standard". Any other is "non-standard".

A container has elements and possibly auxiliary data e.g. nodes. In this Annex, each of
these two types of data is subject to a basic degree of persistence, in certain
combinations described later in this Annex. Each combined degree is denoted by the pair
<elements_degree>-<auxiliary_data_degree>, where each side is the degree of the indicated
type of data. Example: core-core. In Ada code, the underscore takes the place of the
hyphen.

The combined degrees made exclusively of standard basic degrees are called "standard".
The standard combined degrees, together with any other degrees provided by the
implementation, form the "supported" degrees.

The descendants of Ada.Persistent_Containers are like the descendants of
Ada.Indefinite_Elements (Annex <Indefinite_Elements>) with the difference that, for each
container package in Ada.Indefinite_Elements, its corresponding place in
Ada.Persistent_Containers is occupied by a set of persistent container packages, one for
each supported persistence degree.

The name of each persistent container package is the concatenation of the following three
parts, in order:
- the name of the corresponding package in Ada.Indefinite_Elements
- an underscore
- the name of the persistence degree of the persistent container being named.

The specification of each persistent container package of Ada.Persistent_Containers is a
copy of the specification of the corresponding container package of Ada.Indefinite_Elements,
with the difference that the former has additional entities specific to its persistence
degree:
- a file degree adds the generic parameter
    [Aux_]File : in out Ada.Streams.Stream_IO.File_Type;
- a name or cache degree adds the generic parameter
    [Aux_]Name : in String;
- a file, name or cache degree adds the public entity
    procedure Flush (Container : in out Container_Type);
- a core degree does not add any entity.

The "Aux_" prefix refers to auxiliary data. An name without it refers to elements.

Naturally, any required "with" clause is also added to the specification.

The following combined degrees are supported:
core-core  file-core   name-core   cache-core
           file-file   name-file   cache-file
           file-name   name-name   cache-name  
           file-cache  name-cache  cache-cache

[NOTES]
An implementation may support any of the missing standard combined degrees, namely
core-file, core-name, core-cache.

[Examples]

Excerpt of the specification of a persistent container package:
with Ada.Streams.Stream_IO;
...
generic
   type Element_Type (<>) is private;
   with function "=" (L, R : Element_Type) return Boolean is <>;
   File : in out Ada.Streams.Stream_IO.File_Type;
   Aux_File : in out Ada.Streams.Stream_IO.File_Type;
package Ada.Persistent_Containers.Lists.Double.Unbounded_File_File is
...
   procedure Flush (Container : in out Container_Type);
...
   -- the remainder of the specification is like that of
   -- Ada.Indefinite_Elements.Lists.Double.Unbounded

[Legality Rules]

File, name and cache degrees for elements require the actual element type have stream
operations Input and Output.

[Dynamic Semantics]

Core: data in main memory at all times.
File: data in the specified stream file.
Name: like file, but file specified by name. More below.
Cache: like name, except data in main memory for the life of the container.

For a name or cache degree, the required file is open or created upon creation of the
container. If the name is the null string, a temporary file is created. If the name is
not null and the file exists, it is open. If the name is not null and the file does not
exist, it is created anew, and the container is initialized with zero elements.

Except for any storage medium specific aspect related to the persistence degree of a
persistent container <X>, as defined in this Annex, the behaviour of <X> is exactly like
that of the corresponding container in Annex <IE>.

[NOTES]

A core-core container behaves exactly as its corresponding container in
Ada.Indefinite_Elements.

Elements and auxiliary data may share a file. This happens for a file-file, name-name,
name-cache or cache-name container, when the corresponding actual parameters specify
the same file.

For a file degree, if the specified file is not open, an I/O error is bound to happen upon
certain container operations. So it is wise to open the file before instantiating the
container.

!discussion

<<If I ask a student whether her design is as good as Chartres, she often smiles tolerantly
at me as if to say, "Of course not, that isnt't what I am trying to do.... I could never do
that." Then, I express my disagreement, and tell her: "That standard *must* be our
standard. If you are going to be a builder, no other standard is worthwhile.">>

-- Cristopher Alexander, Foreword to [Gabriel 1996]

1. BRIEF DISCUSSION OF INDEFINITE ELEMENTS

By "indefinite elements" we mean containers of elements of indefinite type.

The motivation for indefinite elements finds ample support in the community. The objectives
include:

- class-wide programming (containers of class-wide elements)
- containers of unconstrained elements, e.g. String
- pointerless programming, a.k.a. value semantics
- containers of containers

Indefinite elements are even *required* for some situations. For example, consider a
recursive element type T, namely one that must refer to containers of elements of type T.
This cannot be done strictly within the bounds of A.16. The proof-of-concept implementation
of Annex <PC> has an example of this situation.

2. BRIEF DISCUSSION OF PERSISTENCE

By "persistence" we mean multiple degrees of actual persistence, see Annex <PC>.

The motivation for persistence is well known. It includes:
- long term persistence is often required
- some large elements should be directly stored on file, e.g. pictures

The current proposal makes PC dependent on IE. The reasons for this include the finding
that at least on sensible way of implementing PC requires IE, namely the already referenced
proof-of-concept implementation.

The motivation for persistent containers in Ada 2005 specifically includes the following
vision: Ada already has excellent support for concurrency and distributed systems. An Ada
2005 with PC will make it the absolute language of choice for database systems, or systems
that require databaseing, e.g. complex web systems. The actual choice of Ada for such
systems is bound to increase their quality, and, properly advertised, will boost the
visibility and credibility of the language.

!example

!corrigendum
!ACATS test
(To be done. There are already test programs for proof-of-concept or reference
implementations. A proper test plan might depend on AI-302.)
!appendix

*************************************************************

From: Martin Krischik
Sent: Friday, September 26, 2003  3:44 AM

> Ok. Here's my first try then. Sorry if it is illformatted. I'm again
> temporarily limited to that crapy weboutlook thingamagig with no control
> over format.
> ACL, BC+ (and maybe RESOLVE/Ada, I didn't had time to check this one yet).
> SCOPE meets both requirements, but it's still developing. Also for several

Ok, BC is problably the Booch Components but what is ACL and why BC+?

> The specifications of the descendants of Ada.Indefinite_Elements are a copy
> of the specifications of the descendants of Ada.Containers specified in

This has problably been discussed before but why the naming difference between
Containers  and Indefinite_Elements. should if be not either Elements  and 
Indefinite_Elements or Containers  and Indefinite_Containers?

Sorry for asking but I am new here and I feel that both questions should be 
answered inside the proposal.

*************************************************************

From: Marius Amado Alves
Sent: Sunday, September 28, 2003  4:09 AM

> Ok, BC is problably the Booch Components but what is ACL and why BC+?
(Martin K.)

Sorry a bibliography section is missing yet. My Bases document has some
references:

115  BC
     Ada 95 Booch Components. -- (Software and associated
     documents maintained by Simon Wright.)
       http://www.pogner.demon.co.uk/components/bc/

116  Bond & Barnes 2000
     Library Packages for Ada 95 / Mark Stephen Bond ;
     supervised by Dr. Alan Barnes. -- CS380-IP Final Year
     Individual Project Report, B.Sc. Computing Science, School
     of Engineering and Applied Science, Aston University,
     Birmingham ; May 2000. -- (Persistent extension of ^BC.)
       http://www.pogner.demon.co.uk/components/bc/contrib/bond/

BC+ is shorthand for 116 above.

ACL is your's AdaCL, in www.ada.krischik.com. It is not in any
document's bibliography yet.

* I take the change to ask instructions from the ARG people on how to
update the documents I have submitted before with these and other
information. *

*************************************************************

From: Marius Amado Alves
Sent: Sunday, September 28, 2003  5:01 AM

> This has problably been discussed before but why the naming 
> difference between Containers  and Indefinite_Elements. 
> should if be not either Elements  and 
> Indefinite_Elements or Containers  and Indefinite_Containers?

The container type is not indefinite (it is a definite,
pass-by-reference type). It is the element type that is indefinite. The
formally correct name would be
Containers_Of_Elements_Of_Indefinite_Type. Clearly an abbreviation is
desirable. Considering the pattern

  [Containers_Of_]Indefinite_Elements

I have opted for dropping the prefix. Reasons:

- long form still a bit too long

- "elements" entail "container" (and vice versa), so the long form has
some redundancy

- the short form has two words, pairing well with the companion
(alternate?) system Persistent_Containers (Annex <PC>)

*************************************************************

From: Jeffrey Carter
Sent: Sunday, September 28, 2003  2:48 PM

Perhaps more desirable is to have these be children of Ada.Containers: 
Ada.Containers.Indefinite_Elements and Ada.Containers.Persistent_Elements.

*************************************************************

From: Marius Amado Alves
Sent: Sunday, September 28, 2003  4:09 AM

Could be. But then it should be simply

  Ada.Containers.Persistent

because the elements are not the only data subject to persistence there
(auxiliary data e.g. nodes are also, see Annex <PC>).

Also, note there is a 'contains' relation between

  Persistent_Containers > Indefinite_Elements > Containers

w.r.t actual element type and persistence degree, i.e.
Indefinite_Elements can actually replace Containers, and core-core
persistent containers equate Indefinite_Elements (and so can also
replace Containers). This opens the possibility of allowing (or
requiring) an implementation conforming to Annex <IE> or <PC> to simply
'overwrite' Containers with the supported Annex. This implementation
permission/requirement is not on my original proposal, but I hereby
submit it for appreciation. Note a conforming implementation should then
be required to offer exactly the same efficiency (complexity?, metrics?)
for core-core persistent containers of *definite* *actual* element types
as (it would) for Containers. I expect this to be doable because there
is even an 'Definite attribute already in the language, so I expect
implementations to be able to choose the best actual implementation of
the instantiated package at compile or link time, and doing that by not
going (too much?) outside the language. *This scheme would eliminate the
need for any extra package branches altogether.*

*************************************************************

From: Martin Krischik
Sent: Tuesday, September 30, 2003  4:55 AM

> Perhaps more desirable is to have these be children of Ada.Containers:
> Ada.Containers.Indefinite_Elements and Ada.Containers.Persistent_Elements.

But symetry would then demand Ada.Containers.Definite_Elements wich was my 
original point.

*************************************************************

From: Marius Amado Alves
Sent: Monday, September 29, 2003  9:38 AM

PROBLEM

Using a container library like AI-302, how does one instantiate a
recursive container R, namely one whose elements refer to R containers?
(The terminal condition can the empty container.)

SOLUTIONS

I have three tentative solutions to this problem, sketched below.
Solutions 1 and 2 are simple but kind of dirty, as they go outside the
type system. Solution 3 is more the Ada way, but sounds too complicated.
Some solutions require redesign of the library. I'm looking for a "best
of both worlds" solution, namely simple *and* safe. Is such a solution
likely to be found? Thanks.

SOLUTION 1

Instatiate a container package R with a class wide element type e.g.
Root_Element'Class (this requires indefinite elements e.g. Annex <IE>).
Declare the 'actual' element type as an extension e.g.

type Recursive_Element is new Element_Root with
  record
    C : R.Container;
  end record;

Then use unchecked conversion between Recursive_Element and R.Element =
Root_Element'Class. (Does unchecked conversion guarantees the result
here? It seems to do so with GNAT. But ARM-wise? Is there an alternative
to unchecked conversion here?)

SOLUTION 2

Provide conversion facilities between any container type and a 'common'
type e.g.  Integer. Then declare the 'pointer' component using the
common type e.g.

type Recursive_Element is
  record
    C : Integer;
  end record;

and use the conversion facilites appropriately. (This is ugly because
we're really out of the type system--but it has precedents in the use of
access/address/integer conversions in some idioms.)

SOLUTION 3

Have the container types descend from a common root e.g. Container_Root.
Then declare the pointer component as the corresponding access to
class-wide type. (I fear this puts unwarranted restrictions on the
classes of containers and/or elements--on top of complicating the life
of the user.)

*************************************************************

From: Matthew Heaney
Sent: Monday, September 29, 2003  2:28 PM

> Using a container library like AI-302, how does one 
> instantiate a recursive container R, namely one whose 
> elements refer to R containers? (The terminal condition can 
> the empty container.)

You can do that by first instantiating a container on an element type, and then
instantiating the container again, using as the element the container you created in the
first instantiation.

The terminal condition is just a non-container element.

To make this work you need to have a variable view of the element (here, an element that
is a container).  You can get a variable view of an element by instantiating the
Generic_Element function.

It would be nice if Ada had something akin to T&.  Iterators in C++ are declared this way:

   T& operator*();
   const T& operator*() const;

In Ada you can only do this:

   function To_Access (I : IT) return ET_Access;
   function To_Constant_Access (I : IT) return ET_Constant_Access;

I wish the language had some kind of user-defined selector function to return a reference
to a component, but the functions above get the job done.

*************************************************************

From: Mario Amado Alves
Sent: Monday, September 29, 2003  4:31 PM

Hmm... it's not exactly recursive because the elements of the second
container type R do not refer exactly to R containers--they refer to the
first container type instead.

*************************************************************

From: Ed Schonberg
Sent: Monday, September 29, 2003  5:24 PM

Full recursion would be equivalent to set theory with unrestricted applications
of the power set operator, to create sets of sets of.... This cannot be done
with a statically typed language, you have to program each level explicitly.
What Matthew proposes is the first step in this infinite ladder of types.

*************************************************************

From: Mario Amado Alves
Sent: Tuesday, September 30, 2003  3:39 AM

I don't see this set theory here. Ada types allows recursion (via
pointers to an incomplete type). My solutions to the problem provide
recursion. Both Ada and my solutions provide *exact* recursion, meaning
the type refers to itself, really itself (although indirectly).
Matthew's solution does not provide this.


*************************************************************

From: Ed Schonberg
Sent: Tuesday, September 30, 2003  4:31 PM

If you have reference semantics, then of course a conventional
tree is all you need.

If all you have is containers without any atomic components, you do
have pure set theory and you can represent 0 with {}, n+1 with {n}, and
so on, but that is probably NOT the purpose of these containers :-)!
 
If you want value semantics, atomic components at the bottom, and
homogeneous containers I don't see how you can avoid programming each
level by hand.

*************************************************************

From: Nick Roberts
Sent: Tuesday, September 30, 2003  3:43 PM

[Regarding recursive container containment]

Mário,

As far as I'm concerned, the most likely way to do this is to declare:
   (1) an incomplete type T1;
   (2) an access type A1 for which T1 is the designated type;
   (3) a record type T2 which includes a component of type A1;
   (4) a container type T3 with T2 as its element type;
   (5) a completion of T1 as a derivation of T3.

For example:

   type Contact_List; -- (1)
   type Contact_List_Ref is access Contact_List; -- (2)

   type Contact_Descriptor is
      record
         ...
         Friends: Contact_List_Ref;
         ...
      end record; -- (3)

   package Contact_Lists is
new Ada.Containers.Lists.Double.Unbounded(Contact_Descriptor); -- (4)
   use Contact_Lists; -- to see non-operation and iterator-only declarations

   type Contact_List is new Contact_Lists.Container_Type; -- (5)

I'm not certain that this works perfectly, and I'm not sure it's the best
possible way.

Would we have difficulty instantiating Generic_Sort and Generic_Merge?

*************************************************************

From: Mario Amado Alves
Sent: Monday, September 29, 2003  4:31 PM

Hmm... it's not exactly recursive because the elements of the second
container type R do not refer exactly to R containers--they refer to the
first container type instead.

*************************************************************

From: Mario Amado Alves
Sent: Thursday, October  2, 2003  1:06 PM

"If you have reference semantics, then of course a conventional tree is all you need." (Ed)

That is indeed the case. AI-302 containers are by-reference types.

*************************************************************

From: Mario Amado Alves
Sent: Thursday, October  2, 2003  1:08 PM

> As far as I'm concerned, the most likely way to do this is to declare:
>    (1) an incomplete type T1;
>    (2) an access type A1 for which T1 is the designated type;
>    (3) a record type T2 which includes a component of type A1;
>    (4) a container type T3 with T2 as its element type;
>    (5) a completion of T1 as a derivation of T3.
> 
> I'm not certain that this works perfectly, and I'm not sure it's the 
> best possible way.

It's a nice way. The Ada way. Access types. Thanks. I'll rewrite what I
have this way and see if I find any problems. (The only 'problem' I see
already is merely of style: because containers are already by-reference,
access types to containers bring two levels of indirection--which is ugly
to us pointerless programming types.)

*************************************************************

From: Tucker Taft
Sent: Monday, September 29, 2003  10:34 PM

I wonder about the complexity vs. value of multimaps
and multisets.  It seems easy enough to use maps and sets,
and make the associated value itself be a container
(e.g. a linked list).  I realize there might be some
small additional cost, but in my experience, the
typical number of uses of multisets/multimaps is an order
of magnitude smaller than the number of uses of sets/maps,
while the implementation complexity of multisets/multimaps
if you are trying to do them without the level of indirection
implied by a set of lists is significant (and rarely
worth the trouble).

*************************************************************

From: Matthew Heaney
Sent: Tuesday, September 30, 2003  9:47 AM

In the multimap case, it is indeed true you could implement it as a map
whose element subtype is a linked list.  With that in mind, my latest
proposal has 5 containers:

vector
linked list
hashed map with string-key forms too
sorted set
sorted multiset

There are only unbounded forms.  If we take away the (sorted) multiset,
that would reduce it to 4 containers:

vector
linked list
hashed map et al
sorted set

The case for implementing a multiset as a set with a linked-list element
is harder to argue, because set elements are in general nonmutable.  To
modify a set element, the API provides the Unchecked_Modification
operation, which was named that way to put the fear of god into anyone
who tried to use it.  

If we intend to use a sorted set as a multiset, then we're saying that
set element modification is normal.  In that case we should probably
move the Unchecked_Modification operation back up into the parent, and
give the operation a less scary name.  Alternatively, we could provide a
sorted map.

> I realize there might be some
> small additional cost, but in my experience, the
> typical number of uses of multisets/multimaps is an order
> of magnitude smaller than the number of uses of sets/maps, 
> while the implementation complexity of multisets/multimaps if 
> you are trying to do them without the level of indirection 
> implied by a set of lists is significant (and rarely worth 
> the trouble).

In the case of Charles, all the sorted maps, multimaps, sets, and
multisets use the same red-black tree type.  There are only minor
differences between the unique-key and multiple-key forms.  (Actually,
that's true for the hashed forms as well.)  

The only salient difference between a single-key container and a
multi-key container is the behavior of insert.  In the former case
insertion is conditional, and in latter case insertion is unconditional.
The low-level tree type has both kinds of insertion operations, which
are called as appropriate by the high-level container abstraction.

*************************************************************

From: Nick Roberts
Sent: Tuesday, September 30, 2003  4:01 PM

> I wonder about the complexity vs. value of multimaps
> and multisets.  It seems easy enough to use maps and sets,
> and make the associated value itself be a container
> (e.g. a linked list).  ...

I think I'd agree that the multimaps and multisets could be done without,
but I'm alarmed about the idea of reducing the number of containers or
operations too much. Surely we should not set some arbitrary limit (e.g. 20
or 25 pages) which could cause some really useful feature to be discarded.
Please, oh please, if Matt ends up with 26 or 27 pages the result should be
reviewed and possibly considered acceptable!

Incidentally, on reading through AI-302 (02), I'm coming to like the
proposal. My concerns about this design (Charles) were always relatively
minor, and I'd much rather that some container library were adopted, rather
than none at all. I think it's size is mitigated by its orthogonality, and I
think that the size of the perceived interface is more important than the
amount of code or the size of an implementation.

As for the composition problems, couldn't the container types be made
visibly controlled? I don't much like this idea, but it might be the lesser
of two evils.

Also, should I toss in a proposal for some trivial utility generic
procedures and functions (Sort_Array, Reverse_Array, that sort of thing)?

*************************************************************

From: Mario Amado Alves
Sent: Tuesday, September 30, 2003  4:15 AM

I have found (uni? mono?)elementary containers (containers of only one
element) useful in many situations.

generic
  type Element (<>) is private;
package Ada.Containers.Elementary is
  type Container is private;
  procedure Put (C : in out Container; E : in Element);
  function Get (C : Container) return Element;
end;

It is an incredibily simple (and short :-) abstraction but very useful.
It provides pointerless programming. With Annex <PC>, it provides a
simple way to save/load one-of-a-kind objects. I have implemented the IE
and PC extensions to AI-302-02 using elementary containers to much
advantage (see below; more details on request). I propose elementary
containers be added to AI-302, right on clause A.16. Alternatively, on
Annex <IE>.

The spec above might be too short. Here's how a real signature looks
like:

  generic
    type Element (<>) is private;
    type Element_Ptr is access all Element;
    type Container is private;
    with procedure Put (C : in out Container; E : Element) is <>;
    with function Put (E : Element) return Container is <>;
    with function Get (C : Container) return Element is <>;
    with procedure Delete (C : in out Container) is <>;
    with function Access_Of (C : Container) return Element_Ptr is <>;
    with function "=" (L, R : Container) return Boolean is <>;
    with procedure Overwrite (C : Container; E : Element) is <>;
    with function Img (C : Container) return String is <>;
  package Signature is end;

This is from an adaptation of SCOPE to Charles used to make
proof-of-concept implementations for Annexes <IE> and <PC>. It is long,
but: Element_Ptr, Access_Of and "=" were specifically added for Charles;
Img was added for instrumentation; Overwrite is also kind of
implementation-oriented, and can be dropped in the right context; Delete
can also be excused, if we're happy with the container doing that on
finalization.

So a more realistic spec might be

generic
  type Element (<>) is private;
package Ada.Containers.Elementary is
  type Container is private;
  procedure Put (C : in out Container; E : in Element);
  function Put (E : Element) return Container;
  function Get (C : Container) return Element;
  procedure Delete (C : Container);
end;

*************************************************************



Questions? Ask the ACAA Technical Agent