A container library for C

Jacob Navia




Figure



Contents

    1.1  Design goals
        1.1.1  Error analysis
        1.1.2  Full feature set
        1.1.3  Abstraction
        1.1.4  Performance
    1.2  How the functions are specified in this document.
    2.1  Containers
    2.2  The two types of interface
    2.3  The interface concept
    2.4  Error handling
        2.4.1  Return code
    2.5  Naming conventions
        2.5.1  Container Names
        2.5.2  Interface object name
    2.6  The different containers
        2.6.1  Single and double linked lists
        2.6.2  Flexible arrays (vector)
        2.6.3  ValArray: Arrays of numbers
        2.6.4  String collection
        2.6.5  Bit-string
        2.6.6  Dictionary
        2.6.7  Hash Table
        2.6.8  AVL trees
        2.6.9  Scapegoat trees
        2.6.10  Bloom Filter
        2.6.11  Queue
        2.6.12  Deque
        2.6.13  Buffers
        2.6.14  Priority Queues
        2.6.15  At a glance
    2.7  Types used by the library
        2.7.1  CompareInfo
        2.7.2  CompareFunction
        2.7.3  Save function
        2.7.4  Read function
        2.7.5  Error function
        2.7.6  Destructor function
    3.1  Creation of a container: Create
    3.2  Destruction of a container: Clear and Finalize
        3.2.1  Other creation functions
    3.3  Adding an element to a container: Add and AddRange
    3.4  Removing elements from a container
        3.4.1  Using an element as key
        3.4.2  Removing at a given position
        3.4.3  Using a container as a stack
        3.4.4  Batch removing
    3.5  Retrieving elements
    3.6  Using masks
    3.7  Sorting a sequential container: Sort
    3.8  Copying a container: Copy
    3.9  Saving and loading a container to or from disk: Save and Load
    3.10  Inserting a container into another
        3.10.1  Sequential containers
        3.10.2  Associative containers
    3.11  Replace an element with another
        3.11.1  Sequential containers: ReplaceAt
        3.11.2  Associative containers: Replace
    3.12  Looping through all elements of a container
        3.12.1  Using a simple loop to iterate a container
        3.12.2  Using the Apply function.
        3.12.3  Using iterators
    3.13  Setting and retrieving the state: GetFlags and SetFlags
    3.14  Retrieving the number of elements stored: Size
    3.15  Sizes
        3.15.1  Sizeof
        3.15.2  SizeofIterator
    3.16  Memory management
        3.16.1  Memory manager objects
        3.16.2  Pooled memory management
        3.16.3  Heap of same size objects
        3.16.4  Garbage collection
        3.16.5  Multi-threading
    3.17  At a glance
    4.1  Masks
        4.1.1  The interface
        4.1.2  The API
    4.2  Memory management
        4.2.1  The default memory manager
        4.2.2  The Heap interface: iHeap
    4.3  Pooled memory interface: iPool
    4.4  Error handling Interface: iError
    4.5  The iterator interface
        4.5.1  The interface
    4.6  The observer interface
        4.6.1  Caveats
        4.6.2  The interface
        4.6.3  Notifications messages
    5.1  The List interfaces: iList, iDlist
        5.1.1  General remarks
        5.1.2  Specializations of linked lists
    5.2  Double linked lists: iDlist
    5.3  The Vector interface: iVector
        5.3.1  The interface
        5.3.2  The API
    5.4  The bit-string container: iBitString
        5.4.1  The interface
        5.4.2  API
    5.5  The string collection container: istrCollection, iWstrCollection
        5.5.1  The interface
        5.5.2  API
    5.6  The dictionary container: iDictionary
        5.6.1  The dictionary interface
        5.6.2  The API
    5.7  The TreeMap interface: iTreeMap
        5.7.1  The interface
    5.8  Hash Table: iHashTable
        5.8.1  The interface
        5.8.2  The API
    5.9  Queues: iQueue
        5.9.1  Interface
        5.9.2  The API
    5.10  Deque: iDeque
        5.10.1  Interface
    5.11  Priority queues
        5.11.1  Interface
        5.11.2  A complete example
        5.11.3  The API
    5.12  Bloom filters
        5.12.1  The interface: iBloomFilter
        5.12.2  The API
    5.13  Value arrays
        5.13.1  Operations
        5.13.2  Slices and masks
        5.13.3  The interface
    5.14  Buffers
        5.14.1  Stream buffers
        5.14.2  Circular buffers
    5.15  The generic interfaces
        5.15.1  Generic containers
        5.15.2  Sequential containers
        5.15.3  Associative containers
    6.1  Adding conversions between containers
    6.2  Infinite arrays
        6.2.1  Zero extensible arrays
        6.2.2  Arrays extensible by insert
        6.2.3  Pitfalls
    7.1  Unique
    7.2  Paste
    7.3  Mapcar
    8.1  The different source files
        8.1.1  Building the software
    8.2  Partitioning
    8.3  Data structures
        8.3.1  The generic part
        8.3.2  Lists
        8.3.3  Source files
        8.3.4  Double linked lists
        8.3.5  Vector
        8.3.6  Dictionary
        8.3.7  String collection
        8.3.8  Masks
        8.3.9  Bit strings
        8.3.10  The iterator implementation
        8.3.11  The timestamp field
    8.4  The code
        8.4.1  List
        8.4.2  Queues
        8.4.3  The dictionary
        8.4.4  The bloom filter
        8.4.5  Debugging malloc
        8.4.6  The observer interface
        8.4.7  ValArrays
    9.1  Pre processing a generic file
    9.2  Using the pre-processor
Index
*

Chapter 1
Introduction

The objective of this proposal is to standardize the usage of common data structures within the context of the C language. The existence of a common standard interface for lists, hash tables, flexible arrays, and other containers has several advantages:
Figure
The big innovation of C in the eighties was its standard library, that made input/output portable across machines and implementations. The container library would replicate again that idea, at a higher level.
The specifications presented here are completely scoped by the C99 specifications, and can be implemented even in compilers that do not implement C99 and remained within the C94 context. No language extensions are needed nor any are proposed.
The interfaces proposed try to present complete packages, i.e. interfaces with all the necessary functions to allow the widest usage: Serialization, searching, and many other functionalities are included in the proposed standard to allow for maximum code portability. It can be argued that this makes for "fat" containers, but if you read carefully you will notice that many things can be left out in systems that run in low memory or with feeble computing power.
This documentation is composed of several parts:
  1. An introductory part where the general lines of the library are explained.
  2. A specifications part where each function of the library is fully specified. This is the proposal for the next C standard.
  3. An ëxamples" part that shows the uses of the library and allows you to have a better idea of how the usage of the library looks like.
  4. An implementation part where the code of the sample implementation is discussed. This is designed as a guide for implementors to give them a basis to start with.

1  Design goals

1.1  Error analysis

It has been a tradition in C to place raw performance as the most important quality of specifications. To follow this sacred cow, C specifications ignored any error analysis arguing that any specification of failure modes would damage "performance". No matter that raw machine performance increased by several orders of magnitude, the cost of a check for NULL was always "too expensive" to afford.
This kind of mental framework was described by one of the people in the discussion group "comp.lang.c++" as follows:2
In C++, the program is responsible for ensuring that all parameters to the standard library functions are valid, not only the third parameter of std::mismatch(). For example, also the first range for std:mismatch() must be valid, one may not pass a start iterator from one container and end iterator from another, for example. However, STL does not guarantee any protection against such errors, this is just UB.
These specifications try to break away from that frame of thought. Each function specifies a minimal subset of failure modes as a consequence of its error analysis. This allows user code to:
At the same time, the mandatory error checking consists mainly of checks that can be implemented with a few integer comparisons. For instance a check for zero is a single instruction in most processors. If implemented correctly the conditional jump after the comparison with zero is not taken in the normal case and correctly predicted by the processor. This means that the pipeline is not disturbed and the cost for the whole operation is much less than a cycle.
Why is error analysis an essential part of any program specifications?
Because mistakes are a fact of life. Good programmers are good most of the time only. Even very good programmers do make mistakes3. Software must be prepared to cope with this fact in an orderly fashion because if failure modes are not specified they have catastrophic consequences and lead to brittle software that crashes randomly.
Note that error analysis is not error handling. Error handling is taking an action after an error, a task only the application can do. What the library can do is to establish a framework where a user defined procedure receives enough information about the specific problem at hand.
Error analysis means that for each function and each API: The library provides hooks for the users that can control each step and provide functions that can do the error handling, for instance logging the error and jumping to a pre-established recovery point.

1.2  Full feature set

Another design goal is to offer to the user a full feature set, complete with serializing, iterators, search, read-only containers and all the features needed in most situations. Other features are planned for later like multi-threading support. The objective here is to avoid incompatible and non portable extensions because some essential feature is missing.

1.3  Abstraction

The library is designed with the possibility of implementing abstraction like serial and associative containers that allow software to treat several containers in a way that abstract most of their features, improving code reuse by allowing to implement algorithms for a class of objects. This is specially true in the iterators feature.
It can be argued that the C language lacks many of the abstractions constructs of other languages like templates, inheritance, and many others. All that is true, but the objective of this proposal is to show that those constructs are just an aid to developing abstractions, an aid that is paid in added complexity for the resulting language, and in a limitation of what is feasible within a given framework. Since C has no framework, no preferred inheritance model, it is possible to create abstractions that are quite unconstrained: there is no framework precisely.

1.4  Performance

Even with all the tests, the performance of the library has been maintained at a high level compared to similar libraries in other languages. The performance should improve if standardized because compiler writers could specialize their optimizations targeting this code.

2  How the functions are specified in this document.

The specifications part of the proposal uses the same building blocks for each of the functions proposed.
Name
The name of the function. Note that when using this name, the container interface should be always before: iList.Add, iDictionary.Add, etc.
The name is followed by the prototype defined as a function pointer. For the function Add of the container List we have
    int (*Add)(List *list,const void *data);

This means that Add is a function pointer in the interface iList. It would be used as: iList.Add(list,data).
Errors:
The minimal set of errors that can appear during the execution of the function is listed. Each implementation is free to add implementation specific errors to this list. Note that how the library behaves after an error is defined by the current error function in the container (if any), then by the behavior of the error function in the iError interface. This can be changed by the user by using the iError interface.
Returns: The return value of the operation. Normally, negative values are error codes, positive values means success, and zero means non fatal errors, more in the sense of a warning.

Chapter 2
Overview

1  Containers

In the context of this library, a container is a data structure used to organize data within a single logical object that allows for adding, searching and removing data. In most containers the data is not further specified, but the library assumes that all elements of a container have the same type. The data can be anything, images, numbers, text, whatever. The only thing that the container knows is the size of the data, if we store a series of objects of the same size, or its address, if we store objects of different sizes. In the later case we store just a pointer in the container4.
A special kind of containers, ValArrays, contain the basic types of the C language and the library treats them specially. There is one ValArray for each elementary type. For character strings they are stored in ßtring collections", a term borrowed from the C# language.
Each container has a way of iterating through all its elements by using an ïterator" auxiliary object, that returns each stored object in sequence. In sequential containers you can also iterate using an index variable, what can be cheaper than using iterators for arrays but very expensive in lists.
All objects stored by the library are copied into the library, and the library is responsible for the management of the associated storage. If you do not want this, just store a pointer to the data and manage the data yourself.
A container has a set of functions for accessing the elements it stores, called its interface. This object is a collection of function pointers that provide the functionality of the container. The interfaces are stored in writable memory and the user can, at run time, change the behavior of a class of containers by changing the function pointer. This operation is called ßubclassing" in this document.
Subclassing allows the user to modify the behavior of a container, maybe adding some functionality, without having to write all the container from scratch. The user of the library can: Subclassing adds enormous flexibility to this design, since it makes possible to add functionality in a transparent way.
Using the organization of the data as a classification criteria we have basically two different kinds of containers
A sequential container is organized in a linear order. We have a sequence starting at index zero up to the number of elements stored. Data items can be retrieved by index, and it makes sense to speak of a "next" and a "previous" element.
Sequential containers can be contiguous (arrays) or disjoint (lists). In the first case access is very fast since it implies multiplying the index by the size of each element to get to any position in the data. In the second case access the nth element can be a lengthy operation since the chain of "next" or "previous" pointers must be followed for each access to a given position.
An associative container stores an object divided in two parts: a key, that is used as a token for the data, and the data itself. It associates key/value pairs. Speed of access is fast, but not linear, and can degrade as new items are stored in it.
Statistical containers are containers that return the probability of an item being found in them. See the bloom filter as an example.
In all cases, we have some basic properties of an abstract container that are common to all of them.
Managing a sequence involves trade offs what performance is concerned. If the usage will involve frequent insertion and deletion of objects you will prefer a container that handles those operations in constant time: the time to add or delete an object doesn't increase with the number of elements in the container. Such a container will be unlikely to provide also access to a given element in constant time. Access is likely to be much slower, and what you gain in flexibility you loose in another dimension. It is the user of the library, the programmer, that decides what container fits best the intended usage.
Since usage patterns change, however, the library tries to ensure that you can change the container you are using with minimal effort. If at the beginning of an application a list looked like a good solution but later an array, that provides constant time access is better suited, you can change the type of container without changing every line that uses it. The common vocabulary of the library makes this possible.

2  The two types of interface

The library proposes two types of access: The functions to call, their names, etc, are the same in both approaches with one important exception: the templated approach needs an object instead of a void * to the data as parameter, and returns an object instead of a void pointer to the data.
For instance, when writing for the generic interface you use:
double MyDouble;
List *myList;
myList = iList.Create(sizeof(double));
// ...
iList.Add(myList,&myDouble);

For the templated interface you use:
double MyDouble;
doubleList *myList;
myList = idoubleList.Create();
// ...
idoubleList.Add(myList,myDouble);

Naming rules allow the user of the templated library to easily add the required names to its program and use them. The naming rules are explained further down.
The drawback of the second approach is a bigger complexity of the compilation process. When using a template container the user must:

3  The interface concept

Each container is defined by its interface, i.e. the table of functions it supports. For each interface, its name is composed of a lower case ï" followed by the container name: iList, iVector, istrCollection, etc.
Each function of the interface receives always the container as its first argument. Obviously, the big exception is the creation function, that receives various arguments depending of which container or from what input, the container is to be created.
For each container interface a global object exists that allows direct access to the function table without the need of creating a container to access it.
This interface allows for simple access to each container using a very similar vocabulary:
iList.Add(list,object);
istrCollection.Add(strcol,object);

The objects stored in a container have always the same size. When storing objects of different sizes just store a pointer to the objects, since pointers have always the same size.

4  Error handling

This specification describes the basic error handling that each function of the library must do. Other errors can appear in different implementations.
Error handling has three different phases:
  1. Detection. All library functions detect blatantly wrong arguments, for instance a  NULL pointer when an object is expected, or arguments out of their valid range, etc.
  2. Reporting. When an error is detected the library calls the corresponding error function that receives a character string with the name of the function where the error was detected, and an integer error code. Error codes are always negative constants.
  3. Response. The library's default response to an error is to print it in the standard error stream. This can be modified by the user at any time by calling the SetErrorFunction API, replacing the default behavior with its own.
At each error, the library should call the container instance specific error handling when there is one, or call the general error handling function in the iError interface. When it is not possible to call the instance specific error function, for instance when the instance parameter is  NULL , the library calls the general error handling function in the iError interface5.
The user of the library can either replace the default iError interface with a function that handles the error with a jump to a previously set recovery point, or treat the error locally using the return code.

4.1  Return code

All errors are negative constants, it suffices to test if the result is less than zero. In general we have:
Figure
In the positive return code some implementations may encode additional information. The sample implementation returns always 1 6. The zero return value means that nothing was done: it isn't an error but the container was empty for instance, or nothing was written to a file, etc.
The error codes defined by this specification are:
Other errors can be defined by each implementation.
The treatment of each error is done in the object defined by the iError interface.

5  Naming conventions

5.1  Container Names

The basic container names are:
AssociativeContainerBitStringBloomFilter
CircularBufferDictionaryDlist
DequeHashTableList
MaskPQueueQueue
SequentialContainerstrCollectionStreamBuffer
TreeMapVectorWstrCollection

5.2  Interface object name

All APIs of the library begin with the composite name formed by the letter i, followed by the element type name in the templated versions, then the container name. This builds the interface name, i.e. the name of the function table object. This object is indexed by the name of the specific API being called.
For calling the Add API of the List container you would write:
iList.Add. This uses the generic implementation with void *.
For calling the Add API of the Vector container built from double objects you would write:
idoubleVector.Add. This uses the template implementation with a specific type, in this case the double type.
The interface object idoubleVector has one field for each API it supports. The same for the interface object iVector that is a generic interface objects (uses and returns void * instead of specific types).
Other containers like the list container would have the equivalent API named as:
idoubleList.Add, or iList.Add.
The following drawing tries to make this clear:
Figure
The different containers built from a basic container (say List) are named by concatenating the name of the type and the name of the container: intList, doubleList etc.

6  The different containers

All data structures in this section are known and used for several decades. Lists are a common feature of any data processing task since the sixties for instance. The library provides for abstract containers, and some examples of concrete ones for the elementary types. We have:

6.1   Single and double linked lists

This containers consist of a header and a list of elements containing each a pointer to the next element in the chain, and a pointer to the data item stored. The end of the list is marked by a node that contains a  NULL "next" pointer. Double linked lists contain an additional pointer to the previous element.
This is a very flexible container, allowing you to add and delete elements easily just by rewriting some pointers. You can even split them in two sublists just by zeroing somewhere the "next" pointer.
The price you pay for this flexibility is that sequential access is expensive, the cost of accessing the nth element increases linearly with n.
Storage overhead is one or two pointers per element stored in the list for single/double linked lists..
The data is stored directly after the pointer, there is no pointer to the data. This is a variable length structure with a fixed and a variable part. To avoid using a standard C99 feature that could be absent in older compilers, we use a semi-generic pointer indexed either by one (for older compilers) or by nothing (standard C).

6.1.1  Single linked lists of byte or wide character strings

A specialization of the single linked list is provided for multibyte or wide character strings. The rationale for this specialization is that zero terminated strings are variable length records what would make them impossible to store into a standard list that needs records of the same size.

6.2  Flexible arrays (vector)

This container is an array with added operations that allow the user to insert and delete elements easily. It will resize itself if needed.
The access time is essentially the same as with a normal array. Insertion and deletion are possible but they are in general more expensive than with lists since the container must copy the elements to make place for a new element or to delete an element. An exception to this rule is the deletion of the last element that will be done in constant time since it implies only decrementing the number of elements in the container.
The storage overhead for each element is zero since this container doesn't require any pointers per object stored.
This container uses a reserve storage to avoid allocating new memory for each addition operation. This allows the Add operation to be done in constant time in most occasions.
Comparing vectors with plain arrays, there are following points to be made:

6.3  ValArray: Arrays of numbers

This group is an specialization of flexible array. It features objects that contain numbers in different formats designed to facilitate operations in numerical programming. There are ValArrays for the types short, int, long, float, double, long double, size_t and long long. Each ValArray has the same basic operations (addition, subtraction, etc) but some members have specialized operations: trigonometric operations are defined only in floating point ValArrays, boolean operations only in the unsigned versions of the int/short and the long long types.
ValArray functions come often in two flavors: The first uses two arrays where the left argument is both source and destination, and a second form where a number is applied to the whole array. For instance we have AddTo(leftArray,rightArray) and AddToScalar(Array,number).

6.4  String collection

This container is designed to handle a collection of C strings. It is essentially an application of the flexible array container with some extra functionality to handle strings. It comes in two flavors, as strings in C: multi-byte and wide character strings.

6.5  Bit-string

This container is designed to handle arbitrary sequences of bits. Some algorithms that are easy to program with strings are much more complicated for bit-strings, like to one that mimics ßtrstr" ("bit-strstr").
The bits are packed with 8 bits per character unit. The overhead per bit is the size of the bit-string header only. No pointers are associated with each bit.

6.6  Dictionary

This is an associative container based on a hash table. It associates a text key with some arbitrary data. This container is not ordered. Access time to each element depends on how much elements are stored in it and on the efficacy of the hash function to maintain elements in different slots. Storage overhead per element is one pointer each, plus the size of the slot table. This is for a hash table with linked lists in each slot for managing collisions. Other implementations exist of course.

6.7  Hash Table

This is a more sophisticated version of the dictionary hash table. It allows for keys of binary data and it has automatic resizing in case the table gets too crowded.

6.8  AVL trees

This data structure allows for fast searching for data. You can store millions of records and find a given record with a few comparisons.

6.9  Scapegoat trees

This is another form of trees. They can be more efficient than AVL trees, but from a container perspective they share the same characteristics.

6.10  Bloom Filter

This is a probabilistic data structure used to quickly check if an element is not in a larger set of elements. It returns false positives with a given probability set when the container is built. Elements can be added to it but they can't be removed from the container. It stores no data, just a key.

6.11  Queue

Queues are designed to operate in a FIFO context (first-in first-out), where elements are inserted into one end of the container and extracted from the other. This container can be implemented as an adaptor using a single linked list as its base container. The sample implementation uses this strategy to show how adapters can look like. Other implementations can implement this container directly presenting the same interface.

6.12  Deque

This is a linear container that allows for cheap insertions/deletions at both ends.

6.13  Buffers

Buffers are containers used to hold data temporarily, either to be transmitted or stored into some medium, or to be filtered and used later by other parts of the application. The library provides two types of buffers:

6.14  Priority Queues

This container stores data associated with an integer "priority". The meaning of this integer is not further specified and defined by the application. The Pop operation retrieves the data associated with the lowest priority.

6.15  At a glance

NameTypeInterfaceDescription
BitStringSequentialiBitStringSequence of bits
BloomFilterStatisticaliBloomFilterStatistical container
CircularBufferSequential iCircularBufferCircular buffer
DequeSequentialiDequeDouble ended queue
DictionaryAssociativeiDictionaryHash table with character keys
DlistSequential. No random accessiDlistDouble linked list
HashTableAssociativeiHashTableHash tables with arbitrary binary keys
ListSequential. No random accessiListSingle linked list
QueueSequentialiQueueQueue
PQueueAssociative.iPriorityQueuePriority queue.
StreamBufferSequentialiStreamBufferStream buffer
strCollectionSequentialistrCollectionFlexible string arrays.
TreeMapAssociativeiTreeMapScapegoat trees
Value arraySequential. Random accessiValArrayXXXGrowable array of numbers
VectorSequential. Random accessiVectorGrowable array of objects
WstrCollectionSequentialiWstrCollectionFlexible array of wide character strings

7  Types used by the library

Here is a complete list of all the auxiliary data types defined by the library.

7.1  CompareInfo

typedef struct tagCompareInfo {
    void *ExtraArgs;
    const void *ContainerLeft;
    const void *ContainerRight;
} CompareInfo;

This structure will be passed to the comparison functions. The ExtraArgs pointer will receive the pointer that was passed to the calling function. If both elements being compared are members of a single container, the ContainerRight member will be  NULL .

7.2  CompareFunction

typedef int (*CompareFunction)(const void *elem1, 
                               const void *elem2, 
                               CompareInfo *ExtraArgs);

This type defines the function used to compare two elements. The result should be less than zero if elem1 is less than elem2, zero if they are equal, and bigger than zero if elem1 is bigger than element 2.
The default comparison function is memcmp. This function will compare all the object's area, including eventually padding bytes added by compilers for alignment reasons. To minimize this problem always zero the objects before assigning the values. For instance:
struct Data {
    int Age; // Here the compiler can introduce padding bytes
    double Weight;
};

int fn(void)
{
    struct Data m;
    memset(&m,0,sizeof(m));
    m.Age = 23;
    m.Weight = 76;
    iVector.Add(DataCollection,&m);
}

By zeroing the structure before making the assignments, we have a known value in the padding bytes. If we make comparisons with memcmp, they will work correctly.
Obviously the best way to avoid this problem is to avoid any default comparison function by defining one.

7.3  Save function

typedef int (*SaveFunction)(const void *element, 
                               void *ExtraArg, 
                               FILE *OutputStream);

This function should save the given element into the given stream. The ExtraArg argument receives any argument passed to the Save function. The result should be bigger than zero if the operation completed successfully, zero or less than zero otherwise.
What the save function does is unknown to the rest of the library, basically the only requirement is that its output should be understood by the read function, when called to read each element. The size of the written data can be bigger (or smaller) than the size of each stored element, according to the specific requirements of the application data. This allows to write save functions tha would write a complete data set, including embedded objects into the stream. For instance a save function can save the contents pointed by a character pointer instead of the pointer value that would be meaningless when read again.
The default save function provided by the implementation just writes the contents of each element into the stream. Embedded pointers aren't followed.

7.4  Read function

typedef int (*ReadFunction)(void *element, 
                               void *ExtraArg, 
                               FILE *InputStream);

This function should read into the given element from the given stream. The ExtraArg argument is passed to the container read function and allows to pass an argument to the user defined save function. The amount of data read is unknown to the rest of the software and the only requirement is that it should reverse the work of the save function.
The default read function provided by the implementation should read the contents of one element from the stream and add it to the result container.
The result is bigger than zero if the operation completed successfully, zero or less than zero otherwise.

7.5  Error function

typedef void *(*ErrorFunction)(const char *functionName,int code,...);

This function type is used to handle errors in each container. The first argument is the name of the function where the error occurred, the second is a negative error code. No checks are performed on the function name argument, and other information or messages could be included in the message.
Note that this function is roughly compatible with the prototype of snprintf, and could be used with a format string, a buffer size, and a series of arguments corresponding to the arguments the format requires. The only problem is the conversion between int and size_t.
The result value of the default error function is always  NULL . This result will be in most cases ignored, except in the vector container where it can be used to return special values in the case of the INDEX error.
When iVector interface detects an index error, it will pass in the extra arguments the array pointer and a size_t containing the out of bounds index.

7.6  Destructor function

typedef int (*DestructorFunction)(void *object);

This function is called when an object is being destroyed. An object is destroyed when: This function should free any memory used by pointers within the object without freeing the object memory itself. In most cases the memory used by the library is not allocated with malloc. Its result type is less than zero when an error occurred or greater than zero when it finished successfully. It is implementation defined what happens if a destructor encounters an error.

Chapter 3
The common vocabulary: iGenericContainer

The library uses always the same words to represent similar actions in all containers. In addition, each container can use specific words to name actions that are specific to it. In this section only the common actions will be documented, to give an overview of the common vocabulary available.
At the end of this documentation you will find a complete table that lists each action supported by the library and marks which container supports it.

1  Creation of a container: Create

Containers are created with a call to their "Create" function. The first argument is the size of the objects that will be stored in the container. The second is optional and is a hint to the number of elements that will be stored in the container. Note that if you want to store objects of different sizes you just store a pointer to those objects instead of the objects themselves. The creation functions can have several arguments, the first being always the size of the elements that the container will hold. The prototype can be:
Container * iContainer.Create(size_t elementsize,...);

The creation function needs to allocate memory to hold the container. This memory will be allocated using the current memory manager that is always an implicit argument to all creation functions. The rationale behind this design decision is that you don't change your memory allocation strategy at each call to a container creation function. This simplifies the interface at the expense of making the change of allocation strategy more expensive.
There is an abstract class of objects called "Generic container" that has all functions that are common to all containers. This is an abstraction, and as such, it can't have any concrete examples: there is no creation function for a generic container. You can only create a concrete container, a list, a vector, etc.
Once created, and if the created container supports the generic interface, you can make a cast and treat the concrete container as an abstract member of a mythical "generic" container. This can save you a lot of redundant code since your code is independent of the type of container and will run with any object (even future objects) that support the generic container interface.
Figure
As everything, there is no free lunch. Precisely because of its generality the generic interface is missing a lot of functionality that you will find in the concrete containers interfaces.

2  Destruction of a container: Clear and Finalize

All containers support two cleanup functions:
  1. Clear: remove all elements. The header structure remains untouched. This can be used to free the memory when the container was created with the Init function.
  2. Finalize: Remove all elements and the memory used by the container object using the allocator for this container. The container should NOT have been created using the Init function.
The syntax is:
  1. int iContainer.Clear(Container *);
  2. int iContainer.Finalize(Container *);
The result of those functions is less than zero when something goes wrong, greater than zero otherwise.

2.1  Other creation functions

  1. An implicit argument to all the creation functions is the current allocator, that is used to retrieve space for the container being built. To avoid changing the current allocator, what in multi-threaded environment would need acquiring a lock to that global variable, some containers support a creation function that receives an extra argument: a custom allocator.
    Container * iContainer.CreateWithAllocator(size_t elementsize,
                         const ContainerAllocator *allocator, ...);
    
    
  2. Sometimes it can be useful for some containers (specially lists) to create the header structure using an already existing space, for instance in the space for local variables. For this an 'Init' function can exist, that initializes a container within an existing space. Since normally the detailed structure (and the size of course) of each container header is implementation dependent, you use the Sizeof function with an argument of  NULL to get the size of the header. This can be used within a C99 compiler environment to allocate the space for that variable. 17 The declaration of the container header in C99 would be:
    int function(void)
    {
        char listSpace[iList.Sizeof(NULL)];
        iList.Init(listSpace);
    }
    
    
    If C99 is not available, the best way is to just print the size of the container you are interested in, and then use that value that should stay fixed for a given version. This can be automated and you can find in the Appendix 1, a small program that generates a series of #defines with the values of the sizes of the containers described in this documentation 18.
  3. Initializing with existing data All containers support the InitializeWith function. It will create a container using a table of elements to store. Its arguments are the size of the objects to be stored, the number of those objects, and a pointer to the table. The table should be a contiguous memory area.
    /* For sequential containers and TreeMap */
    Container * iContainer.InitializeWith(size_t elementsize,
                                  size_t n, void *data);
    /* For Dictionary */
    Container * iContainer.InitializeWith(size_t elementsize,
                                  size_t n, char ** Keys,void *data;
    
    

3  Adding an element to a container: Add and AddRange

This operation adds the given element to a container. In sequential containers it is added at the end, in associative containers it is added at an unspecified position.
int iContainer.Add(Container *,const  void *element);

The result of this operation is a positive integer if success, or an error code less than zero if the operation fails.
Invariants: The input data is not modified in any way, it is copied into the container.
Sequential containers support also the AddRange API:
int iContainer.AddRange(Container *,size_t n,const void *elements[]);

This API allows you to pass a table of elements into a sequential container and add it with a single call.

4  Removing elements from a container

4.1  Using an element as key

The functions Erase and EraseAll remove the given element from the container. The result is an integer greater or equal to zero or an error code less than zero if the element couldn't be added 19 .
The EraseAll function is not needed for associative containers since each element in those data structures is unique.
int iContainer.Erase(Container *,const void *element);
int iContainer.EraseAll(Container *,const void *element);

4.2  Removing at a given position

The Erase/EraseAt functions need to search for the given element before erasing it. For sequential containers you can use the ËraseAt" function, that will remove an element at a given position.
int iContainer.EraseAt(Container *,size_t idx);

For associative containers you use a different argument set:
int iContainer.Erase(Container *,const char *Key);

4.3  Using a container as a stack

Elements can be removed from the front or the back in sequential containers using it like a stack:
int iContainer.PopBack(Container *d,void *outbuf);
int iContainer.PopFront(Container *d,void *outbuf);

These functions copy the contents of the last or the first element into the given buffer. If the buffer is NULL, nothing is copied, but the container is modified: the first or the last element is removed.

4.4  Batch removing

Several elements can be removed at once from a container using the RemoveRange function. This function is implemented in sequential containers only.
int iContainer.RemoveRange(Container *c,size_t start, size_t end);

This function removes the elements whose index is greater or equal than start and less than end. If start equals end nothing is removed and the result is zero. If end is greater than the length of the container it will be adjusted to 1 element past the end. If no elements are removed the result is zero, positive when one or more elements are removed. The result is a negative error code when an error occurs.

5  Retrieving elements

The GetElement function retrieves an element from a container. It comes in two different flavors, one for sequential containers, and another for associative ones.
const void *iContainer.GetElement(const Container *,size_t index);
const void *iContainer.GetElement(const Container *,const void *Key);

These functions return a pointer to the requested element or  NULL if the element can't be retrieved. The resulting pointer points directly to the data stored in the container. This could be used to bypass all the flags that control the access to the container. For read-only containers, use the CopyElement function that returns a copy of the requested data into a buffer.
The function GetRange retrieves a slice of a container returning a container of the same type with a copy of the elements storeds in the given range:
Container *iContainer.GetRange(const Container *src,
                              size_t start, size_t end);

The pointer returned can be invalidated by some operations done to the container. For instance if you reverse the order of the elements in a container, a pointer to the element zero will point to something else than when you obtained it. If a container needs reallocating its data space because you added an element, all the pointers that point to data elements of the container can be invalidated. In general it is a bad idea to keep pointers to elements in a container that is being modified
Invariants: The given container is not modified in any way.

6  Using masks

A mask is a sequence of boolean elements that contains zeroes or some value different from zero. They are used to select elements from a sequential container: for each element of the mask different from zero the corresponding value is selected, for all elements that contain a zero, that value is eliminated.
The selection operation can be destructive, reusing a container by eliminating from it all elements not selected by the mask, or can be a copy operation where the copy contains only the selected elements.
In the case of a destructive operation, the destructor functions are called for each element destroyed.
int (*Select)(Container *,const mask *);
Container *(*SelectCopy)(const Container *,const mask *);

The functions CompareEqual and CompareEqualScalar produce a mask using two containers as input. Those masks can be used with the selection functions. It is possible of course, to create masks from completely different sources, either directly or by copying. See the iMask interface for further details.
Mask *(*CompareEqual)(const Container *,const Mask *);
Mask *(*CompareEqualScalar)(const Container *, const Mask *);

The CompareEqual function compares two sequential containers of the same type and length, producing a mask containing a value different from zero at each element position where the pair of elements from each container is equal, zero otherwise.
The CompareEqualScalar function compares each element of the given sequential container with a single elemnt, producing a mask with a value different from zero20 for each element that is equal to the given element.

7  Sorting a sequential container: Sort

The "Sort" function will sort a container in place. To keep the old, unsorted contents, make a copy of the container first.
int iContainer.Sort(Container *);

8  Copying a container: Copy

The "Copy" function will make a fresh copy of a container. Some fields of the header are copied: the error and compare functions, the flags, and others. Memory will be allocated withe the source container allocator.
newContainer * iContainer.Copy(const Container *);

Invariants: The input container is not modified.

9  Saving and loading a container to or from disk: Save and Load

The functions "Save" and "Load" will save / load the contents, state, and characteristics of a container into / from disk. They need an open file stream, open in binary mode, and in the correct direction: saving needs a stream open in the write direction, loading needs a stream open in the read direction.
int Save(const Container *c,FILE *stream, SaveFunction fn,void *arg);
Container *Load(FILE *stream,ReadGunction fn,void *arg);

Both Save and Load provide default functions to save and load an element but they do a shallow save: pointers aren't followed to save the data they point to 21.

10  Inserting a container into another

10.1  Sequential containers

int (*InsertIn)(Container *destination,
                size_t position,
                Container *source);
int (*Append)(SequentialContainer * SC1,SequentialContainer * SC2);

The InsertIn function will insert into the "destination" container the contents of the source container at the given position. The source is not modified in any way, and a copy of its data will be used. Both containers must be of the same type and store elements of the same type. The library only tests the element size of each one.
The Append function is similar to InsertIn: the elements of the second container are appended at the end of the first one. The big difference is that the second container is destroyed. It is absorbed into the first: its elements are not copied but inserted. This means that a requirement is that the allocator be the same in both containers.

10.2  Associative containers

int (*InsertIn)(Container *destination, Container *source);

This function will insert into the destination container the source container using the source container keys. Otherwise the same conditions apply as to the sequential containers: the containers must be of the same type and store elements of the same type.

11  Replace an element with another

11.1  Sequential containers: ReplaceAt

int (*ReplaceAt)(Container *dst,size_t position,void *newData);

Replaces the element at the given position with the new data.

11.2  Associative containers: Replace

int (*Replace)(Dictionary *Dict, const char *Key,void *Value);

Replaces the element with the given key. If the element is absent nothing is done.

12  Looping through all elements of a container

The user has three methods for looping through all elements:
  1. Using a simple loop construct
  2. Using the Apply function
  3. Using an iterator
One the most familiar design patterns is the ITERATOR pattern, which ‘provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
Traditionally, this is achieved by identifying an ITERATOR interface that presents operations to initialize an iteration, to access the current element, to advance to the next element, and to test for completion; collection objects are expected to implement this interface, usually indirectly via an auxiliary object.
This is exactly the case in the iterator proposal here. Essential to the pattern is the idea that elements are accessed sequentially, but independently of their position in the collection; for example, labeling each element of a tree with its index in left-to-right order fits the pattern, but labeling each element with its depth does not. This traditional version of the pattern is sometimes called an EXTERNAL ITERATOR.
An alternative INTERNAL ITERATOR approach assigns responsibility for managing the traversal to the collection instead of the client: the client needs only to provide an operation, which the collection applies to each of its elements. The latter approach is simpler to use, but less flexible; for example, it is not possible for the iteration to affect the order in which elements are accessed, nor to terminate the iteration early. This is the algorithm followed by the Apply function.

12.1  Using a simple loop to iterate a container

You can iterate any sequential container with a simple loop. You use the "Size" function to limit the loop. At each loop step you get the corresponding element with the "GetElement" function, present in this form in all containers.
// "Container" is a pointer to some container
for (size_t i=0; i<iContainer.Size(Container); i++) {
    someType *element = iContainer.GetElement(Container,i);
    // Use "element" here.
}

For associative containers you retrieve first a strCollection containing all keys using the GetKeys function, present in all associative containers. Then, you retrieve each element by looping through the string collection that you have obtained in a similar manner to the sequential containers.

12.2  Using the Apply function.

The Apply function will iterate through all elements calling a given function for each one.1 Its prototype is:
void iContainer.Apply(Container, //pointer to some container
                      int(*Applyfn)(void *elem,void *arg),
                      void *arg);

This function receives three arguments:
  1. A pointer to the container. 22.
  2. A function pointer that should point to a function that receives two arguments: the element of the container, and an extra argument where it can receive (and write to) global information about the search. This extra argument is
  3. The third one passed to the Apply function. Apply will pass this argument to the given function together with a pointer to the element retrieved from the container.

12.3  Using iterators

Iterators are objects returned by each container that allow you to iterate (obviously) through all elements of a container. You use iterators like this:
Iterator *it = iContainer.NewIterator(someContainer);
Mytype *Myobject;
for (myobject = it->GetFirst(it); 
     myobject!= NULL; 
     myobject = it->GetNext(it)) {
     // Work with "myobject" here
}
iContainer.DeleteIterator(it); // dispose the iterator object

Iterators provide a container-independent way of iterating that will work with any container, both sequential or associative. In associative containers the specific sequence is implementation defined, and in sequential containers is the natural sequence.
The main objective for iterators is to break a dependence between an algorithm and the type of container it is working with. Since all containers support iterators, you can write your code independently (to a great extent) from which specific iterator you are using.
Invariants: The input container could be modified in some implementations. A list of existing iterators is possible, to allow invalidating them in case of modifications to the container for example.
Iterators always support at least the following functions:
void *iterator->GetFirst(iterator);
void *iterator->GetNext(iterator);
void *iterator->GetCurrent(iterator);

All containers support the "NewIterator" and "deleteIterator" methods:
iterator *iContainer.NewIterator(Container); 
int iContainer.deleteIterator(iterator);

Iterators must be destroyed since they are allocated using the containers allocator.
Sequential containers can support additional functions:
void *iterator->GetLast(iterator);
void *iterator->GetPrevious(iterator);
void *iterator->Seek(iterator i, size_t position);

This interface allows users to write fully general algorithms that will work with any container, independently of its internal structure. Obviously the performance can differ from container to container depending on usage.
All iterators will become invalid if the underlying container changes in any way, except through the iterator itself.23 Each container can conceptually be seen as a sequence of generations, or states. Beginning with the fresh constructed state, the container evolves until it reaches the destroyed state after the execution of the Finalize function. This sequence of states interacts with an iterator as follows: An iterator applies only to a single container state. Any modification of the container state, directly or indirectly moves the state and invalidates the iterator.
An implementation may catch some of the movements of the container in the state space and report an error when an iterator is used that belongs to a different container state. But not all access can be catched. If the user has pointers to an iterator's data and modifies this data without using the container API an implementation may not catch this error.

13  Setting and retrieving the state: GetFlags and SetFlags

Each container has a set of flags that can be read and written to change the container's behavior. The only flag that is defined by all containers is the read-only flag. Implementations can extend this to offer different services like copy-on-write, or other applications.
The read-only flag means that no direct pointers to an element or to the whole data are returned, no functions that modify the container are allowed to proceed and that the Clear() and Finalize() APIs will not work. You must unset this flag to allow for destruction of the object. 24
Using the state space concept introduced above, this flag freezes the state of a container disallowing any further evolution. The only API that can modify the state is the SetFlags API that can reset the state to a read/write state again.

14  Retrieving the number of elements stored: Size

All containers support querying the number of elements stored. The prototype is:
    size_t iContainer.Size(const Container *);

There is no error return. If a  NULL pointer is given to those functions the result is zero.

15  Sizes

15.1  Sizeof

This computes the total size used by the container in bytes, including the header structure and the data stored in the container. Any other overhead must be accounted for.
    size_t iContainer.Sizeof(const Container *);

If its argument is  NULL , Sizeof returns the size of the container header. This can be used to allocate space for a container as a local variable for instance.

15.2  SizeofIterator

Computes the size of the iterator for the given container. The objective here is to allow the declaration of the iterator as a local variable to avoid having to free the iterator at the exit of the function.
int Fn(void)
{
    char buf[iList.SizeofIterator(NULL)];
    Iterator *it = (Iterator *)buf;
    iList.InitIterator(it);
    // Use iterator "it" here
    // ...
    // No need to call deleteIterator at exit
}

16  Memory management

All containers have a pointer to their allocator object. An allocator object is a simple interface that provides 4 functions:
  1. malloc: A function that receives a size_t and returns a void * pointing to a memory block of the requested size, or  NULL if no more memory is available. Note that this function receives the number of bytes to allocate, not the number of items.25
  2. realloc: A function that will resize a previously allocated block.
  3. free: A function that will release the memory allocated previously with malloc/realloc.
  4. calloc: a function that will allocate n objects of m size and clear the memory block to zero before returning it.
At the start of the library runtime a default allocator object exists that uses the four functions of the standard C library. Other allocator objects can be used, and the user can change the global allocator at any time. Each container retrieves the default allocator object when created, and stores it in the container descriptor. Any further change to the default allocator will not affect existing containers that have already an allocator. When changing the allocator you should do that before creating the container.
Some containers are created without any heap management by default. You can introduce heap management by calling the UseHeap function, that will install a new heap in the container. Other containers are always created with a heap, and you should pass them an allocator object for object creation.

16.1  Memory manager objects

The library provides two memory manager objects:
  1. The default memory manager, that receives the standard C library functions; malloc, free, realloc and calloc.
  2. The debug memory manager that implements the same functions with added functionality designed to:

16.2  Pooled memory management

The problem with the traditional C memory management is that it requires that the programmer cares about each piece of RAM that is allocated by the program and follows the lifetime of each piece to ensure that it gets returned to the system for reuse. In today's software world, this is just impractical.
A better strategy is to use a pool of memory where related memory allocations can b e done from a common pool. When the module finishes, all the allocated pool is freed just by destroying the whole pool. This is much easier to manage, and in many cases more efficient. The proposed interface has the following functionalities:
  1. Creation. The creation function receives a memory allocator to use for this pool.
  2. Alloc. This function receives a pool and a size and returns a memory block, or  NULL if there is no more memory.
  3. Clear. This erases all objects allocated in the pool without returning the memory to the system.
  4. Finalize. This releases all memory and destroys all objects.
Note that there is no realloc, and that the "Clear" function is optional. Not all pools support it. The rationale for these decisions being that realloc would need to store the size of each block, what in a pool maintained by a single stack like pointer would be very expensive.

16.3  Heap of same size objects

Many containers are used to store sets of objects of the same size. The library provides a specialized heap management software for this application. It stores vectors of objects of the same size. The interface provided is as follows:

16.4  Garbage collection

Automatic garbage collection is offered by some compiler systems as an alternative to traditional memory management. This solution is not compatible with real time requirements, and is not practical in machines with very low memory configurations.
In other cases however, it can be a real simplification since the programmer is relieved from the huge task of taking care of each piece of memory and to cater its disposal. A simple memory model is proposed: you program as if the amount of memory was infinite and never worry about freeing the memory you use. Periodically the collector starts collecting unused memory chunks and adds them to the pool of available memory or releases it to the underlying operating system.
This model is not the solution to all memory management problems. It can be a solution to some situations, specially when developing in workstation environments where memory is freely available. The bugs that can appear are also very difficult to solve. One of the most difficult is when you keep by mistake some reference to a large piece of memory making the recycling of the memory impossible. In that case you have to search in all the code of the application for the reference that keeps the memory block marked as used, and that can be very difficult in large applications.

16.5  Multi-threading

In environments where multi-threading or other parallel programming constructs are possible, the implementation must provide for sequential semantics, i.e. each operation should perform as described in this documentation with the additional caveat that any operation that modifies a container must be atomic, i.e. it can't be interrupted leaving the container in an unstable or incoherent state. It is up to the implementation to ensure that if an atomic operation is interrupted, the inconsistent container state will be invisible to other processes or threads accessing the container.

17  At a glance

For a more detailed description see § 10API Overview on page pageref
OperationDescription
AddAdds a single element at the end of the container
AddRangeAdds a sequence of elements
AppendAdds a container after a first one
ApplyCalls a function with each element of the container in sequence.
ClearErases all elements
ContainsSearches for an element in a container
CopyCopies a container
CreateCreates a container that will use the current allocator.
CreateWithAllocatorCreates a container that will use a given allocator.
deleteIteratorDisposes of the storage used by the iterator object
EqualCompares two containers of the same type
EraseRemoves an element from the container
EraseAtErases an element at a given position
FinalizeDestroys all storage used
GetElementRetrieves one element from a container
GetFlagsReturns the flags
GetAllocatorRetrieves the allocator used by the container
GetRangeRetrieves several elements
IndexOfRetrieves the position of an element
InsertAtInserts an element at a given position
InsertInInserts a container into another
LoadRetrieves a container from disk
NewIteratorReturns an iterator object
PopFront Retrieves the first element and removes it
PopBackRetrieves the last element and removes it
PushFront Stores an element at the start of a container
PushBackStores an element at the end
RemoveRangeErases a range of elements
Replace Replaces one element (Associative containers)
ReplaceAt Replaces one element (Sequential containers)
SaveStores the container to disk
SetCompareFunctionSets or retrieves the function used for comparisons
SetErrorFunctionSets or retrieves the function used to report errors
SetDestructorSets or retrieves the function called when an element is destroyed
SelectSelects elements using a mask
SelectCopySelect elements to copy using a mask.
SetFlagsSets the flags
SizeReturns the number of elements stored in the container
SizeofReturns the number of bytes used
SizeofIteratorReturns the size of the iterator object for a container
SortSorts the contents
Iterator vocabulary
GetFirst Retrieves a pointer to the first element
GetNext Retrieves a pointer to the next element
GetPrevious Retrives a pointer to the previous element if the container supports bidirectional seek.
GetCurrentRetrieves a pointer to the current element
GetLast Retrieves a pointer to the last element
Replace Replaces the current element with a different element. If the given pointer to the replacement is  NULL it will delete the current element.
SeekSequential containers only. Positions the cursor at a given position.

Chapter 4
The auxiliary interfaces

These interfaces are used by all the containers in the rest of the library. They provide basically three functions Figure

1  Masks

A mask is a sequence that contains boolean data used for selection of items in a sequential container. It is not specified if a mask is a bit string (i.e. a strictly boolean array) or an array of chars or other integers used to hold the binary data. In all cases a value of the mask at a given position means select if it is different than zero, or do not select if it is zero.
The interface offered by the mask object is very small. Masks can't be resized but they have an allocator to be able to reclaim the memory they use when created. This allocator will be initialized to the current allocator when the mask is created.

1.1  The interface

typedef struct _Mask Mask;
typedef struct tagMaskInterface {
    int (*And)(Mask *src1,Mask *src2);
    int (*Clear)(Mask *m);
    Mask *(*Copy)(Mask *src);
    Mask *(*Create)(size_t length);
    Mask *(*CreateFromMask)(size_t length,char *data);
    int (*Finalize)(Mask *m);
    int (*Or)(Mask *src1,Mask *src2);
    size_t (*PopulationCount)(const Mask *m);
    int (*Set)(Mask *m,size_t idx,int val);
    size_t (*Size)(Mask *);
} iMask;

1.2  The API


And

    int (*And)(Mask *src1,Mask *src2);

Description: Stores into src1 the result of a logical AND operation between each element of src1 with the corresponding element of src2.
Errors:
CONTAINER_ERROR_BADARG Any mask pointer is  NULL .
CONTAINER_ERROR_INCOMPATIBLE The masks are of different length.
Returns: A positive number if the operation was performed, a negative error code if an error occurs.

Clear

    int (*Clear)(Mask *m);

Description: Sets all elements of the mask to zero.
Errors:
CONTAINER_ERROR_BADARG The mask pointer is  NULL .
Returns: A positive number if the mask was cleared, a negative error code if an error occurs.

Copy

Mask *(*Copy)(Mask *src);

Description: Allocates a new mask and copies the contents of the given one into it.
Errors:
CONTAINER_ERROR_BADARG The mask pointer is  NULL .
Returns: A pointer to the new mask or  NULL if an error occurs.

CreateFromMask

    Mask *(*CreateFromMask)(size_t length,char *data);

Description: Creates a new mask with the specified length and copies the given data into the mask. Each character in the input data is transformed into the mask internal representation. The storage is obtained using the CurrentAllocator pointer.
Errors:
CONTAINER_ERROR_BADARG The data pointer is  NULL
CONTAINER_ERROR_NOMEMORY No memory is available to perform the allocation.
Returns:A pointer to a new mask or  NULL if an error occurs.

Create

    Mask *(*Create)(size_t length);

Description: Creates a new mask with the specified length. The storage is obtained using the CurrentAllocator pointer. The data is initialized to zero.
Errors:
CONTAINER_ERROR_NOMEMORY No memory is available to perform the allocation.
Returns:A pointer to a new mask or  NULL if an error occurs.

Finalize

    int (*Finalize)(Mask *m);

Description: The memory used by the mask is reclaimed.
Errors:
CONTAINER_ERROR_BADARG The mask pointer is  NULL .
Returns:A positive number if the memory was reclaimed, or a negative error code.

Not

    int (*Not)(Mask *src);

Description: Stores into src the result of a logical NOT operation: each bit is inverted.
Errors:
CONTAINER_ERROR_BADARG The mask pointer is  NULL .
Returns: A positive number if the operation was performed, a negative error code if an error occurs.

Or

    int (*Or)(Mask *src1,Mask *src2);

Description: Stores into src1 the result of a logical OR operation between each element of src1 with the corresponding element of src2.
Errors:
CONTAINER_ERROR_BADARG Any mask pointer is  NULL .
CONTAINER_ERROR_INCOMPATIBLE The masks are of different length.
Returns: A positive number if the operation was performed, a negative error code if an error occurs.

PopulationCount

   size_t (*PopulationCount)(const Mask *m);

Description: Counts the number of entries different from zero in the given mask, returning the sum.
Errors:
CONTAINER_ERROR_BADARG The mask pointer is  NULL .
Returns: A positive number or zero.

Set

    int (*Set)(Mask *m,size_t idx,int val);

Description: Sets the given position to the given value if the value fits in the internal representation of the mask. If not, an implementation defined conversion occurs.
Errors:
CONTAINER_ERROR_BADARG The mask pointer is  NULL .
CONTAINER_ERROR_INDEX The index given is out of bounds.
Returns:A positive number if the value was set or a negative error code.

Size

    size_t (*Size)(Mask *);

Description: The number of elements in the mask is returned.
Errors:
CONTAINER_ERROR_BADARG The mask pointer is  NULL .
Returns:The number of elements. If the mask pointer is  NULL , the result is zero.

Sizeof

    size_t (*Sizeof)(Mask *);

Description: The number of bytes used by the given mask. If the argument is  NULL the number of bytes of the header structure is returned.
Errors:
None.
Returns:The number of bytes.

2  Memory management

Several interfaces implement different memory allocation strategies. This should give flexibility to the implementations, allowing it to use several memory allocation strategies within the same container.
The library starts with the default memory manager, that contains pointers to the default C memory management functions: malloc, free, realloc and calloc. Another memory manager is the debug memory manager that should implement more checking and maybe offer hooks to the debugger. The sample implementation shows how to implement several simple checks, but other implementations can extend this simple interface providing much more sophisticated controls26.
Figure

2.1  The default memory manager

The C language provides several functions to manage memory. The default Allocator object is built from the standard C memory allocation functions.
typedef struct tagAllocator {
    void *(*malloc)(size_t);
    void  (*free)(void *);
    void *(*realloc)(void *,size_t);
    void *(*calloc)(size_t,size_t);
} ContainerAllocator;
extern ContainerAllocator * CurrentAllocator;

At startup, the CurrentAllocator points to an object constructed with the functions of the C standard library. This is a required interface. The user can change at any time the current allocator by making the CurrentAllocator point to a different object. Note that this change does not change the allocators of the containers already created but the allocators of the new containers allocated after the change is made.
This is the established procedure to build custom memory allocators to provide for special alignment requirements, improve speed, allocate objects from the stack instead of the heap, and many other usages.
The library can also include a debug version on top of the standard C functions, offering the same interface. Changing the CurrentAllocator to point to that object allows to switch to the debug version. The debug version of the sample implementation offers:
extern ContainerAllocator iDebugMalloc;

This interface is optional. The sample implementation documents a possible implementation, see § 8.4.5Debugging malloc on page pageref .

2.2  The Heap interface: iHeap

Some containers can benefit from a cacheing memory manager that manages a stock of objects of the same size. This is not required and not all implementations may provide it. If they do, the interface is:
    int (*UseHeap)(Container *c);
    ContainerHeap *(*GetHeap)(Container *c);

In the sample implementation, many complex data structures are implemented using a heap. This allows automatically to have an iterator, since for looping all elements of the container it suffices to iterate the underlying heap. The standard interface for the heap is:
typedef struct tagContainerHeapInterface {
   void (*Clear)(ContainerHeap *heap);
   ContainerHeap *(*Create)(size_t ElementSize,
                  const ContainerAllocator *m);
   void (*Finalize)(ContainerHeap *heap);
   int (*FreeObject)(ContainerHeap *heap,void *element);
   ContainerHeap *(*InitHeap)(void *heap,size_t nbElements,
                  const ContainerAllocator *allocator);
   Iterator *(*NewIterator)(ContainerHeap *);
   void *(*NewObject)(ContainerHeap *heap);
   size_t (*Sizeof)(ContainerHeap *heap);
   int (*deleteIterator)(Iterator *it);
} ContainerHeapInterface;


Create

ContainerHeap *iHeap.Create(size_t elementSize, Allocator *m);

Description: Creates a new heap object that will use the given memory manager to allocate memory. All elements will have the given size. If the memory manager object pointer is  NULL , the object pointed by CurrentAllocator will be used.
Returns:a pointer to the new heap object or  NULL , if an error occurred.
Errors:
CONTAINER_ERROR_BADARG The element size is bigger than what the heap implementation can support..
CONTAINER_ERROR_NOMEMORY Not enough memory is available to complete the operation.

InitHeap

   ContainerHeap * (*InitHeap)(void *heap,size_t ElementSize,
                    ContainerAllocator *m);

Description: Initializes the given buffer to a heap header object designed to hold objects of ElementSize bytes. The heap will use the given memory manager. If the memory manager parameter is  NULL the default memory manager is used.
This function supposes that the heap parameter points to a contiguous memory space at least enough to hold a ContainerHeap object. The size of this object can be obtainer by using the iHeap.Size API with a  NULL parameter.
Returns: A pointer to the new ContainerHeap object or  NULL if there is an error. Note that the pointer returned can be different from the passed in pointer due to alignment requirements.

newObject

   void *iHeap.newObject(ContainerHeap *heap);

Description: The heap returns a pointer to a new object or  NULL if no more memory is left.
Errors:
CONTAINER_ERROR_NOMEMORY Not enough memory is available to complete the operation.
Returns:A pointer to an object or  NULL if there is not enough memory to complete the operation.

FreeObject

   size_t iHeap.FreeObject(ContainerHeap *heap,void *element);

Description: Adds the given object to the list of free objects, allowing for recycling of memory without new allocations. The element pointer can be  NULL .
Errors:
CONTAINER_ERROR_BADARG The heap pointer is  NULL .
Returns:The number of objects in the free list.

Clear

   void iHeap.Clear(ContainerHeap *heap);

Description: Releases all memory used by the free list and resets the heap object to its state as it was when created.
Errors:
CONTAINER_ERROR_BADARG The heap pointer is  NULL .

Finalize

    void iHeap.Finalize(ContainerHeap *heap);

Description: Destroys all memory used by the indicated heap and frees the heap object itself.
Errors:
CONTAINER_ERROR_BADARG The heap pointer is  NULL .

Sizeof

    size_t iHeap.Sizeof(ContainerHeap *heap);

Description: Returns the number of bytes used by the given heap, including the size of the free list. If the argument "heap" is  NULL , the result is the size of the heap header structure (i.e. sizeof(ContainerHeap).
Errors:
None.
Example:

void SomeFunction(void)
{
    char buffer[iHeap.Sizeof(NULL)];
    ContainerHeap *ch;

    ch = iHeap.InitHeap(buffer,200,NULL);
    // ...
    iHeap.Clear(ch);
}

This example uses the variable length arrays that have been introduced in the C language by the latest standard (C99). The Sizeof function returns the size of the header object that is used to specify the size of the buffer. The buffer is passed to the InitHeap function using a number of objects of 200 and the default memory allocator.

3  Pooled memory interface: iPool

Figure
Many containers could benefit from a memory pool. A memory pool groups all allocations done in a specific context and can be released in a single call. This allows the programmer to avoid having to manage each single piece of memory like the basic interface.
typedef struct _tagPoolAllocatorInterface {
    Pool  *(*Create)(ContainerAllocator *m);
    void  *(*Alloc)(Pool *pool,size_t size);
    void  *(*Calloc)(Pool *pool,size_t size);
    void   (*Clear)(Pool *);
    void   (*Finalize)(Pool *);
} PoolAllocatorInterface;

Note that there is no realloc function. Pooled memory is often implemented without storing the size of the block to cut overhead. Since a realloc function could be expensive, implementations are not required to provide it.

Create

    Pool *iPool.Create(ContainerAllocator *m);

Description: Creates a new pool object that will use the given memory manager. If m is null, the object pointed by the CurrentAllocator will be used.
Errors:
CONTAINER_ERROR_NOMEMORY Not enough memory to complete the operation.
Returns: A pointer to the new object or  NULL if the operation couldn't be completed.

Alloc

    void  *iPool.Alloc(Pool *pool,size_t size);

Description: Allocates size bytes from the pool pool. If there isn't enough memory to resize the pool the result is  NULL .
Errors:
CONTAINER_ERROR_NOMEMORY Not enough memory to complete the operation.
Returns: A pointer to the allocated memory or  NULL if error.

Calloc

    void  *iPool.Calloc(Pool *pool,size_t n,size_t size);

Description: Allocates n objects of size ßize" in a single block. All memory is initialized to zero. If there is no memory left it returns  NULL ;
Errors:
CONTAINER_ERROR_NOMEMORY Not enough memory to complete the operation.
Returns: A pointer to the allocated memory or  NULL if error.

Clear

    void iPool.Clear(Pool *);

Description: Reclaims all memory used by the pool and leaves the object as it was when created.
Errors:
CONTAINER_ERROR_BADARG The pool pointer is  NULL .

Finalize

    void   iPool.Finalize(Pool *);

Description: Reclaims all memory used by the pool and destroys the pool object itself.
Errors:
CONTAINER_ERROR_BADARG The pool pointer is  NULL .

4  Error handling Interface: iError

The ïError" interface provides a default strategy for handling errors. The "RaiseError" function will be used as the default error function within the creation function for all containers that support a per container instance error function.
typedef (*ErrorFunction)(const char *,int,...);
typedef struct {
  void        (*RaiseError)(const char *fname,int code,...);
  void        (*EmptyErrorFunction)(const char *fname,int code,...);
  const char *(*StrError)(int errorCode);
  ErrorFunction (*SetErrorFunction)(ErrorFunction);
  int         (*NullPtrError)(const char *);
} ErrorInterface;


RaiseError

 void      iError.RaiseError(const char *fname,int errcode,...);

Description: The parameter "fname" should be the name of the function where the error occurs. The ërrcode" parameter is a negative error code. The actual value of the code is defined for the cases mentioned in the section 2.4.1Return code. Other negative values can be defined by the implementation.
Other parameters can be passed depending on the error. The sample implementation never passes anything else but the name of the function where the error occurs and the error code.
The behavior of the default error function is implementation specific. In the sample code this function will just print the error message in the standard error stream. Other implementations could end the program, log the error into a error stream, or do nothing.
Returns:No return value

EmptyErrorFunction

 void      iError.EmptyErrorFunction(const char *fname,int errcode,...);

Description: This function can be used to ignore all errors within the library. It does nothing.

StrError

  const char *iError.StrError(int errorCode);

Description: Converts the given error code in a character string. If the error code doesn't correspond to any error defined by the implementation a character string with an implementation defined value is returned.

SetErrorFunction

  ErrorFunction iError.SetErrorFunction(ErrorFunction);

Description: Changes the value of the default error function. If its argument is  NULL , nothing is done, and the call is interpreted as a query of the current value.
Returns: The old value of the default error function.

NullPtrError

int (*NullPtrError)(const char *msg);

Description: This is a utility function equivalent to:
int NullPtrError(const char *fname)
{
    iError.RaiseError(fname,CONTAINER_ERROR_BADARG);
    return CONTAINER_ERROR_BADARG;
}

5  The iterator interface

Figure
The iterator object exposes at least the functions "GetFirst", for initializing the loop, and "GetNext", for getting the next element in the sequence. The functions "NewIterator" and "deleteIterator" are specific to each container interface even if they all have the same syntax.

5.1  The interface

typedef struct _Iterator {
    void *(*GetNext)(Iterator *);
    void *(*GetPrevious)(Iterator *);
    void *(*GetFirst)(Iterator *);
    void *(*GetCurrent)(Iterator *);
    void *(*GetLast)(Iterator *);
    void *(*Seek)(Iterator *it,size_t pos);
    int (*Replace)(Iterator *it, void *data, int drection);
} Iterator;


GetCurrent

    void *(*GetCurrent)(Iterator *);

Description: Returns the element at the cursor position.
Errors:
CONTAINER_ERROR_BADARG The iterator pointer is  NULL .
Returns: A pointer to the current element or  NULL , if the container is empty or an error occurs. If the container is read-only, a pointer to a copy of the element is returned. This pointer is valid only until the next iterator function is called.

GetFirst

    void *(*GetFirst)(Iterator *);

Description: This function initializes the given iterator to the first element in the container. For sequential operators this is the element with index zero. In associative operators which element is the first is implementation defined and can change if elements are added or removed from the container.
If the container is empty the result is  NULL .
Errors:
CONTAINER_ERROR_BADARG The iterator pointer is  NULL .
Returns: A pointer to the first element or  NULL , if the container is empty or an error occurs. If the container is read-only, a pointer to a copy of the element is returned. This pointer is valid only until the next iterator function is called.
Example:

Iterator *myIterator;
List *myList;
myType *obj; // "myList" stores objects of type "myType"
myIterator = iList.NewIterator(myList); // Request iterator
for (obj = myIterator->GetFirst(myIterator); 
     obj != NULL; 
     obj = myIterator->GetNext(myIterator)) {
     //Use obj here
}
iList.deleteIterator(myIterator); // Reclaim memory


GetNext

    void *(*GetNext)(Iterator *);

Description: Positions de cursor at the next element and returns a pointer to its contents. If the iterator is at the end of the container the result is  NULL and the iterator remains at the last position, a subsequent call to GetCurrent returns the last element.
If the container is read-only, a pointer to a copy of the object is returned. This pointer is valid only until the next iterator function is called.
Errors:
CONTAINER_ERROR_BADARG The iterator pointer is  NULL .
CONTAINER_ERROR_OBJECT_CHANGED The container has been modified and the iterator is invalid. Further calls always return  NULL .
Returns: A pointer to the next element or  NULL , if the cursor reaches the last element. If the container is read-only, a pointer to a copy of the element is returned, valid until the next element is retrieved

GetPrevious

    void *(*GetPrevious)(Iterator *);

Description: Positions de cursor at the previous element and returns a pointer to its contents. If the pointer is at the beginning of the container the result is  NULL and the iterator remains at the beginning, a subsequent call to GetCurrent will return the first element of the container.
This function is meaningful only in sequential containers. Its existence in associative containers is implementation defined. Even in sequential containers, it can be very expensive to find a previous element, for instance in single linked lists. In those cases it can always return  NULL .
Errors:
CONTAINER_ERROR_BADARG The iterator pointer is  NULL .
CONTAINER_ERROR_OBJECT_CHANGED The container has been modified and the iterator is invalid. Further calls always return  NULL .
Returns: A pointer to the previous element or  NULL , if the cursor reached the first element already. If the container is read-only, a pointer to a copy of the element is returned.
Example:

Iterator *myIterator;
List *myList;
myType *obj; // "myList" stores objects of type "myType"
myIterator = iList.NewIterator(myList); // Request iterator
for (obj = myIterator->GetLast(myIterator); 
     obj != NULL; 
     obj = myIterator->GetPrevious(myIterator)) {
     //Use obj here
}
iList.deleteIterator(myIterator); // Reclaim memory


GetLast

    void *(*GetLast)(Iterator *);

Description: Positions the cursor at the last element and returns a pointer to it. Returns  NULL if the container is empty. If the container is read-only, a pointer to a copy of the element is returned.
This function is meaningful only in sequential containers. Its existence in associative containers is implementation defined. Even in sequential containers, it can be very expensive to find the last element, for instance in single linked lists. In those cases it can always return  NULL .
Errors:
CONTAINER_ERROR_BADARG The iterator pointer is  NULL .
CONTAINER_ERROR_OBJECT_CHANGED The container has been modified and the iterator is invalid. Further calls always return  NULL .

Seek

void *(*Seek)(Iterator *it,size_t pos);

Description: Positions the given iterator at the indicated position and then returns a pointer to the element's data at that position. If the position is bigger than the last element of the container, the last element position will be used.
This function is supported in sequential containers only.
Errors:
CONTAINER_ERROR_BADARG The iterator pointer is  NULL .
CONTAINER_ERROR_OBJECT_CHANGED The container has been modified and the iterator is invalid. Further calls always return  NULL .
Returns: A pointer to the data of the given element or  NULL if an error occurs.

Replace

int (*Replace)(Iterator *it,void *data, int direction);

Description: Replaces the current object pointed by the given iterator with the new data. If the data argument is  NULL the element is erased from the container. If the direction parameter is different from zero, in sequential containers the iterator will point to the next element, otherwise it will point to the previous element. In associative containers this parameter is ignored and the iterator is always set to the next element, if any.
Errors:
CONTAINER_ERROR_BADARG The iterator pointer is  NULL .
CONTAINER_ERROR_OBJECT_CHANGED The container has been modified and the iterator is invalid. Further calls always return  NULL .
CONTAINER_ERROR_READONLY The container is read only.
Returns:A positive value if the element was changed or erased, zero if the container was empty, or a negative error code if an error occurred.

6  The observer interface

In its general form, the observer design pattern can be defined as a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
When a container changes its state, specifically when elements are added or removed, it is sometimes necessary to update relationships that can be very complex. The observer interface is designed to simplify this operation by allowing the container to emit notifications to other objects that have previously manifested interest in receiving them by subscribing to them. In general notifications are sent only when one of the defined operations for a container occur, mostly operations that change the number of elements.
This interface then, establishes a relationship between two software entities:
  1. The container, that is responsible for sending the notifications when appropriate
  2. The receiver, that is an unspecified object represented by its callback function that is called when a change occurs that matches the notifications specified in the subscription.
Since this relationship needs both objects, it will be finished when either object goes out of scope or breaks the relationship for whatever reason. Both objects can unsubscribe (terminate) their relationship.

6.1  Caveats

6.2  The interface

typedef void (*ObserverFunction)(const void *ObservedObject,
                                 unsigned Operation, 
                                 void *ExtraInfo[]);

typedef struct tagObserverInterface {
    int (*Subscribe)(void *ObservedObject, 
                     ObserverFunction callback, unsigned Operations);
    int (*Notify)(const void *ObservedObject,unsigned operation,
                  void *ExtraInfo1,void *ExtraInfo2);
    size_t (*Unsubscribe)(void *ObservedObject,
                          ObserverFunction callback);
} ObserverInterface;
extern ObserverInterface iObserver;


ObserverFunction

typedef void (*ObserverFunction)(void *ObservedObject,
                                 unsigned Operation, void *ExtraInfo[]);

Description: This function will be called by the interface when a notification is received for an observed object. The call happens after all arguments have been processed, the actual work of the function is finished (when adding an object) or not yet done (when destroying an object). The container is in a consistent state. For the callbacks that are called when an object is deleted from a container the call happens before any call to free() and before any call to a destructor (if any) is done. For the calls that add an object the callback is called after the container has been modified.
Arguments:
  1. ObservedObject: Specifies the object that sends the notification, i.e. the container that has the subscription. It is assumed that this container conforms to the iGeneric interface.
  2. Operation: The operation that provoked the notification. Since it is possible to subscribe to several operations with only one callback function, this argument allows the callback to discriminate between the operation notifications.
  3. ExtraInfo: This argument is specific to each operation and conveys further information28 for each operation.
None of the arguments will be ever  NULL or zero.

Subscribe

int (*Subscribe)(void *ObservedObject, ObserverFunction callback, 
                 unsigned Operations);

Description: This function establishes the relationship between the observed object (argument 1) and the observer, represented by its callback (argument 2). The third argument establishes which operations are to be observed. This operation performs an allocation to register the relationship in the observer interface tables, therefore it can fail with an out of memory condition.
Errors:
CONTAINER_ERROR_BADARG The observed object pointer is  NULL , the callback function pointer is  NULL , or the operations argument is zero.
CONTAINER_ERROR_NOMEMORY There is not enough memory to proceed.
Returns:An integer greater than zero if the relationship was established, a negative error code otherwise.

Notify

int (*Notify)(void *ObservedObject,unsigned Operation,
                  void *ExtraInfo1,void *ExtraInfo2);

Description: This function will be used by the container to send a message to the receiver callback. The arguments correspond roughly to the arguments the callback function will receive. "Notify" will call all the objects that are observing ObservedObject and that have subscribed to one of the operations specified in the Operation argument. This implies a search through the observer interface table, and possibly several calls, making this function quite expensive. The time needed is roughly proportional to the number of registered callbacks and the complexity of the callbacks themselves.
Errors:
CONTAINER_ERROR_BADARG The ObservedObject pointer is  NULL or the Operation argument is zero.
Returns:A positive number with the number of objects that received the notifications, zero if there was no match for the combination of observed object and operations specified, or a negative error code.

Unsubscribe

size_t (*Unsubscribe)(void *ObservedObject, ObserverFunction callback);

Description: This function breaks the relationship between the observed object and the observer. There are several combinations of both arguments:

6.3  Notifications messages

Operation Argument 1 Argument 2
Operation Argument 1 Argument 2
AddPointer to the new object NULL or slice specs if any
AddRangeA size_t with the number of objects addedPointer to a table of n elements that were added
AppendA pointer to the object being appended. It is of the same type as the object emitting the notification NULL
ClearPointer to the container being cleared NULL
CopyPointer to the copy of the container NULL
ErasePointer to the object being deleted. The object is still valid NULL
EraseAtPointer to object being deletedPosition (as size_t)
Finalize NULL  NULL
InsertPointer to the new object being insertedA size_t with the position of the object being inserted if applicable
InsertInPointer to the object being inserted, that has the same type as the object sending the notification NULL
PopPointer to the object being popped NULL
PushPointer to the object being pushed NULL
ReplaceAtPointer to the old valuePointer to the new value
Here is a complete example that demonstrates some of the above functions.
Example:

include "containers.h"
static void fn(void *ObservedObject, unsigned operation,
               void *extraInfo[])
{
    printf("Object is %p, operation is %d\n",ObservedObject,operation);
}
int main(void)
{
    ValArrayInt * vInt = iValArrayInt.CreateSequence(24,0,1);

    printf("Original array: \n");
    iValArrayInt.Fprintf(vInt,stdout,"%d ");
    iObserver.Subscribe(vInt,fn,CCL_ADD|CCL_FINALIZE);
    printf("Adding an integer\n");
    iValArrayInt.Add(vInt,4096);
    iValArrayInt.Fprintf(vInt,stdout,"%d ");
    iValArrayInt.Finalize(vInt);
}
OUTPUT:
Original array: 
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 
Adding an integer
Object is 0x100100080, operation is 1
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 4096 
Object is 0x100100080, operation is 16

We setup our observer function calling the Subscribe API. We request to be notified when there is an addition and when the object finalizes. Our callback function does nothing but print some of its arguments. We see that we get called when the requested operations are performed.

Chapter 5
The containers

1  The List interfaces: iList, iDlist

The list container appears in two flavors: The space overhead of single linked lists is smaller at the expense of more difficult access to the elements. It is up to the application programmer to decide which container fits best in his/her application 29.
It is often more efficient to get the next element from a list starting with the previous element instead of searching the whole list starting from the beginning. For this, the list and the Dlist containers provide:
These operations can't be done in a read-only list.
The exact layout of the ListElement structure is undefined and private to each implementation. This is the reason for providing the ElementData function: it hides the exact position and layout of the data from user code, that remains independent from implementation details.
The interfaces of both containers are very similar. Double linked lists support all functions in single linked ones, and add a few more. To avoid unnecessary repetition we document here all the single linked list interface, then only the functions that the Dlist interface adds to it.
typedef struct tagListInterface {
   int (*Add)(List *L,const void *newval);
   int (*AddRange)(List *L, size_t n,const void *data);
   void *(*Advance)(ListElement **pListElement);
   int (*Append)(List *l1,List *l2);
   int (*Apply)(List *L,int(Applyfn)(void *,void *),void *arg);
   void *(*Back)(const List *l);
   int (*Clear)(List *L);
   int (*Contains)(const List *L,const void *element);
   List *(*Copy)(const List *L);
   int (*CopyElement)(const List *list,size_t idx,void *OutBuffer);
   List *(*Create)(size_t element_size);
   List *(*CreateWithAllocator)(size_t elementsize,
         const ContainerAllocator *mm);
   void *(*ElementData)(ListElement *le);
   int (*Equal)(const List *l1,const List *l2);
   int (*Erase)(List *L,const void *);
   int (*EraseAll)(List *l,const void *);
   int (*EraseAt)(List *L,size_t idx);
   int (*EraseRange)(List *L,size_t start,size_t end);
   int (*Finalize)(List *L);
   ListElement *(*FirstElement)(List *l);
   void *(*Front)(const List *l);
   const ContainerAllocator *(*GetAllocator)(const List *list);
   void *(*GetElement)(const List *L,size_t idx);
   size_t (*GetElementSize)(const List *l);
   unsigned (*GetFlags)(const List *L);
   ContainerHeap *(*GetHeap)(const List *l);
   List *(*GetRange)(const List *l,size_t start,size_t end);
   int (*IndexOf)(const List *L,const void *SearchedElement,
        void *ExtraArgs,size_t *result);
   List *(*Init)(List *aList,size_t element_size);
   int (*InitIterator)(List *L,void *buf);
   List *(*InitWithAllocator)(List *aList,size_t element_size,
         const ContainerAllocator *mm);
   List *(*InitializeWith)(size_t elementSize,size_t n,
         const void *data);
   int (*InsertAt)(List *L,size_t idx,const void *newval);
   int (*InsertIn)(List *l, size_t idx,List *newData);
   ListElement *(*LastElement)(List *l);
   List *(*Load)(FILE *stream, ReadFunction loadFn,void *arg);
   Iterator *(*NewIterator)(List *L);
   ListElement *(*NextElement)(ListElement *le);
   int (*PopFront)(List *L,void *result);
   int (*PushFront)(List *L,const void *str);
   int (*RemoveRange)(List *l,size_t start, size_t end);
   int (*ReplaceAt)(List *L,size_t idx,const void *newval);
   int (*Reverse)(List *l);
   int (*RotateLeft)(List *l, size_t n);
   int (*RotateRight)(List *l,size_t n);
   int (*Save)(const List *L,FILE *stream, SaveFunction saveFn,
        void *arg);
   int (*Select)(List *src,const Mask *m);
   List *(*SelectCopy)(const List *src,const Mask *m);
   CompareFunction (*SetCompareFunction)(List *l,CompareFunction fn);
   DestructorFunction (*SetDestructor)(List *v,DestructorFunction fn);
   int (*SetElementData)(List *l, ListElement *le,void *data);
   ErrorFunction (*SetErrorFunction)(List *L,ErrorFunction);
   unsigned (*SetFlags)(List *L,unsigned flags);
   size_t (*Size)(const List *L);
   size_t (*Sizeof)(const List *l);
   size_t (*SizeofIterator)(const List *);
   ListElement *(*Skip)(ListElement *l,size_t n);
   int (*Sort)(List *l);
   List *(*SplitAfter)(List *l, ListElement *pt);
   int (*UseHeap)(List *L, const ContainerAllocator *m);
   int (*deleteIterator)(Iterator *);
} ListInterface;

Figure

1.1  General remarks

Lists are containers that store each element in a sequence, unidirectionally (single linked lists) or bidirectionally (double linked lists). The advantage of linked lists is their flexibility. You can easily and with a very low cost remove or add elements by manipulating the links between the elements. Single linked lists have less overhead than their double linked counterparts (one pointer less in each node), but they tend to use a lot of computer power when inserting elements near the end of the list: you have to follow all links from the beginning until you find the right one.
The list nodes themselves do not move around, only their links are changed. This can be important if you maintain pointers to those elements. Obviously, if you delete a node, its contents (that do not move) could be recycled to contain something else than what you expect.
The iList interface consists (as all other interfaces) of a table of function pointers. The interface describes the behavior of the List container.
The stack operations push and pop are provided with PushFront and PopFront because they have a very low cost, insertion at the start of a single linked list is very fast. PushBack is the equivalent of the Add operation, but PopBack would have a very high cost since it would need going through all the list.
The list container features in some implementations a per list error function. This is the function that will be called for any errors, except in cases where no list object exists: the creation function, or the error of getting a  NULL pointer instead of a list pointer. In those cases the general iError interface is used, and iError.RaiseError is called. The default value of the list error function is the function iError.RaiseError at the moment the list is created.
Other implementations of this interface may specialize list for a certain category of uses: lists of a few elements would try to reduce overhead by eliminating a per list error function and replace it with the standard error function in iError, for instance, eliminating their fields in the header. If the read-only flag support is dropped, the whole "Flags" field can be eliminated. In such an implementation, the SetFlags primitive would always return an error code.
The sample implementation of the list container supports the following state flags:
#define CONTAINER_READONLY          1

If this flag is set, no modifications to the container are allowed, and the Clear and Finalize functions will not work. Only copies of the data are handed out, no direct pointers to the data are available.
#define CONTAINER_SORTED_FRONT      2
#define CONTAINER_SORTED_BACK       4

If this flag is set, the container is maintained always in sorted order, with the biggest element at the index zero for CONTAINER_SORTED_FRONT or with the biggest element at the end if CONTAINER_SORTED_BACK is set. It is an error if both flags are set, and the results in that case are implementation defined.

1.1.1  Specializations

All ßpecialized" containers share the same interface with the following exceptions:
Figure
In the right side of the drawing we see the generic list container using generic pointers (void *) and the stringlist container. Strings are special because in C their length is the result of a function call instead of being fixed like other data types.
In the left side, we see three specialized containers for some numeric data types. Those containers are generated using two types of source files:

Add

int (*Add)(List *l,const void *data);
int (*Add)(TYPEList *l, TYPE data);

Description: Adds the given element to the container. In its generic form it is assumed that "data" points to a contiguous memory area of at least ElementSize bytes. Inits specialized form the data is passed by value. Returns a value greater than zero if the addition of the element to the list completed successfully, a negative error code otherwise.
Errors:
CONTAINER_ERROR_BADARG The list or the data pointers are  NULL .
CONTAINER_ERROR_READONLY The list is read-only. No modifications allowed.
CONTAINER_ERROR_NOMEMORY Not enough memory to complete the operation.
Invariants: The input data is not modified.
Returns: A positive number if the element was added or a negative error code otherwise.
Example:

/* This example shows how to: 
(1) Create a linked list of "double" data
(2) Fill it using the "Add" function
(3) Print it using the GetElement function */
#include <containers.h>
static void PrintList(List *AL)
{
    size_t i;
    for (i=0; i<iList.Size(AL);i++) {
        printf("%g ",*(double *)iList.GetElement(AL,i));
    }
    printf("\n");
}
static void FillList(List * AL,size_t siz)
{
    size_t i;

    for (i=0; i<siz;i++) {
        double d = i;
        iList.Add(AL,&d);
    }
}

int main(void)
{
    List *AL = iList.Create(sizeof(double));
    FillList(AL,10);
    PrintList(AL);
    return 0;
}
OUTPUT:
0 1 2 3 4 5 6 7 8 9

Using the templated container implementation:
#include <containers.h>
#include <doublelist.h>
static void PrintList(doubleList *AL)
{
    size_t i;
    for (i=0; i<idoubleList.Size(AL);i++) {
        printf("%g ",idoubleList.GetElement(AL,i));
    }
    printf("\n");
}
static void FillList(doubleList * AL,size_t siz)
{
    size_t i;

    for (i=0; i<siz;i++) {
        idoubleList.Add(AL,(double)i);
    }
}

int main(void)
{
    doubleList *AL = idoubleList.Create();
    FillList(AL,10);
    PrintList(AL);
    return 0;
}
OUTPUT:
0 1 2 3 4 5 6 7 8 9


Advance

void *(*Advance)(ListElement **ppElement);
TYPE *(*Advance)(TYPEListElement **ppElement);

Description: Given the address of a pointer to an element, it returns a pointer to the data stored into that element and writes the address of the next element into its argument ppElement. If ppElement is NULL it returns NULL . If *ppElement is NULL it also returns NULL , and obviously there is no advancing done.
Returns:A pointer to the data stored in the given element or NULL if the data can't be retrieved.

AddRange

    int (*AddRange)(List * AL,size_t n, void *data);
    int (*AddRange)(TYPEList * AL,size_t n, TYPE *data);

Description: Adds the n given elements to the end of the container. It is the same operations as the PushBack operation. It is assumed that "data" points to a contiguous memory area of at least n*ElementSize bytes. If n is zero no error is issued even if the array pointer or the data pointer are  NULL .
Errors:
CONTAINER_ERROR_BADARG The list or the data pointers are  NULL , and n is not zero.
CONTAINER_ERROR_READONLY The list is read-only. No modifications allowed.
CONTAINER_ERROR_NOMEMORY Not enough memory to complete the operation.
Returns: A positive number if the operation completed, negative error code otherwise.

Append

    int (*Append)(List *list1,List *list2);

Description: Appends the contents of list2 to list1 and destroys list2.
Errors:
CONTAINER_ERROR_BADARG Either list1 or list2 are  NULL .
CONTAINER_ERROR_READONLY One or both lists are read only.
CONTAINER_ERROR_INCOMPATIBLE The size of the elements in the lists differ, or the lists allocators are different 30.
Returns: A positive value if the operation succeeded, or a negative error code otherwise.
Example:

#include <containers.h>
static void PrintList(List *AL)
{
    size_t i;
    for (i=0; i<iList.Size(AL);i++) {
        printf("%g ",*(double *)iList.GetElement(AL,i));
    }
    printf("\n");
}
static void FillList(List * AL,size_t siz)
{
    size_t i;

    for (i=0; i<siz;i++) { double d = i; iList.Add(AL,&d);}
}

int main(void)
{
    List *L1 = iList.Create(sizeof(double));
    List *L2 = iList.Create(sizeof(double));
    FillList(L1,10);
    FillList(L2,10);
    iList.Append(L1,L2);
    PrintList(L1);
    return 0;
}
OUTPUT:
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9


Apply

    int (*Apply)(List l,int (Applyfn)(void *,void *),void *arg);
    int (*Apply)(List l,int (Applyfn)(TYPE ,void *),void *arg);

Description: Will call the given function for each element of the list. The first argument of the callback function receives an element of the list. The second argument of the callback is the arg argument that the Apply function receives and passes to the callback. This way some context can be passed to the callback, and from one element to the next. Note that the result of the callback is not used. This allows all kinds of result types to be accepted after a suitable cast. If the list is read-only, a copy of the element will be passed to the callback function.
Errors:
CONTAINER_ERROR_BADARG Either list or Applyfn are  NULL .
CONTAINER_ERROR_NOMEMORY : The list is read-only and there is no more memory to allocate the buffer to copy each element.
Notes:
The list container of C++ has no direct equivalent, but in the algorithm part of the STL there is a "for_each" construct, that does essentially the same. Java and C# offer a similar "ForEach" functionality.
Example:

#include <containers.h>
static int Callback(void *pElement,void *pResult)
{
    double *p = pElement;
    double *result = pResult;
    *result += *p;
    return 1;
}
    
void main(void) 
{
    double sum = 0;
    List *list = iList.Create(sizeof(double));
    double d = 2;
    iList.Add(list,&d);
    d = 3;
    iList.Add(list,&d);
    iList.Apply(list,Callback,&sum);
    // Here sum should be 5.
    printf("%g\n",sum);
}

/* Template file example */
#include <containers.h>
#include "doublelist.h"
static int Callback(double Element,void *pResult)
{
    double *result = pResult;
    *result += Element;
    return 1;
}
    
void main(void) 
{
    double sum = 0;
    doubleList *list = idoubleList.Create();
    iList.Add(list,2);
    iList.Add(list,3);
    iList.Apply(list,Callback,&sum);
    // Here sum should be 5.
    printf("%g\n",sum);
}

The above example shows a function callback as used by Äpply". It receives two pointers, one to the current element and another to an extra argument that in this case contains a pointer to the sum. For each call to the callback, the function adds the contents of the element to the sum.
The main function creates a list, adds two elements with the values 2 and 3, and then calls Äpply" to get their sum using the callback.

Back

const void *(*Back)(const List *list);
const TYPE  (*Back)(const TYPEList *l);

Description: Returns the last element of the given list or  NULL if the list is empty.
Errors:
CONTAINER_ERROR_BADARG The list pointer is  NULL .
CONTAINER_ERROR_READONLY The list is read only.
Invariants: The input list is not modified.
Returns: The last element or  NULL if the list is empty or an error occurs.

Clear

    int (*Clear)(List *l);

Description: Erases all stored data and releases the memory associated with it. The list header will not be destroyed, and its contents will be the same as when the list was initially created. It is an error to use this function when there are still active iterators for the container.31
Errors:
CONTAINER_ERROR_BADARG The list pointer is  NULL .
CONTAINER_ERROR_READONLY The list is read only.
Returns: The result is greater than zero if successful, or an error code if an error occurs.
Example:

List *l;
    int m = iList.Clear(l);


Contains

    int (*Contains)(List *list,void *data);
    int (*Contains)(TYPEList *l, TYPE data);

Description: Returns one if the given data is stored in the list, zero otherwise. The "data" argument is supposed to point to an element at least ElementSize bytes. The list's comparison function is used for determining if two elements are equal. This comparison function defaults to memcmp.
Errors:
CONTAINER_ERROR_BADARG Either list or data are  NULL .
Notes:
C++ has std::find that does essentially the same . Java and C# have a "Contains" method.
Example:

List *list;
    int r = iList.Contains(list,&data);


Copy

    List *(*Copy)(const List *L);

Description: A shallow copy of the given list is performed. Only ElementSize bytes will be copied for each element. If the element contains pointers, only the pointers are copied, not the objects they point to. The new memory will be allocated using the given list's allocator.
Errors:
CONTAINER_ERROR_NOMEMORY There is not enough memory to complete the operation.
CONTAINER_ERROR_BADARG The given list pointer is  NULL .
Invariants: The input list is not modified.
Notes:
C++ has no direct equivalent but the assignment operator should work, Java and C# support a copy method.
Example:

List *newList,*OldList;
    newList = iList.Copy(OldList);
    if (newList == NULL) { /* Error handling */ }


CopyElement

    int (*CopyElement)(const List *list,size_t idx,void *outBuffer);
    int (*CopyElement)(const TYPEList *list,size_t idx,TYPE *outBuffer);

Description: Copies the element data at the given position into the given buffer, assuming that at least ElementSize bytes of storage are available at the position pointed by the output buffer. The main usage of this function is to access data in a read only container for later modification.
Errors:
CONTAINER_ERROR_BADARG The given list pointer or the output buffer are  NULL .
CONTAINER_ERROR_INDEX The given position is out of bounds.
Invariants: The input list is not modified.
Returns: A positive value if the operation succeeded, or a negative error code if it failed.
Notes:
Neither C# nor Java provide this functionality because the treatment of pointers in those languages makes the need for such a construct unnecessary.
Example:

List *list; double d;
    if (iList.CopyElement(list,3,&d) > 0)
        printf("The value at position 3 is %g\n",d);


Create

    List *(*Create)(size_t element_size);
    TYPEList *(*Create)(void); // Template version

Description: The creation function returns an empty List container, initialized with all the default values. The current memory manager is used to allocate the space needed for the List header. The list is supposed to contain elements of the same size. If the elements you want to store are of different size, use a pointer to them, and create the list with sizeof(void *) as the size parameter.
Returns: A pointer to a newly created List or  NULL if an error occurs.
Errors:
CONTAINER_ERROR_NOMEMORY There is not enough memory to complete the operation.
CONTAINER_ERROR_BADARG The given element size is zero or greater than what the implementation allows for maximum object size.
Errors provoke the call of the current default error function of the library since this is the creation function and there isn't a container specific error function yet.
Example:

List *ListOfDoubles = iList.Create(sizeof(double));
    doubleList *ListOfDoubles = idoubleList.Create();


CreateWithAllocator

List *(*CreateWithAllocator)(size_t elem_size,
                             ContainerAllocator *allocator);
// Template file version
TYPEList *(*CreateWithAllocator)(ContainerAllocator *allocator);

Description: The creation function returns an empty List container, initialized with all the default values. The given memory manager is used to allocate the space needed for the List header. The list is supposed to contain elements of the same size. If the elements you want to store are of different size, use a pointer to them, and create the list with sizeof(void *) as the size parameter.
Returns: A pointer to a newly created List or  NULL if an error occurs.
Errors:
CONTAINER_ERROR_NOMEMORY There is not enough memory to complete the operation.
CONTAINER_ERROR_BADARG The given element size is zero or greater than what the implementation allows for maximum object size, or the given allocator pointer is  NULL .
Errors provoke the call of the current default error function of the library since this is the creation function and there isn't a container specific error function yet.
Example:

ContainerAllocator *myAllocator;
    List *ListOfDoubles = 
          iList.CreateWithAllocator(sizeof(double),myAllocator);
    doubleList *ListOfDoubles = 
          idoubleList.CreateWithAllocator(myAllocator);


deleteIterator

    int deleteIterator(Iterator *it);

Description: Reclaims the memory used by the given iterator object
Errors:
CONTAINER_ERROR_BADARG The iterator pointer is  NULL .
Returns:A positive value if successful or a negative error code.

Equal

    int (*Equal)(const List *list1,const List *list2);

Description: Compares the given lists using the list comparison function of either list1 or list2 that must compare equal. If the list differ in their length, flags, or any other characteristic they compare unequal. If any of their elements differ, they compare unequal. If both list1 and list2 are  NULL they compare equal. If both list1 and list2 are empty they compare equal.
Errors:
None
Invariants: The two lists are not modified.
Returns: The result is one if the lists are equal, zero otherwise.

Erase

    int (*Erase)(List *list,void *const data);

Description: Removes from the list the element that matches the given data, that is assumed to be a pointer to an element.
Returns: A negative error code if an error occurred, or a positive value that indicates that a match was found and the element was removed. If the element is not in the list the result is CONTAINER_ERROR_NOTFOUND .
Errors:
CONTAINER_ERROR_BADARG One or both arguments are  NULL .
Example:

double d = 2.3;
    List *list;
    int r = iList.Erase(list,&d);
    if (r > 0)
        printf("2.3 erased\n");
    else if (r == CONTAINER_ERROR_NOTFOUND)
        printf("No element with value 2.3 present\n");
    else
        printf("2.3 not erased. Error is \%s\n",iError.StrError(r));


EraseAll

    int (*EraseAll)(List *list,const void *data);

Description: Removes from the list all elements that match the given data, that is assumed to be a pointer to an element.
Returns: A negative error code if an error occurred, or a positive value that indicates that a match was found and the element was removed. If the element is not in the list the result is CONTAINER_ERROR_NOTFOUND .
Errors:
CONTAINER_ERROR_BADARG One or both arguments are  NULL .

EraseAt

    int (*EraseAt)(List *list,size_t idx);

Description: Removes from the list the element at the given position.
Returns: A negative error code if an error occurred or a positive value that indicates that the element was removed.
Errors:
CONTAINER_ERROR_BADARG The given list pointer is  NULL .
CONTAINER_ERROR_INDEX The given position is out of bounds.
Example:

List *list;
    int r = iList.EraseAt(list,2);
    if (r > 0)
        printf("Element at position 2 erased\n");
    else
        printf("Error code %d\n",r);


EraseRange

  int (*EraseRange)(List *L,size_t start,size_t end);

Description: Removes from the list the given range, starting with the start index, until the element before the end index. If end is greater than the length of the list, it will be 'rounded' to the length of the list.
Errors:
CONTAINER_ERROR_BADARG The given list pointer is  NULL .
Returns: A positive number indicates success, zero means nothing was erased, and a negative number an error.
Example:

#include <containers.h>
static void print_list(List *li)
{
        int i;
        for (i=0; i<iList.Size(li); i++)
                printf(" %d",*(int *)iList.GetElement(li,i));
        printf("\n");
}
int main(void)
{
    List *li = iList.Create(sizeof(int));
    int i;
    for (i=0; i<10;i++) {
        iList.Add(li,&i);
    }
    print_list(li);
    iList.EraseRange(li,3,8);
    print_list(li);
}
OUTPUT:
 0 1 2 3 4 5 6 7 8 9
 0 1 2 8 9


Finalize

    int (*Finalize)(List *list);

Description: Reclaims all memory used by the list, including the list header object itself.
Errors:
CONTAINER_ERROR_BADARG The given list pointer is  NULL .
CONTAINER_ERROR_READONLY The list is read-only. No modifications allowed.
Returns: A positive value means the operation completed. A negative error code indicates failure.
Example:

List *list;
    int r = iList.Finalize(list);
    if (r < 0) { /* error handling */ }


FirstElement

ListElement *(*FirstElement)(List *list);

Description: Finds the first element of the list and returns a pointer to it. This is a pointer to the element, not to the data stored at that element. It is an error to attempt to use this function with a read-only list.
Errors:
CONTAINER_ERROR_BADARG The list pointer is NULL .
CONTAINER_ERROR_READONLY The list is read only.
Returns:A pointer to the element or NULL if the list is empty or an error occurs.

Front

const void *(*Front)(const List *l);
TYPE *(*Front)(const TYPEList *l);

Description: Returns a pointer to the first element of the given list or  NULL if the list is empty.
Errors:
CONTAINER_ERROR_BADARG The list pointer is  NULL .
CONTAINER_ERROR_READONLY The list is read only.
Invariants: The input list is not modified. The resulting pointer is read-only.
Returns: The first element or  NULL if the list is empty or an error occurs.

GetAllocator

ContainerAllocator *(*GetAllocator)(const List *l);

Description: Returns the list's allocator object. If the list pointer is  NULL it returns  NULL .

GetElementSize

    size_t (*GetElementSize)(const List *l);

Description: Retrieves the size of the elements stored in the given list. Note that this value can be different than the value given to the creation function because of alignment requirements.
Errors:
CONTAINER_ERROR_BADARG The given list pointer is  NULL .
Invariants: The input list is not modified.
Returns: The element size.
Example:

List *l;
    size_t siz = iList.GetElementSize(l);


GetElement

    const void *(*GetElement)(const List *list,size_t idx);
    const TYPE *(*GetElement)(const TYPEList *list,size_t idx);

Description: Returns a read only pointer to the element at the given index, or  NULL if the operation failed. This function will return  NULL if the list is read only.
Use the CopyElement function to get a read/write copy of an element of the list.
Errors:
CONTAINER_ERROR_BADARG The given list pointer is  NULL .
CONTAINER_ERROR_INDEX The given position is out of bounds.
CONTAINER_ERROR_READONLY The list is read only.
Invariants: The input list is not modified. The resulting pointer is read-only.
Example:

List *list;
    doubleList *dlist;
    // ...
    double d = *(double *)iList.GetElement(list,3);
    double d = idoubleList.GetElement(dlist,3);


GetFlags / SetFlags

    unsigned (*GetFlags)(const List *l);
    unsigned (*SetFlags)(List *l,unsigned newFlags);

Description: GetFlags returns the state of the container flags, SetFlags sets the flags to a new value and returns the old value.
Errors:
CONTAINER_ERROR_BADARG The given list pointer is  NULL .
Invariants: The input list is not modified.
Returns: The flags or zero if there was an error.

GetRange

    List *(*GetRange)(const List *list,size_t start,size_t end);
    TYPE *(*GetRange)(const TYPEList *l,size_t start,size_t end);

Description: Selects a series of consecutive elements starting at position start and ending at position end. Both the elements at start and end are included in the result. If start is greater than end start and end are interchanged. If end is bigger than the number of elements in list, only elements up to the number of elements will be used. If both start and end are out of range an error is issued and  NULL is returned. The selected elements are copied into a new list.
Invariants: The original list remains unchanged.
Errors:
CONTAINER_ERROR_BADARG The given list pointer is  NULL
CONTAINER_ERROR_INDEX Both start and end are out of range.
Returns: A pointer to a new list containing the selected elements or  NULL if an error occurs.
Example:

List *list;
    List *range = iList.GetRange(list,2,5);
    if (range == NULL) { /* Error handling */ }


IndexOf

int (*IndexOf)(const List *l,const void *ElementToFind,
                               void *args,size_t *result);
int (*IndexOf)(const TYPEList *l,const TYPE ElementToFind,
                               void *args,size_t *result);

Description: Searches for an element in the list. If found its zero based index is returned in the passed pointer "result".
Otherwise the result of the search is CONTAINER_ERROR_NOTFOUND and the passed pointer will remain unmodified. The args argument will be passed to the comparison function that is called by IndexOf.
Errors:
CONTAINER_ERROR_BADARG The given list pointer or element are  NULL .
Invariants: The input list and the data to be searched are not modified.
Returns: A positive value if element is found or a negative value if not found or an error occurs.
Example:

List *list;
    double data;
    size_t idx;
    int r = iList.IndexOf(list,&data,&idx);
    if (r == CONTAINER_ERROR_NOTFOUND)
        printf("Not found\n");
    else if (r < 0)
        printf("Error\n");
    else printf("Found at position %ld\n",idx);


Init

List *(*Init)(List *aList,size_t element_size);
TYPEList *(*Init)(TYPEList *aList);

Description: Initializes the memory pointed by the aList argument. The new list will use the allocator pointed by the current memory allocator. It is assumed that the memory pointed by aList contains at least the size of the header object. This size can be obtained by calling the Sizeof function with a  NULL argument.
Errors:
CONTAINER_ERROR_BADARG The given list pointer is  NULL .
Example:

// This example uses C99
void Example(void)
{
    char aList[iList.Sizeof(NULL)];
    List *list = iList.Init((List *)aList);
    doubleList *l = idoubleList.Init((TYPEList *)aList);
}


InitializeWith

List *(*InitializeWith)(size_t elementSize, size_t n,const void *Data);
// Template file version
TYPEList *(*InitializeWith)(size_t n,const TYPE *Data);

Description: Construct a new list from the given data. It is assumed that Data points to a memory area that contains at least n items of elementSize bytes each.
Errors:
CONTAINER_ERROR_BADARG The Data argument is  NULL .
Invariants: The input data is not modified.
Returns:The new list or  NULL if an error occurs

InitWithAllocator

    List *(*InitWithAllocator)(List *aList,
                               size_t element_size,
                         const ContainerAllocator *allocator);
    TYPEList *(*InitWithAllocator)(TYPEList *aList,
                         const ContainerAllocator *allocator);

Description: Initializes the memory pointed by the aList argument. The new list will use the given allocator. It is assumed that the memory pointed by aList contains at least the size of the header object. This size can be obtained by calling the Sizeof function with a  NULL argument.
Errors:
CONTAINER_ERROR_BADARG The given list pointer is  NULL .
Invariants: The given allocator is not modified.

InsertAt

    int (*InsertAt)(List *L,size_t idx,const void *newData);
    int (*InsertAt)(TYPEList *L,size_t idx,const TYPE newData);

Description: Inserts the new element. The new element will have the given index, that can go from zero to the list count inclusive, i.e. one more than the number of elements in the list. In single linked lists the cost for this operation is proportional to idx.
Errors:
CONTAINER_ERROR_BADARG The given list pointer or the element given are  NULL .
CONTAINER_ERROR_READONLY The list is read only.
CONTAINER_ERROR_INDEX The given position is out of bounds.
CONTAINER_ERROR_NOMEMORY There is not enough memory to complete the operation.
Invariants: The data is not modified.
Returns: A positive value if the operation succeeded, or a negative error code if the operation failed.
Example:

1 #include "containers.h"
  2 static int PrintInt(void *pdata,void *extraArgs)
  3 {
  4     int i = *(int *)pdata;
  5     fprintf(extraArgs,"%d ",i);
  6 }
  7 int main(void)
  8 {
  9     List *L;
 10     int data;
 11 
 12     L = iList.Create(sizeof(int));
 13     data = 0;
 14     iList.Add(L,&data);
 15     iList.PushFront(L,&data);
 16     data = 2;
 17     iList.InsertAt(L,1,&data); 
 18     data = 5;
 19     iList.Add(L,&data);
 20     data = 6;
 21     iList.Add(L,&data);
 22     iList.Apply(L,PrintInt,stdout);
 23     iList.Finalize(L);
 24 }
OUTPUT: 0 2 0 5 6

  1 #include "containers.h"
  2 #include "intlist.h"
  3 static int PrintInt(int data,void *extraArgs)
  4 {  
  5     fprintf(extraArgs,"%d ",data);
  6 }
  7 int main(void)
  8 {
  9     intList *L;
 10     int data;
 11 
 12     L = iintList.Create();
 13     iintList.Add(L,0);
 14     iintList.PushFront(L,0);
 15     iintList.InsertAt(L,1,2); 
 16     iintList.Add(L,5);
 17     iintList.Add(L,6);
 22     iintList.Apply(L,PrintInt,stdout);
 23     iintList.Finalize(L);
 24 }
OUTPUT: 0 2 0 5 6


We create a list of integers at line 12. We add zero to it, then again a zero at the first position. Our list now is just 0 0. We insert at the position 1 the value 2 in line 17. Then we add some data at the end. To print the list we use Apply with a function that receives the file where the data should be printed in the ExtraArgs parameter.

InsertIn

    int (*InsertIn)(List *Destination, size_t position, List *source);

Description: Inserts the list given in its third argument at the given position in the list pointed to by its first argument. The data is copied, and the source argument is not modified in any way. Both lists must have elements of the same type. The library only tests the size of each one.
Errors:
CONTAINER_ERROR_BADARG The source or the destination lists are  NULL .
CONTAINER_ERROR_READONLY The destination list is read only.
CONTAINER_ERROR_INDEX The given position is out of bounds.
CONTAINER_ERROR_NOMEMORY There is not enough memory to complete the operation.
CONTAINER_ERROR_INCOMPATIBLE The lists store elements of different size.
Returns: A positive value if the operation succeeded, or a negative error code if the operation failed.
Example:

#include <containers.h>
/* Prints the contents of a list */
static void PrintList(List *AL)
{
    size_t i;
    printf("Count %ld\n",(long)iList.Size(AL));
    for (i=0; i<iList.Size(AL);i++) {
        printf("%g ",*(double *)iList.GetElement(AL,i));
    }
    printf("\n");
}
/* Fills a list with 10 numbers. The 10 is hardwired... */
static void FillList(List * AL,int start)
{
    size_t i;

    for (i=0; i<10;i++) {
        double d = i+start;
        iList.Add(AL,&d);
    }
}
/* Creates two lists: one with the numbers from 0 to 9, another 
   with numbers 100 to 109, then inserts the second into the 
   first at position 5 */
int main(void)
{
    List *AL = iList.Create(sizeof(double));
    List *AL1 =iList.Create(sizeof(double));
    FillList(AL,0);
    FillList(AL1,100);
    iList.InsertIn(AL,5,AL1);
    PrintList(AL);
    return 0;
}
OUTPUT:
Count 20
0 1 2 3 4 100 101 102 103 104 105 106 107 108 109 5 6 7 8 9


LastElement

ListElement *(*LastElement)(List *l);

Description: Returns a pointer to the last element stored in the given list or NULL if the list is empty or an error occurs. It is an error to call this function in a read-only list.
Errors:
CONTAINER_ERROR_BADARG The list pointer is NULL .
CONTAINER_ERROR_READONLY The list is read only.
Returns:The last element or NULL

Load

    List *(*Load)(FILE *stream,ReadFunction readFn,void *arg);

Description: Reads a list previously saved with the Save function from the stream pointed to by stream. If readFn is not  NULL , it will be used to read each element. The arg argument will be passed to the read function. If the read function is  NULL , this argument is ignored and a default read function is used.
Errors:
CONTAINER_ERROR_BADARG The given stream pointer is  NULL .
CONTAINER_ERROR_NOMEMORY There is not enough memory to complete the operation.
Returns: A new list or  NULL if the operation could not be completed. Note that the function pointers in the list are NOT saved, nor any special allocator that was in the original list. Those values will be the values by default. To rebuild the original state the user should replace the pointers again with the new list.

NewIterator

    Iterator *(*NewIterator)(List *list);

Description: Allocates and initializes a new iterator object to iterate this list.
Errors:
CONTAINER_ERROR_NOMEMORY No more memory is available.
Returns:A pointer to a new iterator or  NULL if there is no more memory left.
Example:

List *list;
    Iterator *it = iList.NewIterator(list);
    double *d;
    for (d=it->GetFirst(it); d != NULL; d = it->GetNext(it)) {
        double val = *d;
        // Work with the value here
    }
    iList.deleteIterator(it);


NextElement

DlistElement *(*NextElement)(DlistElement *le);

Description: Returns a pointer to the next element in the list. If the input list is NULL it returns NULL .
Errors:
None.
Returns:The next element or NULL .

PopFront

int (*PopFront)(List *L,void *result);

Description: Pops the element at position zero copying it to the result pointer. If the "result" pointer is  NULL , the first element is removed without any copying. The library supposes that result points to at least ElementSize bytes of contiguous storage.
Errors:
CONTAINER_ERROR_BADARG The list or the result pointer are  NULL .
CONTAINER_ERROR_READONLY The list is read only.
Returns: A positive value if an element was popped, zero if the list was empty, or a negative error code if an error occurred.
Example:

double d;
    int r = iList.PopFront(L,&d);
    if (r==0)
        printf("List empty\n");
    else if (r < 0) {
        printf("Error %d\n",r);
    else    printf("OK, popped value %g\n",d);


PushFront

    int (*PushFront)(List *L,void *element);

Description: Inserts the given element at position zero.
Errors:
CONTAINER_ERROR_BADARG The list or the element pointer are  NULL .
CONTAINER_ERROR_READONLY The list is read only.
CONTAINER_ERROR_NOMEMORY There is not enough memory to complete the operation.
Returns: A positive value if the operation completed, or a negative error code otherwise.
Example:

double d = 2.3;
    if (iList.PushFront(list,&d) < 0)
        printf("Error\n");


RemoveRange

int (*RemoveRange)(List *l,size_t start,size_t end);

Description: Removes all elements having an index equal or greater than start and less than end . If end is greater than the number of elements in the collection it will be adjusted to one more than the number of elements. If start is bigger than end the range is still valid and starts with the value of end and ends with the value of start .
Errors:
CONTAINER_ERROR_BADARG The list pointer is  NULL .
Returns: Zero if no elements were removed. Otherwise returns a positive number for success, a negative error code in case of an error.

ReplaceAt

    int (*ReplaceAt)(List *list,size_t idx,const void *newData);

Description: Replaces the list element at position idx with the new data starting at the position pointed to by "newData" and extending ElementSize bytes.
Errors:
CONTAINER_ERROR_BADARG The list or the new element pointer are  NULL .
CONTAINER_ERROR_READONLY The list is read only.
CONTAINER_ERROR_INDEX The given position is out of bounds.
Invariants: The input data is not modified.
Returns: A negative error code if an error occurs, or a positive value if the operation succeeded.
Example:

List *list;
    double d = 6.7;
    int r = iList.ReplaceAt(list,2,&d);
    if (r < 0) { /* Error handling */ }


Reverse

    int (*Reverse)(List *list);

Description: Reverses the order of the given list: the head becomes the tail and the tail becomes the head. The original order is lost.
Errors:
CONTAINER_ERROR_BADARG The list pointer is  NULL .
CONTAINER_ERROR_READONLY The list is read only.
Returns: A negative error code if an error occurs, or a positive value if the operation succeeded.
Example:

#include <containers.h>
static void print_list(List *li)
{
        int i;
        for (i=0; i<iList.Size(li); i++)
                printf(" %d",*(int *)iList.GetElement(li,i));
        printf("\n");
}
int main(void)
{
    List *li = iList.Create(sizeof(int));
    int i;
    for (i=0; i<10;i++) {
        iList.Add(li,&i);
    }
    print_list(li);
    iList.Reverse(li);
    print_list(li);
}
OUTPUT
 0 1 2 3 4 5 6 7 8 9
 9 8 7 6 5 4 3 2 1 0


RotateRight

    int (*RotateRight)(List *src,size_t n);

Description: Rotates right the list by the indicated amount. The last n elements will be written to the start of the array, and the rest will be shifted right.
Errors:
CONTAINER_ERROR_BADARG The list pointer is  NULL .
CONTAINER_ERROR_READONLY The list is read only.
Returns:A positive number if something was moved, zero otherwise (the input was zero or a modulo of the array size).

RotateLeft

    int (*RotateLeft)(List *src,size_t n);

Description: Rotates left the list by the indicated amount. The first n elements will be written to the end of the list, and the rest will be shifted left to fill the empty n places.
Errors:
CONTAINER_ERROR_BADARG The list pointer is  NULL .
CONTAINER_ERROR_READONLY The list is read only.
Returns:A positive number if something was moved, zero otherwise (the input was zero or a modulo of the array size), or an error code less than zero if an error occurs.

Save

int (*Save)(const List *l, FILE *stream,SaveFunction SaveFn, void *arg);

Description: The contents of the given list are saved into the given stream. If the save function pointer is not  NULL , it will be used to save the contents of each element and will receive the arg argument passed to Save. Otherwise a default save function will be used and arg will be ignored.
Errors:
CONTAINER_ERROR_BADARG The list pointer or the stream pointer are  NULL . EOF A disk input/output error occurred.
Returns: A positive value if the operation completed, a negative value or EOF otherwise.

Select

int (*Select)(List *l,Mask *m);

Description: Uses the given mask to select elements from the given list. The list is modified: all elements that have a corresponding value of zero in the mask are erased from the list. The length of the mask should be equal to the length of the list. If there is a destructor set up for the list, it is called for all the elements that are eliminated.
Invariants: The mask is not modified.
Errors:
CONTAINER_ERROR_BADARG The list or the mask are  NULL .
CONTAINER_ERROR_READONLY The list is read only.
CONTAINER_ERROR_INCOMPATIBLE The mask and the list have different lengths.
Returns: A positive value for success, a negative error code otherwise.
Example:

#include "containers.h"
static void printList(List *v)
{
    int i,*pi;

    for (i=0; i<iList.Size(v);i++) {
        pi = iList.GetElement(v,i);
        printf("%2d ",*pi);
    }
    printf("\n");
}

int main(void)
{
    List * vInt = iList.Create(sizeof(int));
    int i;
    Mask *m;

    for (i=1;i<=24;i++)
        iList.Add(vInt,&i);
    m = iMask.Create(24);
    printf("Original list: \n");
    printList(vInt);

    for (i=0;i<24;i++) {
        iMask.Set(m,i,i&1);
        printf("%2d ",i&1);
    }
    printf("\nSelect of the list:\n");

    iList.Select(vInt,m);
    printList(vInt);

    iList.Finalize(vInt);
    iMask.Finalize(m);
}
OUTPUT:
Original list: 
 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 
 0  1  0  1  0  1  0  1  0  1  0  1  0  1  0  1  0  1  0  1  0  1  0  1 
Select of the list:
 2  4  6  8 10 12 14 16 18 20 22 24 


SelectCopy

List *(*SelectCopy)(const List *l,const Mask *m);

Description: Uses the given mask to select elements from the given list. The list is not modified: all elements that have a corresponding value different of zero in the mask are copied to the new list. The length of the mask should be equal to the length of the list.
Invariants: Neither the input list nor the mask are modified.
Errors:
CONTAINER_ERROR_BADARG The list or the mask are  NULL .
CONTAINER_ERROR_INCOMPATIBLE The mask and the list have different lengths.
CONTAINER_ERROR_NOMEMORY Insufficient ressources to create the result list.
Returns: A pointer to a new list or  NULL if there was an error.
Example:

#include "containers.h"
static void printList(List *v)
{
        int i,*pi;

        for (i=0; i<iList.Size(v);i++) {
                pi = iList.GetElement(v,i);
                printf("%2d ",*pi);
        }
        printf("\n");
}

int main(void)
{
        List * vInt = iList.Create(sizeof(int));
        List *copy;
        int i;
        Mask *m;

        for (i=1;i<=24;i++)
                iList.Add(vInt,&i);
        m = iMask.Create(24);
        printf("Original list: \n");
        printList(vInt);

        for (i=0;i<24;i++) {
                iMask.Set(m,i,!(i&1));
                printf("%2d ",!(i&1));
        }
        printf("\nSelect of the list:\n");

        copy = iList.SelectCopy(vInt,m);
        printList(copy);

        iList.Finalize(vInt);
        iList.Finalize(copy);
        iMask.Finalize(m);
}
OUTPUT
Original list: 
 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 
 1  0  1  0  1  0  1  0  1  0  1  0  1  0  1  0  1  0  1  0  1  0  1  0 
Select of the list:
 1  3  5  7  9 11 13 15 17 19 21 23 


SetCompareFunction

CompareFunction (*SetCompareFunction)(List l,CompareFunction f);

Description: if the f argument is non  NULL , it sets the list comparison function to f.
Errors:
CONTAINER_ERROR_BADARG The list pointer is  NULL .
CONTAINER_ERROR_READONLY The list is read only and the function argument is not  NULL .
Returns: The old value of the comparison function.
Example:

ErrorFunction fn,newfn;
    List *list;
    fn = iList.SetCompareFunction(list,newfn);


SetDestructor

    DestructorFunction SetDestructor(List *l,DestructorFunction fn);

Description: Sets the destructor function to its given argument. If the function argument is  NULL nothing is changed and the call is interpreted as a query since the return value is the current value of the destructor function. If the list argument is  NULL , the result is  NULL .
Returns:The old value of the destructor.

SetElementData

int (*SetElementData)(List *l, ListElement *le,void *data);

Description: Copies ElementSize bytes from its parameter data into the given list element. The list is modified even if all pointers into it could remain valid. Any iterators into the list will stop working.
Errors:
CONTAINER_ERROR_BADARG Any argument is NULL .
Returns:The old value of the error function, or  NULL if there is an error.

SetErrorFunction

    ErrorFunction (*SetErrorFunction)(List *L,ErrorFunction); 

Description: Replaces the current error function for the given list with the new error function if the ErrorFunction parameter is different from  NULL . If the List parameter is  NULL the function returns the value of the current default error function.
Errors:
CONTAINER_ERROR_BADARG The list pointer is  NULL .
CONTAINER_ERROR_READONLY The list is read only and the function argument is not  NULL .
Returns: The old value of the error function, or  NULL if there is an error.

Size

    size_t (*Size)(const List *l);

Description: Returns the number of elements stored in the list.
Errors:
If the given list pointer is  NULL , it returns SIZE_MAX.
Example:

List *li;
    size_t bytes = iList.Size(li);


Sizeof

    size_t (*Sizeof)(const List *list);

Description: Returns the total size in bytes of the list, including the header, and all data stored in the list. If list is  NULL , the result is the size of the List structure.
Returns: The number of bytes used by the list or the size of the empty List container if the argument is  NULL .
Example:

#include "containers.h"
int main(void)
{
    List *l = iList.Create(sizeof(int));
    int i;

    for (i=0; i<10; i++)
        iList.Add(l,&i);
    // Queries for size of the list and for size of the list header
    printf("Size of list: %lu, sizeof list header %lu\n",
        iList.Sizeof(l),iList.Sizeof(NULL));
    // Prints the size of the data (int) and the size of a pointer
    printf("Sizeof int %lu, sizeof void * %lu\n",
        sizeof(int),sizeof(void *));
    iList.Finalize(l);
}

OUTPUT (64 bit system)
Size of list: 208, sizeof list header 88
Sizeof int 4, sizeof void * 8

208−88 is 120. There are 10 elements, so each element is 12 bytes: 8 for a pointer and 4 for an integer32.

SizeofIterator

    size_t (*SizeofIterator)(const List *list);

Description: Returns the total size in bytes of the list iterator.
Returns: The number of bytes used by the list iterator.
Example:

List *list;
    size_t  siz = iList.Sizeof(list);


Sort

    int (*Sort)(List *list);

Description: Sorts the given list using the list comparison function. The order of the original list is destroyed. You should copy it if you want to preserve it.
Returns: A positive number if sorting succeeded, a negative error code if not.
Example:

List *list;
    if (iList.Sort(list) < 0) { /* Error handling */ }


SplitAfter

    List *(*SplitAfter)(List *list, ListElement *point);

Description: Splits the source lost at the given element. This element must be an element of the source list. Checking that this is actually the case is very expensive and probably will be skipped in most implementations. An error here will have very serious consequences for the integrity of the system.
SplitAfter builds a new list with all the elements after the given element that becomes the last element of the source list.
Errors:
CONTAINER_ERROR_BADARG The list pointer or the element are  NULL .
CONTAINER_ERROR_READONLY The source list is read only.
CONTAINER_ERROR_NOMEMORY There aren't enough ressources to create a new list.
Returns:
A pointer to the newly created list or  NULL if an error occurs or the given list element is the last element of the list.
Example:

#include "containers.h"
/* This example creates a list of 16 double precision numbers
   and splits it after element 4. It prints the lists before and after
   the split, then exits
*/
static void Fprintf(List *v) // Prints a list of doubles
{
    int i;
    double *pi;

    for (i=0; i<iList.Size(v);i++) {
        pi = iList.GetElement(v,i);
        printf("%g ",*pi);
    }
    printf("\n");
}

int main(void)
{
    List *l,*newList;
    ListElement *le;
    int i;
    double d;

    l = iList.Create(sizeof(double));
    d = 1;
    for (i=0; i<16;i++) {
        iList.Add(l,&d);
        d += 1;
    }   
    printf("Original list:\n");
    Fprintf(l);
    le = iList.FirstElement(l);
    for (i=0; i<4;i++)
        le = iList.NextElement(le);
    printf("Element %g is at the split\n",*(double *)iList.ElementData(le));
    newList = iList.SplitAfter(l,le);
    printf("New list:\n");
    Fprintf(newList);
    printf("Source list\n");
    Fprintf(l);
    iList.Finalize(l);
    iList.Finalize(newList);
}

OUTPUT:
Original list:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 
Element 5 is at the split
New list:
6 7 8 9 10 11 12 13 14 15 16 
Source list
1 2 3 4 5 


UseHeap

    int (*UseHeap)(List *list,ContainerAllocator *m);

Description: Adds a heap manager to the given list, that should be empty. The heap manager will manage the free list and the allocation of new objects. Use this function when the list will hold a great number of elements. This function is optional and may not be present in all implementations. If m is  NULL , the current memory manager object will be used for allocating and reclaiming memory. Otherwise m should be a memory manager object.
Errors:
CONTAINER_ERROR_BADARG The list pointer is  NULL .
CONTAINER_ERROR_NOT_EMPTY The list is not empty or has already a heap.
Portability: This function is optional and may not be present in all implementations.

1.2  Specializations of linked lists

The sample implementation proposes a stringlist container that can hold a list of strings. Its interface functions and all its vocabulary are identical to the linked list one. This is a recommended extension but may not be present in all implementations of the library.
It comes in two flavors: In this specialization the function GetElementSize returns always zero since it has no meaning in a list composed of strings of different length.33
The function Sizeof that is very fast in normal lists (the element size is known), it is very expensive in string lists since all the list needs to be scanned to add up the contents of each string.34
Another difference is that the function Save and the function Load do not use the user function argument since they are specialized to load and save character strings. The function argument can have any value. It has been maintained for compatibility with the rest of the software.



2  Double linked lists: iDlist

Figure
Differences with the list vocabulary are marked in bold.


Double linked lists have a pair of pointers pointing to the next and to the previous element in the list. It is easy then, to move in either direction through the list. The price to pay is a higher overhead for each element. This container shares most of its interface with the single linked list container. Here we document the functions that aren't already described for the list container.
typedef struct tagDlistInterface {
   int (*Add)(Dlist *dl,const void *newval);
   int (*AddRange)(Dlist *l,size_t n,const void *data);
   void *(*Advance)(DlistElement **pDlistElement);
   int (*Append)(Dlist *l1,Dlist *l2);
   int (*Apply)(Dlist *L,int(Applyfn)(void *elem,void *extraArg),
        void *extraArg);
   void *(*Back)(const Dlist *l);
   int (*Clear)(Dlist *dl);
   int (*Contains)(const Dlist *dl,const void *element);
   Dlist *(*Copy)(const Dlist *dl);
   int (*CopyElement)(const Dlist *l,size_t idx,void *outbuf);
   Dlist *(*Create)(size_t elementsize);
   Dlist *(*CreateWithAllocator)(size_t,const ContainerAllocator *);
   void *(*ElementData)(DlistElement *le);
   int (*Equal)(const Dlist *l1,const Dlist *l2);
   int (*Erase)(Dlist *AL,const void *);
   int (*EraseAll)(Dlist *AL,const void *);
   int (*EraseAt)(Dlist *AL,size_t idx);
   int (*Finalize)(Dlist *AL);
   DlistElement *(*FirstElement)(Dlist *l);
   void *(*Front)(const Dlist *l);
   const ContainerAllocator *(*GetAllocator)(const Dlist *l);
   void *(*GetElement)(const Dlist *AL,size_t idx);
   size_t (*GetElementSize)(const Dlist *dl);
   unsigned (*GetFlags)(const Dlist *AL);
   Dlist *(*GetRange)(Dlist *l,size_t start,size_t end);
   int (*IndexOf)(const Dlist *AL,const void *SearchedElement,
        void *args,size_t *result);
   Dlist *(*Init)(Dlist *dlist,size_t elementsize);
   int (*InitIterator)(Dlist *,void *buf);
   Dlist *(*InitWithAllocator)(Dlist *L,size_t element_size,
          const ContainerAllocator *mm);
   Dlist *(*InitializeWith)(size_t elementSize, size_t n,
          const void *data);
   int (*InsertAt)(Dlist *AL,size_t idx,const void *newval);
   int (*InsertIn)(Dlist *l, size_t idx,Dlist *newData);
   DlistElement *(*LastElement)(Dlist *l);
   Dlist *(*Load)(FILE *stream, ReadFunction loadFn,void *arg);
   void *(*MoveBack)(DlistElement **pDlistElement);
   Iterator *(*NewIterator)(Dlist *);
   DlistElement *(*NextElement)(DlistElement *le);
   int (*PopBack)(Dlist *AL,void *result);
   int (*PopFront)(Dlist *AL,void *result);
   DlistElement *(*PreviousElement)(DlistElement *le);
   int (*PushBack)(Dlist *AL,const void *str);
   int (*PushFront)(Dlist *AL,const void *str);
   int (*RemoveRange)(Dlist *l,size_t start, size_t end);
   int (*ReplaceAt)(Dlist *AL,size_t idx,const void *newval);
   int (*Reverse)(Dlist *l);
   int (*RotateLeft)(Dlist *l, size_t n);
   int (*RotateRight)(Dlist *l,size_t n);
   int (*Save)(const Dlist *L,FILE *stream, SaveFunction saveFn,
        void *arg);
   int (*Select)(Dlist *src,const Mask *m);
   Dlist *(*SelectCopy)(const Dlist *src,const Mask *m);
   CompareFunction (*SetCompareFunction)(Dlist *l,CompareFunction fn);
   DestructorFunction (*SetDestructor)(Dlist *v,
                      DestructorFunction fn);
   int (*SetElementData)(Dlist *l, DlistElement *le,void *data);
   ErrorFunction (*SetErrorFunction)(Dlist *L,ErrorFunction);
   unsigned (*SetFlags)(Dlist *AL,unsigned flags);
   size_t (*Size)(const Dlist *dl);
   size_t (*Sizeof)(const Dlist *dl);
   size_t (*SizeofIterator)(const Dlist *);
   DlistElement *(*Skip)(DlistElement *l,size_t n);
   int (*Sort)(Dlist *l);
   Dlist *(*Splice)(Dlist *list,void *pos,Dlist *toInsert,
          int direction);
   Dlist *(*SplitAfter)(Dlist *l, DlistElement *pt);
   int (*UseHeap)(Dlist *L,const ContainerAllocator *m);
   int (*deleteIterator)(Iterator *);
} DlistInterface;


MoveBack

void *(*MoveBack)(ListElement **ppElement);

Description: Given the address of a pointer to an element, it returns a pointer to the data stored into that element and writes the address of the previous element into its argument ppElement. If ppElement is NULL it returns NULL . If *ppElement is NULL it also returns NULL .
Returns:A pointer to the data stored in the given element or NULL if the data can't be retrieved.

PopBack

    int (*PopBack)(List *L,void *result);

Description: Pops the element at the last position copying it to the result pointer. If the "result" pointer is  NULL , the last element is removed without any copying. Otherwise, the library supposes that result points to at least ElementSize bytes of contiguous storage.
Errors:
CONTAINER_ERROR_BADARG The list or the result pointer are  NULL .
CONTAINER_ERROR_READONLY The list is read only.
Returns: A positive value if an element was popped, zero if the list was empty, or a negative error code if an error occurred.
Example:

double d;
    int r = iList.PopBack(L,&d);
    if (r==0)
        printf("List empty\n");
    else if (r < 0) {
        printf("Error %d\n",r);
    else    printf("OK, popped value %g\n",d);


PreviousElement

DlistElement *(*PreviousElement)(DlistElement *le);

Description: Returns a pointer to the previous element in the list. If the input list is NULL it returns NULL .
Errors:
None.
Returns:The previous element or NULL .

PushBack

    int (*PushBack)(List *L,void *element);

Description: Inserts the given element at the last position of the list.
Errors:
CONTAINER_ERROR_BADARG The list or the element pointer are  NULL .
CONTAINER_ERROR_READONLY The list is read only.
CONTAINER_ERROR_NOMEMORY There is not enough memory to complete the operation.
Returns: A positive value if the operation completed, or a negative error code otherwise.
Example:

double d = 2.3;
    if (iList.PushBack(list,&d) < 0)
        printf("Error\n");


Splice

Synopsis:
Dlist *(*Splice)(Dlist *list, void *Pos, Dlist *toInsert,int direction);

Description: Inserts a list (parameter "toInsert") into another one (parameter "list") at the given position that should be an element of "list". The direction argument means to insert before the position if zero, after the position if not zero.
Errors:
CONTAINER_ERROR_BADARG The list, the list to be inserted or the element pointer are  NULL .
CONTAINER_ERROR_READONLY The list is read only.


3  The Vector interface: iVector

Figure
Vector vocabulary.
The "vector" container is an array that resizes to accommodate new elements. Access is always checked against the array bounds. Insertion and deletion of items are more expensive than in lists, and the cost increases linearly with the array size. Access is very cheap, since a multiplication suffices to get to any array position.
Most functions of the interface are shared with the list, Dlist and the other sequential containers.

3.0.1  Special arrays using a modified error function

Some functions of the iVector interface will examine the return value of the error function after an index error is detected. If the error function returns a pointer value different than  NULL , it will be assumed that that is the value of the API that should be returned to the user. This allows for the implementation of infinite arrays or arrays where an access into some element beyond the end of the array provokes an automatic resize operation of the array up to the required length.
In case of an index error, this container passes always a pointer to the container and the out of range index to the error function. The functions where the return value of the error function is used are:

3.1  The interface

typedef struct tagVectorInterface {
   int (*Add)(Vector *AL,const void *newval);
   int (*AddRange)(Vector *AL,size_t n,const void *newvalues);
   int (*Append)(Vector *AL1, Vector *AL2);
   int (*Apply)(Vector *AL,int (*Applyfn)(void *element,void * arg),
        void *arg);
   void *(*Back)(const Vector *AL);
   int (*Clear)(Vector *AL);
   Mask *(*CompareEqual)(const Vector *left,const Vector *right,
         Mask *m);
   Mask *(*CompareEqualScalar)(const Vector *left, const void *right,
         Mask *m);
   int (*Contains)(const Vector *AL,const void *element,
        void *ExtraArgs);
   Vector *(*Copy)(const Vector *AL);
   int (*CopyElement)(const Vector *AL,size_t idx,void *outbuf);
   void **(*CopyTo)(const Vector *AL);
   Vector *(*Create)(size_t elementsize,size_t startsize);
   Vector *(*CreateWithAllocator)(size_t elemsiz,size_t startsiz,
           const ContainerAllocator *mm);
   int (*Equal)(const Vector *first,const Vector *second);
   int (*Erase)(Vector *AL,const void *);
   int (*EraseAll)(Vector *AL,const void *);
   int (*EraseAt)(Vector *AL,size_t idx);
   int (*Finalize)(Vector *AL);
   void *(*Front)(const Vector *AL);
   const ContainerAllocator *(*GetAllocator)(const Vector *AL);
   size_t (*GetCapacity)(const Vector *AL);
   void **(*GetData)(const Vector *AL);
   void *(*GetElement)(const Vector *AL,size_t idx);
   size_t (*GetElementSize)(const Vector *AL);
   unsigned (*GetFlags)(const Vector *AL);
   Vector *(*GetRange)(const Vector *AL, size_t start, size_t end);
   Vector *(*IndexIn)(Vector *SC,Vector *AL);
   int (*IndexOf)(const Vector *AL,const void *data,void *ExtraArgs,
        size_t *result);
   Vector *(*Init)(Vector *r,size_t elementsize,size_t startsize);
   int (*InitIterator)(Vector *V,void *buf);
   Vector *(*InitializeWith)(size_t elementSize, size_t n,
           const void *Data);
   int (*Insert)(Vector *AL,void *);
   int (*InsertAt)(Vector *AL,size_t idx,void *newval);
   int (*InsertIn)(Vector *AL, size_t idx,Vector *newData);
   Vector *(*Load)(FILE *stream, ReadFunction readFn,void *arg);
   int (*Mismatch)(Vector *a1,Vector *a2,size_t *mismatch);
   Iterator *(*NewIterator)(Vector *AL);
   int (*PopBack)(Vector *AL,void *result);
   int (*PushBack)(Vector *AL,const void *str);
   int (*RemoveRange)(Vector *SC,size_t start,size_t end);
   int (*ReplaceAt)(Vector *AL,size_t idx,void *newval);
   int (*Reserve)(Vector *src,size_t newCapacity);
   int (*Resize)(Vector *AL,size_t newSize);
   int (*Reverse)(Vector *AL);
   int (*RotateLeft)(Vector *V,size_t n);
   int (*RotateRight)(Vector *V,size_t n);
   int (*Save)(const Vector *AL,FILE *stream, SaveFunction saveFn,
        void *arg);
   int (*SearchWithKey)(Vector *vec,size_t startByte,size_t sizeKey,
        size_t startIndex,void *item,size_t *result);
   int (*Select)(Vector *src,const Mask *m);
   Vector *(*SelectCopy)(Vector *src,Mask *m);
   int (*SetCapacity)(Vector *AL,size_t newCapacity);
   CompareFunction (*SetCompareFunction)(Vector *l,
                   CompareFunction fn);
   DestructorFunction (*SetDestructor)(Vector *v,
                      DestructorFunction fn);
   ErrorFunction (*SetErrorFunction)(Vector *AL,ErrorFunction);
   unsigned (*SetFlags)(Vector *AL,unsigned flags);
   size_t (*Size)(const Vector *AL);
   size_t (*Sizeof)(const Vector *AL);
   size_t (*SizeofIterator)(const Vector *);
   int (*Sort)(Vector *AL);
   int (*deleteIterator)(Iterator *);
} VectorInterface;

3.2  The API


Add

    int (*Add)(Vector *AL,void *data);
    int (*Add)(TYPEVector, TYPE data);

Description: Adds the given element to the end of the container. It is the same operations as the PushBack operation. It is assumed that "data" points to a contiguous memory area of at least ElementSize bytes. Returns a value greater than zero if the addition completed successfully, a negative error code otherwise.
Errors:
CONTAINER_ERROR_BADARG The vector or the data pointers are  NULL .
CONTAINER_ERROR_READONLY The vector is read-only. No modifications allowed.
CONTAINER_ERROR_NOMEMORY Not enough memory to complete the operation.
Returns: A positive number if the operation completed, negative error code otherwise.
Example:

Vector *AL = iVector.Create(sizeof(double));
    double data = 4.5;
    int result = iVector.Add(AL,&data);
    if (result < 0) { /* Error handling */ }

    // Template version
    #include "doubleVector.h"
    doubleVector *AL = idoubleVector.Create();
    int result = idoubleVector.Add(AL,4.5);
    if (result < 0) { /* Error handling */ }


AddRange

    int (*Add)(Vector *AL,size_t n, void *data);
    int (*Add)(TYPEVector *AL,size_t n, TYPE *data);

Description: Adds the n given elements to the end of the container. It is the same operations as the PushBack operation. It is assumed that "data" points to a contiguous memory area of at least n*ElementSize bytes. Returns a value greater than zero if the addition completed successfully, a negative error code otherwise. If n is zero no error is issued even if the array pointer or the data pointer are  NULL .
Errors:
CONTAINER_ERROR_BADARG The vector or the data pointers are  NULL , and n is not zero.
CONTAINER_ERROR_READONLY The vector is read-only. No modifications allowed.
CONTAINER_ERROR_NOMEMORY Not enough memory to complete the operation.
Returns: A positive number if the operation completed, negative error code otherwise.
Example:

Vector *AL;
    double data[] = {4.5, 4.6, 4.7 };
    AL = iVector.Create(sizeof(double),5);
    int result = iVector.Add(AL,3, data);
    if (result < 0) { /* Error handling */ }
    // Template version
    #include "doubleVector.h"
    doubleVector *AL = idoubleVector.Create(5);
    double data[] = {4.5, 4.6, 4.7 };
    int result = idoubleVector.Add(AL,3, data);
    if (result < 0) { /* Error handling */ }


Append

  int (*Append)(Vector *AL1, Vector *AL2);

Description: Adds all elements of AL2 at the end of the first container AL1. The two vectors should contain elements of the same type.
Errors:
CONTAINER_ERROR_BADARG One of the Vector pointer is  NULL .
CONTAINER_ERROR_READONLY The first argument is read-only. No modifications allowed.
CONTAINER_ERROR_NOMEMORY Not enough memory to complete the operation.
Returns: A positive number if the operation completed, negative error code otherwise.

Apply

    int (*Apply)(Vector l,int (Applyfn)(void *,void *),void *arg);
    int (*Apply)(TYPEVector l,int (Applyfn)(TYPE,void *),void *arg);

Description: Will call the given function for each element of the array. The first argument of the callback function receives an element of the array. The second argument of the callback is the arg argument that the Apply function receives and passes to the callback. This way some context can be passed to the callback, and from one element to the next. Note that the result of the callback is not used. This allows all kinds of result types to be accepted after a suitable cast. If the array is read-only, a copy of the element will be passed to the callback function.
Errors:
CONTAINER_ERROR_BADARG Either list or Applyfn are  NULL .
CONTAINER_ERROR_NOMEMORY The list is read-only and there is no more memory to allocate the buffer to copy each element.
Returns: A positive value if no errors or a negative error code.
Example:

static int Callback(void *pelement,void *pResult)
    {
        double *p = pelement;
        double *result = pResult;
        *result += *p;
        return 1;
    }
    double AddVector(Vector *l) {
        double sum = 0;
        Vector *alist = iVector.Create(sizeof(double),2);
        double d = 2;
        iVector.Add(alist,&d);
        d = 3;
        iVector.Add(alist,&d);
        iList.Apply(alist,Callback,&sum);
        // Here sum should be 5.
        return sum;
    }
    // ------------------------------Template version
    static int Callback(double element,void *pResult)
    {
        double *result = pResult;
        *result += element;
        return 1;
    }
    double AddVector(Vector *l) {
        double sum = 0;
        doubleVector *alist = idoubleVector.Create(2);
        idoubleVector.Add(alist,2);
        idoubleVector.Add(alist,3);
        idoubleList.Apply(alist,Callback,&sum);
        // Here sum should be 5.
        return sum;
    }



Back

void *(*Back)(const Vector *v);
TYPE  (*Back)(const TYPEVector *v);

Description: Returns the last element of the given vector or  NULL if the vector is empty.
Errors:
CONTAINER_ERROR_BADARG The vector pointer is  NULL .
CONTAINER_ERROR_READONLY The vector is read only.
Returns: The last element or  NULL if the vector is empty or an error occurs.

Clear

    int (*Clear)(Vector *l);

Description: Erases all stored data and releases the memory associated with it. The vector header will not be destroyed, and its contents will be the same as when the array was initially created. It is an error to use this function when there are still active iterators for the container.
Returns: The result is greater than zero if successful, or an error code if an error occurs.
Errors:
CONTAINER_ERROR_BADARG The vector pointer is  NULL .
CONTAINER_ERROR_READONLY The vector is read only.
Example:

Vector *Al;
    int m = iVector.Clear(Al);


CompareEqual

Mask *(*CompareEqual)(ValArray *left,ValArray *right,
                       Mask bitarray);

Description: Assigns to each element of the mask the result of comparing the corresponding elements of the left and right arrays. Conceptually this operation is:
Mask[i] = (left[i] == right[i])

If the mask argument is  NULL it will be allocated and returned. The allocator used is the global memory manager. If it is not  NULL it should contain at least enough positions to hold the data. If it doesn't it will be finalized using iMask.Finalize and allocated with the necessary length.
Errors:
CONTAINER_ERROR_NOMEMORY . The given byte array argument was  NULL but there is no memory to allocate the result.
CONTAINER_ERROR_BADARG The given mask hasn't enough positions available.
CONTAINER_ERROR_INCOMPATIBLE The arrays are of different length.
Returns:A pointer to the bitarray passed or allocated, or  NULL if an error occurs.

CompareEqualScalar

Mask *(*CompareEqual)(const Vector *left,const void *right,
                       Mask *bitarray);

Description: Assigns to each element of the mask the result of comparing the elements of the left array with the right argument. Conceptually this operation is:
bit[i] = (left[i] == right)

If the bitarray argument is  NULL it will be allocated and returned. The allocator used is the one from the left argument. If it is not  NULL it will be allocated if its length is less than the needed length.
Errors:
CONTAINER_ERROR_NOMEMORY . The given byte array argument was  NULL but there is no memory to allocate the result.
Returns:A pointer to the bitarray or  NULL if an error occurs.

Contains

    int (*Contains)(Vector *a,void *data);
    int (*Contains)(TYPEVector *a,TYPE data);

Description: Searches the given data in the array. The "data" argument is supposed to point to an element at least ElementSize bytes. The array's comparison function is used for determining if two elements are equal. This comparison function defaults to memcmp.
Errors:
CONTAINER_ERROR_BADARG Either array or data are  NULL .
Returns: One if the given data is stored in the array, zero otherwise. If either the data pointer or the array pointer are  NULL it returns a negative error code.
Example:

Vector *a = iVector.Create(sizeof(double),2);
    double data = 78.67;
    int r = iVector.Contains(a,&data);
    // ---------------------------Template version
    doubleVector *a = iVector.Create(2);
    int r = iVector.Contains(a,78.67);


Copy

    Vector *(*Copy)(Vector *A);

Description: A shallow copy of the given array is performed. Only ElementSize bytes will be copied for each element. If the element contains pointers, only the pointers are copied, not the objects they point to. The new memory will be allocated using the given array's allocator.
Errors:
CONTAINER_ERROR_NOMEMORY There is not enough memory to complete the operation.
CONTAINER_ERROR_BADARG The given vector pointer is  NULL .
Example:

Vector *newVector,*OldVector;
    newVector = iVector.Copy(OldVector);


CopyElement

    Vector *(*Copy)(const Vector *A,size_t idx,void *result);

Description: The element at the given index is copied into the output buffer that should be at least big enough to hold one element. If the index is bigger than the number of elements the error function is called. If it returns a valid pointer (not  NULL ) it is assumed that this is a pointer to a valid element that should be copied into the output buffer.
Errors:
If the idx argument is out of range the CopyElement function calls the vector error function. If the error function returns a result different than  NULL , the function assumes that it is a pointer to some value that should be copied into the result buffer.
CONTAINER_ERROR_INDEX The index is out of bounds.
CONTAINER_ERROR_BADARG The given vector pointer is  NULL .
Example:

Vector *V;
    char buffer[16];
    newVector = iVector.CopyElement(V,3,buffer);


CopyTo

void **(*CopyTo)(Vector *AL);

Description: Copies the whole contents of the given array into a table of pointers to newly allocated elements, finished by a  NULL pointer.
Errors:
CONTAINER_ERROR_BADARG The iterator pointer is  NULL .
CONTAINER_ERROR_NOMEMORY There is not enough memory to complete the operation.
Returns: A pointer to a table of pointers or  NULL if an error occurs.

deleteIterator

    int deleteIterator(Iterator *it);

Description: Reclaims the memory used by the given iterator object
Returns: Integer smaller than zero with error code or a positive number when the operation completes.
Errors:
CONTAINER_ERROR_BADARG The iterator pointer is  NULL .

Create

    Vector *(*Create)(size_t element_size,size_t startsize);
    TYPEVector *(*Create)(size_t startsize);

Description: The creation function returns an empty array, initialized with all the default values. The current memory manager is used to allocate the space needed for the header. The array is supposed to contain elements of the same size. If the elements you want to store are of different size, use a pointer to them, and create the array with sizeof(void *) as the size parameter.
Returns: A pointer to a newly created array or  NULL if an error occurs.
Errors:
CONTAINER_ERROR_NOMEMORY There is not enough memory to complete the operation.
CONTAINER_ERROR_BADARG The given element size is zero.
Any errors provoke the call the current default error function of the library since this is the creation function.
Example:

Vector *DArray = iVector.Create(sizeof(double),100);


CreateWithAllocator

Vector *(*CreateWithAllocator)(size_t elementsize,
                    size_t startsize,ContainerAllocator *allocator);

Description: This function is identical to Create with the difference that it accepts a pointer to an allocator object. Actually, Create can be written as:
return CreateWithAllocator(elementsize,startsize,CurrentAllocator);

Equal

    int (*Equal)(Vector *first,Vector *second);

Description: Compares the given arrays. If they differ in their length, flags, or element size they compare unequal. If any of their elements differ, they compare unequal. If both first and second are  NULL they compare equal.
Errors:
None
Returns: The result is one if the vectors are equal, zero otherwise.

Erase

    int (*Erase)(Vector *AL,void *data);
    int (*Erase)(TYPEVector *AL,TYPE data);

Description: Removes from the vector the element that matches the given data, that is assumed to be a pointer to an element.
Returns: A negative error code if an error occurred, or a positive value that indicates that at least one match was found and the elements were removed. If the element is not in the vector the result value is CONTAINER_ERROR_NOTFOUND .
Errors:
CONTAINER_ERROR_BADARG One or both arguments are  NULL .
Example:

double d = 2.3;
    Vector *AL;
    int r = iVector.Erase(AL,&d);
    if (r > 0)
        printf("2.3 erased|n");
    else if (r == 0)
        printf("No element with value 2.3 present\n");
    else
        printf("error code %d\n",r);


EraseAll

    int (*EraseAll)(Vector *v,const void *data);
    int (*EraseAll)(TYPEVector *v,const TYPE data);    

Description: Removes from the list all elements that match the given data, that is assumed to be a pointer to an element.
Returns: A negative error code if an error occurred, or a positive value that indicates that at least a match was found and the element was removed. If the element is not in the list the result is CONTAINER_ERROR_NOTFOUND .
Errors:
CONTAINER_ERROR_BADARG One or both arguments are  NULL .

EraseAt

    int (*EraseAt)(Vector *AL,size_t idx);

Description: Removes from the array the element at the given position.
Returns: A negative error code if an error occurred or a positive value that indicates that the element was removed.
Errors:
CONTAINER_ERROR_BADARG The given vector pointer is  NULL .
CONTAINER_ERROR_INDEX The given position is out of bounds.
Example:

Vector *AL;
    int r = iVector.EraseAt(AL,2);
    if (r > 0)
        printf("Element at position 2 erased\n");
    else
        printf("Error code %d\n",r);


Finalize

    int (*Finalize)(Vector *AL);

Description: Reclaims all memory used by the container, including the array header object itself.
Errors:
CONTAINER_ERROR_BADARG The given vector pointer is  NULL .
CONTAINER_ERROR_READONLY The container is read-only. No modifications allowed.
Returns: A positive value means the operation completed. A negative error code indicates failure.
Example:

Vector *AL;
    int r = iVector.Finalize(AL);
    if (r < 0) { /* error handling */ }


Front

void *(*Front)(const Vector *v);
TYPE  (*Front)(const TYPEVector *v);

Description: Returns the first element of the given vector or  NULL if the vector is empty.
Errors:
CONTAINER_ERROR_BADARG The vector pointer is  NULL .
CONTAINER_ERROR_READONLY The vector is read only.
Returns: The first element or  NULL if the vector is empty or an error occurs.

GetCapacity

    size_t (*GetCapacity)(const Vector *AL);

Description: Returns the number of elements the array can hold before it needs to reallocate its data35.
Errors:
CONTAINER_ERROR_BADARG The given array is  NULL .
Returns: The array capacity or zero if there was an error.

GetElementSize

    size_t (*GetElementSize)(const Vector *AL);

Description: Retrieves the size of the elements stored in the given vector. Note that this value can be different than the value given to the creation function because of alignment requirements. In template containers this function returns sizeof(TYPE).
Errors:
CONTAINER_ERROR_BADARG The given vector pointer is  NULL .
Returns: The element size.
Example:

Vector *AL;
    size_t siz = iVector.GetElementSize(AL);


GetData

void **(*GetData)(const Vector *AL);

Description: Returns a pointer to the data area of the container, or  NULL if an error occurs.
Errors:
CONTAINER_ERROR_READONLY The container is read-only.
CONTAINER_ERROR_BADARG The given pointer is  NULL
Returns:The pointer to the array's data or  NULL .

GetElement

    void *(*GetElement)(Vector *AL,size_t idx);
    TYPE *(*GetElement)(TYPEVector *AL,size_t idx);

Description: Returns a read only pointer to the element at the given index, or  NULL if the operation failed. This function will return  NULL if the vector is read only. If the index is greater than the number of elements, the error function will be called. If the error function returns a valid pointer (not  NULL ) the result will be that pointer. This allows the construction of infinite arrays, or sparse arrays, etc. By default, the error function returns always  NULL .
Use the CopyElement function to get a read/write copy of an element of the vector.
Errors:
CONTAINER_ERROR_BADARG The given array pointer is  NULL .
CONTAINER_ERROR_INDEX The given position is out of bounds.
CONTAINER_ERROR_READONLY The array is read only.
Example:

Vector *AL;
    double *d = iVector.GetElement(AL,3);
    if (d == NULL) { /* Error handling */ }


GetFlags / SetFlags

    unsigned (*GetFlags)(Vector *AL);
    unsigned (*SetFlags)(Vector *AL,unsigned newFlags);

Description: GetFlags returns the state of the container flags, SetFlags sets the flags to a new value and returns the old value.
The Vector container supports the following flags:
CONTAINER_READONLY If this flag is set, no modifications to the container are allowed, and the Clear and Finalize functions will not work. The GetElement function will always return  NULL . You should use the CopyElement function to access the data

GetRange

    Vector *(*GetRange)(Vector *AL,size_t start,size_t end);

Description: Selects a series of consecutive elements starting at position start and ending at position end. Both the elements at start and end are included in the result. If start > end or start > Size(AL),  NULL is returned. If end is bigger than the number of elements in the vector AL, only elements up to the number of elements will be used. The selected elements are copied into a new array. The original array remains unchanged.
Errors:
CONTAINER_ERROR_BADARG The given array pointer or the element given are  NULL
Returns:A pointer to a new vector containing the selected elements or  NULL if an error occurs.
Example:

Vector *AL;
    Vector *range = iVector.GetRange(AL,2,5);
    if (range == NULL) { /* Error handling */ }


IndexIn

Vector *(*IndexIn)(Vector *SC,Vector *AL);

Description: Returns an array built from indexing the first argument ("SC") with the array of indexes ÄL" that should be an array of size_t elements. The number of elements of the resulting array is equal to the number of elements of the indexes array.
Errors:
CONTAINER_ERROR_BADARG The given array pointer or the indexes array are  NULL .
CONTAINER_ERROR_INDEX Any given position is out of bounds.
CONTAINER_ERROR_NOMEMORY There is not enough memory to complete the operation.
Returns: A new array or  NULL if an error occurs. No partial results are returned. If any index is out of bounds the whole operation fails.

IndexOf

int (*IndexOf)(Vector *l,void *data,void *ExtraArgs,size_t *result);
int (*IndexOf)(TYPEVector *l,TYPE data,void *ExtraArgs,size_t *result);

Description: Searches for an element in the array. If found its zero based index is returned in the pointer "result". Otherwise the result of the search is CONTAINER_ERROR_NOTFOUND . The ëxtraArgs" argument will be passed to the comparison function, that is used to compare elements.
Errors:
CONTAINER_ERROR_BADARG The given array pointer or the element given are  NULL .
Returns: A positive number if the element is found, or a negative number containing an error code or the negative constant CONTAINER_ERROR_NOTFOUND .
Example:

Vector *AL;
    double data = 6.8;
    size_t pos;
    int r = iVector.IndexOf(AL,&data,NULL,&pos);
    if (r == CONTAINER_ERROR_NOTFOUND)
        printf("Not found\n");


InsertAt

    int (*InsertAt)(Vector *AL,size_t idx,void *newData);
    int (*InsertAt)(TYPEVector *AL,size_t idx,TYPE newData);

Description: Inserts the new element. The new element will have the given index, that can go from zero to the vector count inclusive, i.e. one more than the number of elements in the vector. If the index is out of bounds, the vector error function is called. If the function returns a valid pointer (a pointer different than  NULL ) it will be assumed that the vector should be increased to the index given. The vector is resized and the data is inserted at the requested position. This allows the implementation of infinite vectors, sparse vectors, and other data structures.
Errors:
When this API detects an index error, it calls the error function. If the error function returns a pointer different from  NULL , the API will extend the requested vector to make possible the insertion. If the API is unable to extend the vector and error is returned and the vector remains unchanged.
CONTAINER_ERROR_BADARG The given vector pointer or the element given are  NULL .
CONTAINER_ERROR_READONLY The vector is read only.
CONTAINER_ERROR_INDEX The given position is out of bounds.
CONTAINER_ERROR_NOMEMORY There is not enough memory to complete the operation.
Returns: A positive value if the operation succeeded, or a negative error code if the operation failed.
Example:

double d = 2.7;
    Vector *AL = iVector.Create(sizeof(double),3);
    int r = iVector.InsertAt(AL,2,&d);
    if (r < 0) { /* Error handling */ }
    else { /* Normal processing */  }
    // --------------------------Template version
    doubleVector *AL = idoubleVector.Create(3);
    int r = idoubleVector.InsertAt(AL,2,2.7);
    if (r < 0) { /* Error handling */ }
    else { /* Normal processing */  }



InsertIn

    int (*InsertIn)(Vector *Destination, size_t pos, Vector *src);

Description: Inserts the array given in its third argument at the given position in the array pointed to by its first argument. The data is copied, and the source argument is not modified in any way. Both arrays must have elements of the same type. The library only tests the size of each one.
Errors:
CONTAINER_ERROR_BADARG The source or the destination vectors are  NULL .
CONTAINER_ERROR_READONLY The destination vector is read only.
CONTAINER_ERROR_INDEX The given position is out of bounds.
CONTAINER_ERROR_NOMEMORY There is not enough memory to complete the operation.
CONTAINER_ERROR_INCOMPATIBLE The vectors store elements of different size.
Returns: A positive value if the operation succeeded, or a negative error code if the operation failed.
Example:

#include <containers.h>
static void PrintVector(Vector *AL)
{
    size_t i;
    printf("Count %ld, Capacity %ld\n",(long)iVector.Size(AL),
                                  (long)iVector.GetCapacity(AL));
    for (i=0; i<iVector.Size(AL);i++) {
        printf("%g ",*(double *)iVector.GetElement(AL,i));
    }
    printf("\n");
}

static void FillVector(Vector * AL,int start)
{
    size_t i;

    for (i=0; i<10;i++) {
        double d = i+start;
        iVector.Add(AL,&d);
    }
}

int main(void)
{
    Vector *AL = iVector.Create(sizeof(double),10);
    Vector *AL1 =iVector.Create(sizeof(double),10);
    FillVector(AL,0);
    FillVector(AL1,100);
    iVector.InsertIn(AL,5,AL1);
    PrintVector(AL);
    return 0;
}
OUTPUT:
Count 20, Capacity 20
0 1 2 3 4 100 101 102 103 104 105 106 107 108 109 5 6 7 8 9
---------------------------------------------Template version
#include <containers.h>
#include "doublevector.h"
static void PrintVector(doubleVector *AL)
{
    size_t i;
    printf("Count %ld, Capacity %ld\n",(long)iVector.Size(AL),
                                  (long)iVector.GetCapacity(AL));
    for (i=0; i<iVector.Size(AL);i++) {
        printf("%g ",idoubleVector.GetElement(AL,i));
    }
    printf("\n");
}

static void FillVector(Vector * AL,int start)
{
    size_t i;

    for (i=0; i<10;i++) {
        idoubleVector.Add(AL,i+start);
    }
}

int main(void)
{
    Vector *AL = idoubleVector.Create(10);
    Vector *AL1 =idoubleVector.Create(10);
    FillVector(AL,0);
    FillVector(AL1,100);
    iVector.InsertIn(AL,5,AL1);
    PrintVector(AL);
    return 0;
}
OUTPUT:
Count 20, Capacity 20
0 1 2 3 4 100 101 102 103 104 105 106 107 108 109 5 6 7 8 9



Load

   Vector *(*Load)(FILE *stream,ReadFunction readFn,void *arg);

Description: Reads an array previously saved with the Save function from the stream pointed to by stream. If readFn is not  NULL , it will be used to read each element. The arg argument will be passed to the read function. If the read function is  NULL , this argument is ignored and a default read function is used.
Errors:
CONTAINER_ERROR_BADARG The given stream pointer is  NULL .
CONTAINER_ERROR_NOMEMORY There is not enough memory to complete the operation.
Returns: A new array or  NULL if the operation could not be completed. Note that the function pointers in the array are NOT saved, nor any special allocator that was in the original vector. Those values will be the values by default. To rebuild the original state the user should replace the pointers again in the new array.

NewIterator

    Iterator *(*NewIterator)(Vector *AL);

Description: Allocates and initializes a new iterator object to iterate this array.
Errors:
If no more memory is available it returns  NULL .
Returns: A pointer to a new iterator or  NULL if there is no more memory left.
Example:

Vector *AL;
    Iterator *it = iVector.NewIterator(AL);
    double *d;
    for (d=it->GetFirst(it); d != NULL; d = it->GetNext(it)) {
        double val = *d;
        // Work with the value here
    }
    iVector.deleteIterator(it);


Mismatch

int (*Mismatch)(const Vector *a1,const Vector *a2,
                                         size_t *mismatch);

Description: Returns the index of the first element that is different when comparing both arrays in the passed pointer mismatch. If one array is shorter than the other the comparison stops when the last element from the shorter array is compared. The comparison stops when the first difference is spotted.
Errors:
CONTAINER_ERROR_BADARG Any of the arguments is  NULL .
CONTAINER_ERROR_INCOMPATIBLE The containers have different comparison functions or store elements of different size.
Returns: If a mismatch is found the result is greater than zero and the mismatch argument will contain the index of the first element that compared unequal. This will be always the case for arrays of different length.
If both arrays are the same length and no differences are found the result is zero and the value pointed to by the mismatch argument is one more than the length of the arrays.
If an error occurs, a negative error code is returned. The mismatch argument contains zero.

PopBack

    int (*PopBack)(Vector *AL,void *result);
    int (*PopBack)(TYPEVector *AL,TYPE *result);

Description: Copies the last element into the given result buffer and deletes the element from the container. If the result buffer is  NULL , no copy is performed.
Errors:
CONTAINER_ERROR_BADARG The array is  NULL .
CONTAINER_ERROR_READONLY The array is read only.
Returns: A negative value if an error occurs, zero if the array is empty or greater than zero if the operation succeeded.

RemoveRange

int (*RemoveRange)(Vector *SC,size_t start,size_t end);

Description: Removes all elements having an index equal or greater than start and less than end . If end is greater than the number of elements in the collection it will be adjusted to one more than the number of elements. If start is bigger than end the range is still valid and starts with the value of end and ends with the value of start .
Errors:
CONTAINER_ERROR_BADARG The vector pointer is  NULL .
Returns: Zero if the vector is empty. Otherwise returns a positive number for success, a negative error code in case of an error.

ReplaceAt

    int (*ReplaceAt)(Vector *AL,size_t idx,void *newData);
    int (*ReplaceAt)(TYPEVector *AL,size_t idx,TYPE newData);

Description: Replaces the array element at position idx with the new data starting at the position pointed to by "newData" and extending ElementSize bytes.
Errors:
CONTAINER_ERROR_BADARG The array or the new element pointer are  NULL .
CONTAINER_ERROR_READONLY The array is read only.
CONTAINER_ERROR_INDEX The given position is out of bounds.
Returns: A negative error code if an error occurs, or a positive value if the operation succeeded.
Example:

Vector *AL = iVector.Create(sizeof(double),2);
    double d = 6.7;
    int r = iVector.ReplaceAt(AL,2,&d);
    if (r < 0) { /* Error handling */ }
    // --------------------------------Template version
    doubleVector *AL = idoubleVector.Create(2);
    int r = idoubleVector.ReplaceAt(AL,2,6.7);
    if (r < 0) { /* Error handling */ }


Reserve

int (*Reserve)(Vector *AL, size_t newSize);

Description: The capacity of the vector is increased at least by the given amount in preparation for a planned increase in elements. The size is in element units.
Errors:
CONTAINER_ERROR_BADARG The array pointer is  NULL .
CONTAINER_ERROR_READONLY The array is read only.
CONTAINER_ERROR_NOMEMORY Not enough memory is available
Returns:A positive number if the space could be reserved, a negative error code if not.

Resize

int (*Resize)(Vector *AL, size_t newSize);

Description: Resizes the given vector to the new capacity, expressed in element units. If the new capacity is smaller than the elements in the vector some elements will be erased. For each erased element its destructor (if any) is called. If the requested newSize argument is equal to the number of elements in the container, all storage beyond what is needed to store exactly newSize elements, is released. This allows to signal the library that the extra storage is no longer needed36.
iVector.Resize(vec,iVector.Size(vec));

Obviously under some implementations it is maybe impossible to allocate exactly the space needed, for instance because space can be only allocated in chunks bigger than the element size of the vector. In those cases only a "best effort" will be done and the used storage will be reduced to a minimum. If an error occurs, and the container can't be resized, no changes occur and the container is left unmodified.
Errors:
CONTAINER_ERROR_BADARG The array pointer is  NULL .
CONTAINER_ERROR_READONLY The array is read only.
CONTAINER_ERROR_NOMEMORY Not enough memory is available
Returns: A negative error code if an error occurs, or a positive value if the operation succeeded. If the requested capacity is equal to the current capacity the result is zero.

Reverse

int (*Reverse)(Vector *AL);

Description: Reverses the order of the elements of the given Vector.
Errors:
CONTAINER_ERROR_BADARG The array pointer is  NULL .
CONTAINER_ERROR_READONLY The array is read only.
CONTAINER_ERROR_NOMEMORY Not enough memory for intermediate storage available
Returns: A negative error code if an error occurs, or a positive value if the operation succeeded.

RotateRight

    int (*RotateRight)(Vector *src,size_t n);

Description: Rotates right the array by the indicated amount. The last n elements will be written to the start of the array, and the rest will be shifted right.
Errors:
CONTAINER_ERROR_BADARG The array pointer is  NULL .
CONTAINER_ERROR_READONLY The array is read only.
CONTAINER_ERROR_NOMEMORY Not enough memory is available
Returns:A positive number if something was moved, zero otherwise (the input was zero or a modulo of the array size).

RotateLeft

    int (*RotateLeft)(Vector *src,size_t n);

Description: Rotates left the array by the indicated amount. The first n elements will be written to the end of the array, and the rest will be shifted left to fill the empty n places.
Errors:
CONTAINER_ERROR_BADARG The array pointer is  NULL .
CONTAINER_ERROR_READONLY The array is read only.
CONTAINER_ERROR_NOMEMORY Not enough memory for intermediate storage available
Returns:A positive number if something was moved, zero otherwise (the input was zero or a modulo of the array size), or an error code less than zero if an error occurs.

Save

int (*Save)(const Vector *AL, FILE *out, SaveFunction Fn, void *arg);

Description: The contents of the given vector are saved into the given stream. If the save function pointer is not  NULL , it will be used to save the contents of each element and will receive the arg argument passed to Save, together with the output stream. Otherwise a default save function will be used and arg will be ignored. The output stream must be opened for writing and must be in binary mode.
Errors:
CONTAINER_ERROR_BADARG The array pointer or the stream pointer are  NULL . EOF A disk input/output error occurred.
Returns: A positive value if the operation completed, a negative value or EOF otherwise.
Example:

Vector *AL;
    FILE *outFile;
    if (iVector.Save(AL,outFile,NULL,NULL) < 0) { 
        /* Handle error here */
    }


SearchWithKey

int (*SearchWithKey)(Vector *vec,size_t startByte,size_t sizeKey,
                     size_t startIndex,void *item, size_t*result);

Description: This function searches the vector for a match in a region of the stored objects, ignoring the rest of the data. It will start at the object with startIndex and compare (using memcmp) the specified region of each object. The region of interest within the stored object is specified by a byte offset and a size. If a match is found the search stops and the zero based index of the object will be written into the result pointer. If the index of the start of the iteration is bigger than the number of elements in the array nothing is searched and the result is zero.
If the sum of startByte and sizeKey is bigger than the size of the elements stored, the comparison will start at the given offset but will stop at the end of the stored element.
Errors:
CONTAINER_ERROR_BADARG The array or the item element pointers are  NULL .
Returns: A negative error code if an error occurs, zero if no match was found, or a positive number indicating that a match was found. The given index pointer is modified only if a match is found.

Select

int (*Select)(Vector *v,Mask *m);

Description: Using the given mask, the elements where the corresponding mask element is zero are eliminated, those with a mask value different of zero are retained. The mask must have the same length as the array.
Errors:
CONTAINER_ERROR_INCOMPATIBLE The mask and the array are of different length.
Returns:A positive value for success, or a negative error code otherwise.

SelectCopy

Vector *(*SelectCopy)(Vector *v,Mask *m);

Description: Using the given mask, the elements where the corresponding mask element is different from zero are copied into a new array, those with a mask value different of zero are ignored. The mask must have the same length as the array.
Errors:
CONTAINER_ERROR_INCOMPATIBLE The mask and the array are of different length.
Returns:A positive value for success, or a negative error code otherwise.

SetCapacity

int (*SetCapacity)(Vector *AL,size_t newCapacity);

Description: Resizes the given Vector to a new value. The new capacity means there will be that number of elements allocated, avoiding costly resizing operations when new elements are added to the Vector. If the number given is less than the number of elements present in the array, elements are discarded from the end of the array.
Errors:
CONTAINER_ERROR_BADARG The Vector pointer is  NULL .
CONTAINER_ERROR_READONLY The array is read only.
Returns:A positive value if resizing was completed, a negative error code otherwise.

SetCompareFunction

CompareFunction (*SetCompareFunction)(Vector *AL,
                                         CompareFunction f);

Description: if the f argument is non  NULL , it sets the array comparison function to f.
Errors:
CONTAINER_ERROR_BADARG The array pointer is  NULL .
CONTAINER_ERROR_READONLY The array is read only and the function argument is not  NULL .
Returns: The old value of the comparison function.
Example:

ErrorFunction fn,newfn;
    Vector *AL;
    fn = iVector.SetErrorFunction(AL,newfn);


SetDestructor

DestructorFunction (*SetDestructor)(Vector *v,DestructorFunction fn);

Description: Sets the destructor function to its given argument. If the function argument is  NULL nothing is changed and the call is interpreted as a query since the return value is the current value of the destructor function. If the vector argument is  NULL , the result is  NULL .
Returns:The old value of the destructor.

SetErrorFunction

    ErrorFunction (*SetErrorFunction)(Vector *V,ErrorFunction); 

Description: Replaces the current error function for the given vector with the new error function if the ErrorFunction parameter is different from  NULL . If the V parameter is  NULL the function returns the value of the current default error function.
Errors:
CONTAINER_ERROR_BADARG The vector pointer is  NULL .
CONTAINER_ERROR_READONLY The vector is read only and the function argument is not  NULL .
Returns: The old value of the error function or  NULL if there is an error.

Size

    size_t (*Size)(const Vector *AL);

Description: Returns the number of elements stored in the array.
Example:

Vector *AL;
    size_t elem = iVector.Size(AL);


Sizeof

    size_t (*Sizeof)(Vector *AL);

Description: Returns the total size in bytes of the vector, including the header, and all data stored in it. If the argument is  NULL , the size of the header only is returned.
Returns: The number of bytes used by the vector or the size of the Vector header if the argument is  NULL .
Example:

Vector *AL;
    size_t  size = iVector.Sizeof(AL);


Sort

    int Sort(Vector *AL);

Description: Sorts the given array using the its comparison function. The order of the original array is destroyed. You should copy it if you want to preserve it.
Returns: A positive number if sorting succeeded, a negative error code if not.
Example:

Vector *AL;
    if (iVector.Sort(AL) < 0) { /* Error handling */ }




4  The bit-string container: iBitString

Figure
BitString vocabulary. Specific items are in bold.
A bit string is a derivation from the Vector container, specialized to hold a sequence of bits. It is a sequential container designed to save space in the storage of boolean values 37.

4.1  The interface

typedef struct tagBitStringInterface {
   int (*Add)(BitString *BitStr,int);
   int (*AddRange)(BitString *b, size_t bitSize, void *data);
   BitString *(*And)(BitString *bsl,BitString *bsr);
   int (*AndAssign)(BitString *bsl,BitString *bsr);
   int (*Append)(BitString *left,BitString *right);
   int (*Apply)(BitString *BitStr,int (*Applyfn)(int ,void * arg),
        void *arg);
   uintmax_t (*BitBlockCount)(BitString *b);
   int (*BitLeftShift)(BitString *bs,size_t shift);
   int (*BitRightShift)(BitString *bs,size_t shift);
   int (*Clear)(BitString *BitStr);
   int (*Contains)(BitString *BitStr,BitString *str,void *ExtraArgs);
   BitString *(*Copy)(BitString *);
   int (*CopyBits)(BitString *bitstr,void *buf);
   BitString *(*Create)(size_t bitlen);
   BitString *(*CreateWithAllocator)(size_t startsiz,
              const ContainerAllocator *mm);
   int (*Equal)(BitString *bsl,BitString *bsr);
   int (*Erase)(BitString *BitStr,int bit);
   int (*EraseAt)(BitString *BitStr,size_t idx);
   int (*Finalize)(BitString *BitStr);
   const ContainerAllocator *(*GetAllocator)(const BitString *b);
   size_t (*GetCapacity)(BitString *BitStr);
   unsigned char *(*GetData)(BitString *BitStr);
   int (*GetElement)(BitString *BitStr,size_t idx);
   size_t (*GetElementSize)(BitString *b);
   unsigned (*GetFlags)(BitString *BitStr); 
   BitString *(*GetRange)(BitString *b,size_t start,size_t end);
   int (*IndexOf)(BitString *BitStr,int SearchedBit,void *ExtraArgs,
        size_t *result);
   BitString *(*Init)(BitString *BitStr,size_t bitlen);
   int (*InitIterator)(BitString *,void *);
   BitString *(*InitializeWith)(size_t size,void *data);
   size_t (*Insert)(BitString *BitStr,int bit);
   size_t (*InsertAt)(BitString *BitStr,size_t idx,int bit);
   int (*LessEqual)(BitString *bsl,BitString *bsr);
   BitString *(*Load)(FILE *stream, ReadFunction saveFn,void *arg);
   int (*Memset)(BitString *,size_t start,size_t stop,int newval);
   Iterator *(*NewIterator)(BitString *);
   BitString *(*Not)(BitString *bsl);
   int (*NotAssign)(BitString *bsl);
   BitString *(*Or)(BitString *left,BitString *right);
   int (*OrAssign)(BitString *bsl,BitString *bsr);
   int (*PopBack)(BitString *BitStr);
   uintmax_t (*PopulationCount)(BitString *b);
   size_t (*Print)(BitString *b,size_t bufsiz,unsigned char *out);
   int (*PushBack)(BitString *BitStr,int val);
   int (*ReplaceAt)(BitString *BitStr,size_t idx,int newval);
   BitString *(*Reverse)(BitString *b);
   int (*Save)(const BitString *bitstr,FILE *stream,
         SaveFunction saveFn,void *arg);
   int (*SetCapacity)(BitString *BitStr,size_t newCapacity);
   int (*SetElement)(BitString *bs,size_t position,int b);
   ErrorFunction *(*SetErrorFunction)(BitString *,ErrorFunction fn);
   unsigned (*SetFlags)(BitString *BitStr,unsigned flags);
   size_t (*Size)(BitString *BitStr); 
   size_t (*Sizeof)(BitString *b);
   BitString *(*StringToBitString)(unsigned char *);
   BitString *(*Xor)(BitString *bsl,BitString *bsr);
   int (*XorAssign)(BitString *bsl,BitString *bsr);
   int (*deleteIterator)(Iterator *);
} BitStringInterface;

4.2  API

Contrary to the other containers presented above like iList or iVector, bitstring receives and returns not pointers but values of bits. This is an important difference and makes for significant changes in the interface of many functions.
Other functions like Apply do not make much sense for bits and are provided just to be coherent in the overall design of the library. Obviously a function that needs a function call per bit is not very fast. The function GetElementSize is provided for compatibility purposes only and returns always 1. Actually it should return 0.125 assuming 8 bits bytes.

Add

    int (*Add)(BitString *BitStr,int);

Description: Adds a bit at the end of the given bitstring.
Errors:
CONTAINER_ERROR_BADARG The given pointer is  NULL .
CONTAINER_ERROR_NOMEMORY There is no memory to carry out the operation.
Returns:A positive number if the bit is added or a negative error code otherwise.
Example:

#include "containers.h"
int main(int argc,char *argv[])
{
    size_t i;
    BitString *b;
    unsigned char buf[512];

    b = iBitString.Create(32);
    for (i=0; i<32;i++)
        iBitString.Add(b,i&1);
    iBitString.Print(b,sizeof(buf),buf);
    printf("%s\n",buf);
    return 0;
}
OUTPUT:
1010 1010  1010 1010  1010 1010  1010 1010


And

    BitString *(*And)(BitString *left,BitString *right);

Description: Makes a logical AND between the left and right arguments. The result is returned in a new bit string, both arguments are not modified. The length of the resulting bit string is the smallest length of both strings.
Returns: A pointer to the newly allocated result or  NULL in case of error.
Errors:
CONTAINER_ERROR_BADARG One of both bitstring pointers are  NULL .
CONTAINER_ERROR_NOMEMORY Not enough memory is available to complete the operation.
Example:

#include "containers.h"
int main(int argc,char *argv[])
{
    size_t i;
    BitString *b,*c,*d;
    unsigned char buf[512];

    b = iBitString.Create(32);
    c = iBitString.Create(32);
    for (i=0; i<32;i++) {
        iBitString.Add(b,i&1);
        iBitString.Add(c,i<16);
    }
    iBitString.Print(b,sizeof(buf),buf);
    printf("%s\n",buf);
    printf(" AND\n");
    iBitString.Print(c,sizeof(buf),buf);
    printf("%s\n",buf);
    printf("=\n");
    d = iBitString.And(b,c);
    iBitString.Print(d,sizeof(buf),buf);
    printf("%s\n",buf);
    return 0;
}
OUTPUT:
1010 1010  1010 1010  1010 1010  1010 1010
 AND
0000 0000  0000 0000  1111 1111  1111 1111
=
0000 0000  0000 0000  1010 1010  1010 1010


AndAssign

    int (*AndAssign)(BitString *left,BitString *right);

Description: Makes a logical AND of its two arguments and assigns the result into the left bit string. If the bit strings have a different length, the operation uses the bits of the right argument until either the end of the right argument or the end of the destination string is reached.
Returns: A positive number or a negative error code in case of error.
Errors:
CONTAINER_ERROR_BADARG One or both arguments are  NULL .
Example:

#include "containers.h"
int main(int argc,char *argv[])
{
    size_t i;
    BitString *b,*c;
    unsigned char buf[512];

    b = iBitString.Create(32);
    c = iBitString.Create(32);
    for (i=0; i<32;i++) {
        iBitString.Add(b,i&1);
        iBitString.Add(c,i<16);
    }
    iBitString.Print(b,sizeof(buf),buf);
    printf("%s\n",buf);
    printf(" AND\n");
    iBitString.Print(c,sizeof(buf),buf);
    printf("%s\n",buf);
    printf("=\n");
    iBitString.AndAssign(b,c);
    iBitString.Print(b,sizeof(buf),buf);
    printf("%s\n",buf);
    return 0;
}
OUTPUT:
1010 1010  1010 1010  1010 1010  1010 1010
 AND
0000 0000  0000 0000  1111 1111  1111 1111
=
0000 0000  0000 0000  1010 1010  1010 1010


BitBlockCount

uintmax_t  (*BitBlockCount)(BitString *b);

Description: Computes the number of blocks where 1 or more bits are set.
Returns: The number of blocks of set bits.
Errors:
CONTAINER_ERROR_BADARG The given argument is  NULL .
Example:

#include "containers.h"
int main(int argc,char *argv[])
{
    size_t i;
    BitString *b,*c,*d;
    unsigned char buf[512];

    b = iBitString.Create(32);
    c = iBitString.Create(32);
    for (i=0; i<32;i++) {
        iBitString.Add(b,i&1);
        iBitString.Add(c,i<16);
    }
    iBitString.Print(b,sizeof(buf),buf);
    printf("%s BitBlockCount=%ld\n",buf,iBitString.BitBlockCount(b));
    iBitString.Print(c,sizeof(buf),buf);
    printf("%s BitBlockCount=%ld\n",buf,iBitString.BitBlockCount(c));
    return 0;
}
OUTPUT:
1010 1010  1010 1010  1010 1010  1010 1010 BitBlockCount=16
0000 0000  0000 0000  1111 1111  1111 1111 BitBlockCount=1


CopyBits

    int (*CopyBits)(BitString *b, void *buffer);

Description: Copies the bits into the given buffer. The size of the buffer is at least:
    1+iBitstring.Size(bitstr)/8

Errors:
CONTAINER_ERROR_BADARG Either the bitstring or the buffer pointer are  NULL .
Returns:A positive number if the bits are copied, a negative error code otherwise.

GetData

   unsigned char *(*GetData)(BitString *b);

Description: Returns a pointer to the bits stored in the bitstring. If the string is read-only the result is  NULL . The size of the needed buffer can be calculated according to:
   BitString *bitstr;
   size_t bytesize;

   bytesize = 1+iBitString.GetSize(bitstr)/CHAR_BIT;

Errors:
CONTAINER_ERROR_BADARG The bit string pointer is  NULL .
CONTAINER_ERROR_READONLY The bitstring is read-only.

GetRange

BitString *(*GetRange)(BitString *b,size_t start,size_t end);

Description: Returns all the bits between the start (inclusive) and the end (inclusive) indices. If end is smaller than start, start and end are exchanged. If end is greater than the size of the bit string, all elements up to the last one are returned. If both start and end are out of range, an error is issued and the result is  NULL .
Returns: A new bit string with the specified contents.
Errors:
CONTAINER_ERROR_BADARG The given argument is  NULL .
Example:

#include "containers.h"
int main(void)
{
    char outbuf[256];
    BitString *bs = iBitString.StringToBitString("0001 1111 0000 0000");
    BitString *bs1 = iBitString.GetRange(bs,8,14);
    BitString *bs2 = iBitString.GetRange(bs,7,14);
    iBitString.Print(bs,sizeof(outbuf),outbuf);
    printf("Original:%s \n",outbuf);
    iBitString.Print(bs1,sizeof(outbuf),outbuf);
    printf("Range 8,14: %s\n",outbuf);
    iBitString.Print(bs2,sizeof(outbuf),outbuf);
    printf("Range 7,14:%s\n",outbuf);
    return 0;
}
OUTPUT:
Original:0001 1111  0000 0000 
Range 8,14: 01 1111
Range 7,14:001 1110


BitLeftShift

int (*BitLeftShift)(BitString *bs,size_t shift);

Description: Shifts left the given bit string by the specified number of bits. New bits introduced by the right are zeroed.
Errors:
CONTAINER_ERROR_BADARG The bit string pointer is  NULL .
Returns:An integer bigger than zero if successful, a negative error code otherwise.
Example:

#include "containers.h"
static void Putbs(BitString *bs,char *prompt)
{
    char outbuf[256];
    iBitString.Print(bs,sizeof(outbuf),outbuf);
    printf("%s: %s\n",prompt,outbuf);
}
int main(int argc,char *argv[])
{
    char outbuf[256];
    int ls;
    if (argc < 2) {
        fprintf(stderr,"Usage: %s: amount\n",argv[0]);
        return 0;
    }
    ls = atoi(argv[1]);
    BitString *bs = iBitString.StringToBitString("0001 1111 1010 0000");
    Putbs(bs,"Original         ");
    iBitString.BitLeftShift(bs,ls);
    sprintf(outbuf,"Shifted left by %d",ls);
    Putbs(bs,outbuf);
    return 0;
}
OUTPUT:
~/ccl/test $ ./a.out 1
Original         : 0001 1111  1010 0000
Shifted left by 1: 0011 1111  0100 0000
~/ccl/test $ ./a.out 3
Original         : 0001 1111  1010 0000
Shifted left by 3: 1111 1101  0000 0000


Not

    BitString *(*Not)(BitString *src);

Description: Makes a logical NOT of its argument. The result is returned in a new bit string. The length of the resulting bit string is the same as the length of the argument.
Returns: A pointer to the newly allocated bit string or  NULL in case of error.
Errors:
CONTAINER_ERROR_BADARG The argument is  NULL .
CONTAINER_ERROR_NOMEMORY Not enough memory is available to complete the operation.

NotAssign

    int (*NotAssign)(BitString *src);

Description: Makes a logical NOT of its argument and assigns the result into it.
Errors:
CONTAINER_ERROR_BADARG The argument is  NULL .
Returns:A positive number or a negative error code in case of error.
Example:

#include "containers.h"
int main(int argc,char *argv[])
{
    size_t i;
    BitString *b;
    unsigned char buf[512];

    b = iBitString.Create(32);
    for (i=0; i<32;i++) {
        iBitString.Add(b,i<16);
    }
    iBitString.Print(b,sizeof(buf),buf);
    printf("    b = %s\n",buf);
    iBitString.NotAssign(b);
    iBitString.Print(b,sizeof(buf),buf);
    printf("NOT b = %s\n",buf);
    return 0;
}
OUTPUT:
    b = 0000 0000  0000 0000  1111 1111  1111 1111
NOT b = 1111 1111  1111 1111  0000 0000  0000 0000


InitializeWith

    BitString *(*ObjectToBitString)(size_t size,void *p);

Description: The bits starting by the given pointer are copied into a new bit string using the size (in bytes) indicated by the second parameter size.
Errors:
CONTAINER_ERROR_BADARG The pointer is  NULL
CONTAINER_ERROR_NOMEMORY There is not enough ressources to finish the operation.
Returns:A new bit string or  NULL if there is an error.
Example:

#include "containers.h"
/* This example changes the sign of a double precision
number by changing its sign bit */
int main(void)
{
    double d = -0.125; /* Number is negative */
    BitString *b = iBitString.ObjectToBitString(sizeof(double),&d);
    /* Now set sign to zero, making the number positive */
    iBitString.SetElement(b,63,0);
    iBitString.CopyBits(b,&d);
    printf("%g\n",d);
    return 0;
}
OUTPUT:
0.125


Or

    BitString *(*Or)(BitString *left,BitString *right);

Description: Makes a logical OR between the left and right arguments. The result is returned in a new bit string, both arguments are not modified. The length of the resulting bit string is the smallest length of both strings.
Errors:
CONTAINER_ERROR_BADARG One of both bitstring pointers are  NULL .
CONTAINER_ERROR_NOMEMORY Not enough memory is available to complete the operation.

OrAssign

    int (*OrAssign)(BitString *left,BitString *right);

Description: Makes a logical OR of its two arguments and assigns the result into the left bit string. If the bit strings have a different length, the operation uses the bits of the right argument until either the end of the right argument or the end of the destination string is reached.
Errors:
CONTAINER_ERROR_BADARG One or both arguments are  NULL .
Returns:A positive number or a negative error code in case of error.

PopulationCount

uintmax_t  (*PopulationCount)(BitString *b);

Description: Computes the number of 1 bits in the bit string.
Returns: The number of set bits in the string.
Errors:
CONTAINER_ERROR_BADARG The given argument is  NULL .

Print

  size_t (*Print)(BitString *b,size_t bufsiz,unsigned char *out);

Description: Prints into the given buffer the contents of the bitstring b without exceeding the length of the given buffer bufsiz. The bits will be grouped into 4 bits separated by a space. Each group of 8 bits will be separated from the rest by two spaces.
Errors:
CONTAINER_ERROR_BADARG . The bit string pointer is  NULL .
Returns:The number of characters written to the output string, including the terminating zero. If the output string pointer is  NULL , it returns the number of characters that would be needed to print the contents of the bitstring.

Reverse

    BitString *(*Reverse)(BitString *b);

Description: The bit sequence of the argument is reversed
Returns: A new bit string containing the reversed argument.
Errors:
CONTAINER_ERROR_BADARG The given argument is  NULL .
Example:

#include "containers.h"
int main(int argc,char *argv[])
{
    size_t pos;
    BitString *b,*c;
    unsigned char buf[512];

    if (argc < 2) {
        fprintf(stderr,"Usage: %s <bitstring>\n",argv[0]);
        return 1;
    }
    b = iBitString.StringToBitString(argv[1]);
    iBitString.Print(b,sizeof(buf),buf);
    printf("Reversing bits of %s\n",buf);
    c = iBitString.Reverse(b);
    iBitString.Print(c,sizeof(buf),buf);
    printf("%s\n",buf);
    return 0;
}
OUTPUT:
Reversing bits of 1111 1100  0000 1111  1111 1111
1111 1111  1111 0000  0011 1111


RemoveAt

   int (*RemoveAt)(BitString *bitStr,size_t idx);

Description: Removes the bit at the specified position. If the position is greater than the length of the string the last position will be used.
Errors:
CONTAINER_ERROR_BADARG The given bit string pointer is  NULL
Returns:A positive number when the bit was removed, a negative error code otherwise. If the bit string is empty the result is zero.
Example:

#include "containers.h"
int main(int argc,char *argv[])
{
    size_t pos;
    BitString *b;
    unsigned char buf[512];

    if (argc < 3) {
        fprintf(stderr,"Usage: %s bitstring pos\n",argv[0]);
        return 1;
    }
    b = iBitString.StringToBitString(argv[1]);
    pos = atoi(argv[2]);
    iBitString.Print(b,sizeof(buf),buf);
    printf("Erasing bit %d of %s\n",pos,buf);
    iBitString.EraseAt(b,pos);
    iBitString.Print(b,sizeof(buf),buf);
    printf("%s\n",buf);
    return 0;
}
OUTPUT:
Erasing bit 2 of 11  1000 1110  0011 1000
1  1100 0111  0001 1100


Set

    int (*Set)(BitString *B,size_t start,size_t stop,bool newvalue);

Description: Sets the range of bits delimiteded by its start and end arguments to the value given by its newvalue argument. If the new value is different than zero a '1' bit is written, otherwise the bit is set to zero. If the stop argument is bigger than the length of the bitstring, the end of the string will be used.
CONTAINER_ERROR_BADARG The bit string pointer is NULL.
CONTAINER_ERROR_INDEX The start argument is bigger or equal to the length of the bitstring.

StringToBitString

    BitString *(*StringToBitString)(unsigned char *);

Reads a bitstring from a character string. The character string should contain only the characters '1', '0', space and tab.
Errors:
CONTAINER_ERROR_BADARG The character string pointer is  NULL .
Returns:A pointer to the new bitstring or  NULL if there was an error or the given character string did not contain any '1' or '0'.

Xor

    BitString *(*Xor)(BitString *left,BitString *right);

Description: Makes a logical XOR between the left and right arguments. The result is returned in a new bit string, both arguments are not modified. The length of the resulting bit string is the smallest length of both strings.
Returns: A pointer to its result or  NULL in case of error.
Errors:
CONTAINER_ERROR_BADARG One of both bitstring pointers are  NULL .
CONTAINER_ERROR_NOMEMORY Not enough memory is available to complete the operation.

XorAssign

    int (*XorAssign)(BitString *left,BitString *right);

Description: Makes a logical XOR of its two arguments and assigns the result into the left bit string. If the bit strings have a different length, the operation uses the bits of the right argument until either the end of the right argument or the end of the destination string is reached.
Returns:A positive number or a negative error code in case of error.
Errors:
CONTAINER_ERROR_BADARG Its argument is  NULL .



5  The string collection container: istrCollection, iWstrCollection

A string collection is a derivation from the Vector container, specialized to hold character strings.

5.1  The interface

To avoid unnecessary repetitions in this document here is documented a generic interface. The word ElementType is either char for multi-byte strings, or wchar_t for wide character strings. The word strCollection in this context means either
typedef struct tagstrCollectionInterface {
   int (*Add)(strCollection *SC,const char *newval);
   int (*AddRange)(strCollection *s,size_t n,const char **values);
   int (*Append)(strCollection *,strCollection *);
   int (*Apply)(strCollection *SC,int (*Applyfn)(char *,void * arg),
        void *arg);
   char *(*Back)(const strCollection *str);
   struct _Vector *(*CastToArray)(const strCollection *SC);
   int (*Clear)(strCollection *SC);
   Mask *(*CompareEqual)(const strCollection *left,
          const strCollection *right,Mask *m);
   Mask *(*CompareEqualScalar)(const strCollection *left,
          const char *str,Mask *m);
   int (*Contains)(const strCollection *SC,const char *str);
   strCollection *(*Copy)(const strCollection *SC);
   char **(*CopyTo)(const strCollection *SC);
   strCollection *(*Create)(size_t startsize);
   strCollection *(*CreateFromFile)(const char *fileName);
   strCollection *(*CreateWithAllocator)(size_t startsiz,
                  const ContainerAllocator *mm);
   int (*Equal)(const strCollection *SC1,const strCollection *SC2);
   int (*Erase)(strCollection *SC,const char *);
   int (*EraseAll)(strCollection *SC,const char *);
   int (*EraseAt)(strCollection *SC,size_t idx);
   int (*Finalize)(strCollection *SC);
   size_t (*FindFirst)(const strCollection *SC,const char *text);
   size_t (*FindNext)(const strCollection *SC, const char *text,
          size_t start);
   strCollection *(*FindText)(const strCollection *SC,
                  const char *text);
   Vector *(*FindTextIndex)(const strCollection *SC,const char *text);
   Vector *(*FindTextPositions)(const strCollection *SC,
           const char *text);
   char *(*Front)(const strCollection *str);
   const ContainerAllocator *(*GetAllocator)(const strCollection *AL);
   size_t (*GetCapacity)(const strCollection *SC);
   char **(*GetData)(const strCollection *SC);
   char *(*GetElement)(const strCollection *SC,size_t idx);
   size_t (*GetElementSize)(const strCollection *SC);
   unsigned (*GetFlags)(const strCollection *SC);
   strCollection *(*GetRange)(strCollection *SC, size_t start,
                  size_t end);
   strCollection *(*IndexIn)(const strCollection *SC,
                  const Vector *AL);
   int (*IndexOf)(const strCollection *SC,const char *SearchedString,
        size_t *result);
   strCollection *(*Init)(strCollection *result,size_t startsize);
   int (*InitIterator)(strCollection *SC,void *buf);
   strCollection *(*InitWithAllocator)(strCollection *c,size_t start,
                  const ContainerAllocator *mm);
   strCollection *(*InitializeWith)(size_t n, char **data);
   int (*Insert)(strCollection *SC,char *);
   int (*InsertAt)(strCollection *SC,size_t idx,const char *newval);
   int (*InsertIn)(strCollection *source, size_t idx,
        strCollection *newData);
   strCollection *(*Load)(FILE *stream, ReadFunction readFn,
                  void *arg);
   int (*Mismatch)(const strCollection *a1,const strCollection *a2,
        size_t *mismatch);
   Iterator *(*NewIterator)(strCollection *SC);
   size_t (*PopBack)(strCollection *, char *result,size_t bufsize);
   size_t (*PopFront)(strCollection *SC,char *outbuf,size_t buflen);
   int (*PushBack)(strCollection *,const char *data);
   int (*PushFront)(strCollection *SC,char *str);
   int (*RemoveRange)(strCollection *SC,size_t start,size_t end);
   int (*ReplaceAt)(strCollection *SC,size_t idx,char *newval);
   int (*Reverse)(strCollection *SC);
   int (*Save)(const strCollection *SC,FILE *stream,
         SaveFunction saveFn,void *arg);
   int (*Select)(strCollection *src, const Mask *m);
   strCollection *(*SelectCopy)(const strCollection *src,
                  const Mask *m);
   int (*SetCapacity)(strCollection *SC,size_t newCapacity);
   StringCompareFn (*SetCompareFunction)(strCollection *SC,
                   StringCompareFn);
   DestructorFunction (*SetDestructor)(strCollection *v,
                      DestructorFunction fn);
   ErrorFunction (*SetErrorFunction)(strCollection *SC,
                 ErrorFunction fn);
   unsigned (*SetFlags)(strCollection *SC,unsigned flags);
   size_t (*Size)(const strCollection *SC);
   size_t (*Sizeof)(const strCollection *SC);
   size_t (*SizeofIterator)(const strCollection *l);
   int (*Sort)(strCollection *SC);
   int (*WriteToFile)(const strCollection *SC,const char *filename);
   int (*deleteIterator)(Iterator *);
} strCollectionInterface;

5.2  API

Most of the functions present in the interface are exactly like the functions in Vector. Only those that differ will be documented here.

AddRange

    int (*AddRange)(strCollection *SC,size_t n,
                    const ElementType *data[]);

Description: Adds each string of the array of string pointers at the end of the container. It is assumed that "data" points to a contiguous array of string pointers whose size is given by the "n" parameter. Returns a value greater than zero if the addition completed successfully, a negative error code otherwise. If n is zero nothing is done and no errors are issued, even if the array pointer or the data pointer are  NULL .
Errors:
CONTAINER_ERROR_BADARG The strCollection pointer or the data pointers are  NULL .
CONTAINER_ERROR_READONLY The collection is read-only. No modifications allowed.
CONTAINER_ERROR_NOMEMORY Not enough memory to complete the operation.
Returns: A positive number if the operation completed, negative error code otherwise.
Invariants: The data added is copied and not modified in any way.
Example:

strCollection *SC = istrCollection.Create(10);
    char *data[] = { "one","two,"three"};
    int result = istrCollection.AddRange(SC,3,data);
    if (result < 0) { /* Error handling */ }


CastToArray

        Vector *(*CastToArray)(const strCollection *SC);

Description: Converts a string collection into an vector.
Errors:
CONTAINER_ERROR_BADARG The strCollection pointer is  NULL .
CONTAINER_ERROR_NOMEMORY Not enough memory to complete the operation.
Invariants: The given collection is not modified in any way.
Returns: A positive number if the operation completed, negative error code otherwise.

CreateFromFile

strCollection *(*CreateFromFile)(const char *fileName);

Description: Reads the given text file and stores each line in a string of the collection. The end of line characters are discarded.
Errors:
CONTAINER_ERROR_BADARG The fileName pointer is  NULL .
CONTAINER_ERROR_NOMEMORY Not enough memory to complete the operation.
CONTAINER_ERROR_NOENT The file doesn't exist or can't be opened for reading.
Returns: A pointer to a new string collection with the contents of the file. If an error occurs the result is  NULL and the current error function (in the iError interface) is called.

FindFirst

size_t (*FindFirstText)(const strCollection *SC,
                        const ElementType *text);

Description: Finds the first occurrence of the given text in the string collection.
Errors:
CONTAINER_ERROR_BADARG One or both arguments are  NULL .
Invariants: Neither the input collection, nor the given text are modified.
Returns: The zero based index of the line that contains the given text or the constant CONTAINER_ERROR_NOTFOUND if the text is not found.

FindNext

Iterator *(*Find)(const strCollection *SC,const ElementType *txt,
                       size_t start);

Description: Starts searching for the given text at the specified line.
Errors:
CONTAINER_ERROR_BADARG The strCollection or the text pointer are  NULL .
Invariants: Neither the input collection, nor the given text are modified in any way.
Returns: An iterator that points to the element found or  NULL if nothing was found.

FindTextPositions

Vector *(*FindTextPositions)(const strCollection *SC,
                             const ElementType *text);

Description: Searches all occurrences of the given text in the given string collection.
Errors:
CONTAINER_ERROR_BADARG The strCollection or the text pointer are  NULL .
CONTAINER_ERROR_NOMEMORY Not enough storage for holding the resulting array.
Invariants: Neither the input collection, nor the given text are modified.
Returns: An array list containing a pair of integers for each occurrence containing the zero based position of the line where the text was found and a second number indicating the character index within the line where the searched text occurs. The result is  NULL if there wasn't any occurrences of the searched text in the string collection or an error was detected.

Front

const CHARTYPE *(*Front)(const strCollection *l);

Description: Returns the first element of the given list or  NULL if the collection is empty.
Errors:
CONTAINER_ERROR_BADARG The collection pointer is  NULL .
CONTAINER_ERROR_READONLY The collection is read only.
Invariants: The input collection is not modified. The resulting pointer should not be modified in any way.
Returns: The first element or  NULL if the collection is empty or an error occurs.

Init

strCollection *(*Init)(strCollection *result, size_t startsize);

Description: Initializes the given string collection to contain at least the number of strings given. Uses the current memory manager.
Errors:
CONTAINER_ERROR_NOMEMORY There is no more memory left to complete the operation.
CONTAINER_ERROR_BADARG The string collection pointer is  NULL
Returns:A pointer to the initialized string collection or  NULL if an error occurs.

InitWithAllocator

strCollection *(*InitWithAllocator)(strCollection *result, 
                         size_t startsize,
                         const ContainerAllocator *allocator);

Description: Initializes the given string collection to contain at least the number of strings given. Uses the given memory manager.
Errors:
CONTAINER_ERROR_NOMEMORY There is no more memory left to complete the operation.
CONTAINER_ERROR_BADARG The string collection pointer is  NULL
Returns:A pointer to the initialized string collection or  NULL if an error occurs.

InsertIn

int (*InsertIn)(strCollection *dst, size_t pos, 
                const strCollection *newData);

Description: Inserts the given strCollection into the destination strCollection at the given position. If the position is greater than the actual length of the string collection the new data will be inserted at the end.
Errors:
CONTAINER_ERROR_BADARG The source or destination pointers are  NULL .
CONTAINER_ERROR_READONLY The destination is read only.
Invariants: The new data is not modified in any way.
Example:

#include <containers.h>
static void PrintStringCollection(strCollection *AL)
{
    size_t i;
    printf("Count %ld, Capacity %ld\n",
           (long)istrCollection.Size(AL),
           (long)istrCollection.GetCapacity(AL));
    for (i=0; i<istrCollection.Size(AL);i++) {
        printf("%s ",istrCollection.GetElement(AL,i));
    }
    printf("\n");
}

static void FillStringCollection(strCollection * AL,int start)
{
    size_t i;
    char buf[256];

    for (i=0; i<10;i++) {
        double d = i+start;
        sprintf(buf,"%g",d);
        istrCollection.Add(AL,buf);
    }
}
int main(void)
{
    strCollection *AL = istrCollection.Create(10);
    strCollection *AL1 =istrCollection.Create(10);
    FillStringCollection(AL,0);
    FillStringCollection(AL1,100);
    istrCollection.InsertIn(AL,5,AL1);
    PrintStringCollection(AL);
    return 0;
}

The example creates two string collections, fills them with the string representation of the numbers from 0 to 9 and from 100 to 109, then inserts the second collection into the first one at position 5.
OUTPUT:
Count 20, Capacity 20
0 1 2 3 4 100 101 102 103 104 105 106 107 108 109 5 6 7 8 9


Mismatch

int (*Mismatch)(const strCollection *a1,
                const strCollection *a2,
                           size_t *mismatch);

Description: Returns the index of the first element that is different when comparing both collections in the passed pointer mismatch. If one is shorter than the other the comparison stops when the last element from the shorter array is compared. The comparison also stops when the first difference is spotted.
Errors:
CONTAINER_ERROR_BADARG Any of the arguments is  NULL .
Invariants: The input collections are not modified in any way. Both collections could be the same.
Returns: If a mismatch is found the result is greater than zero and the mismatch argument will contain the index of the first element that compared unequal. This will be always the case for arrays of different length.
If both arrays are the same length and no differences are found the result is zero and the value pointed to by the mismatch argument is one more than the length of the arrays.
If an error occurs, a negative error code is returned. The mismatch argument contains zero.
Example:

#include "containers.h"
char *table[] = {"String 1", "String 2","String 3","String 4",};

int main(void)
{
    size_t idx;
    strCollection *sc = istrCollection.Create(4);
    strCollection *sc2;
    istrCollection.AddRange(sc,sizeof(table)/sizeof(table[0]),table);
    sc2 = istrCollection.Copy(sc);
    istrCollection.ReplaceAt(sc,2,"String456");
    istrCollection.Mismatch(sc,sc2,&idx);
    printf("String collections differ at position %d\n",idx);
}
OUTPUT:
String collections differ at position 2


PopBack

size_t (*PopBack)(strCollection *SC,ElementType *buf,size_t buflen);

Description: If the string collection is not empty, it will copy at most buflen characters into the given buffer. If the buffer pointer is  NULL or the length of the buffer is zero it will return the length of the element that would be popped.
Errors:
CONTAINER_ERROR_BADARG The strCollection pointer is  NULL .
Returns: Zero if the collection was empty, a negative error code if an error occurs, or a positive value if the range was erased.

RemoveRange

int (*RemoveRange)(strCollection *SC,size_t start,size_t end);

Description: Removes all strings having an index equal or greater than start and less than end . If end is greater than the number of elements in the collection it will be adjusted to one more than the number of elements. If start is bigger than end the range is still valid and starts with the value of end and ends with the value of start .
Errors:
CONTAINER_ERROR_BADARG The strCollection pointer is  NULL .
Returns: Zero if the string collection is empty. Otherwise returns a positive number for success, a negative error code in case of an error.

SetCompareFunction

StringCompareFn (*SetCompareFunction)(strCollection *SC,
                  StringCompareFn StrCmp);

Description: Sets the function used for comparing two strings to the given one. If the value of the new function is  NULL the current value is returned.
Note that the definition of the StringCompareFn differs from the normal comparison functions used in all other containers. Its definition is as follows:
typedef int (*StringCompareFn)(const void **s1,
                               const void **s2,CompareInfo *info);

The reason for this change is that a string container holds pointers to characters, hence a double indirection is needed by functions like sort.
Errors:
CONTAINER_ERROR_BADARG The strCollection pointer is  NULL .
Returns:The old value of the comparison function.

WriteToFile

int (*WriteToFile)(const strCollection *SC,const char *fileName);

Description: Writes the contents of the given string collection into a file with the given name. If the collection is empty an empty file is created. The resulting file contains a line for each string in the collection.
Errors:
CONTAINER_ERROR_BADARG The strCollection pointer or the fileName are  NULL .
Invariants: The input collection is not modified.
Returns: A positive number if the operation completes, or a negative error code otherwise. If the collection is empty the result is zero.


6  The dictionary container: iDictionary

Figure
The dictionary vocabulary.
A dictionary is an associative container that associates a text key with a piece of data. It can be implemented by means of a hash table that uses a hash function to map the key into a restricted integer range, used to index a table. A common usage is to associate some data with a character key, but it can also be used to just store character keys without any data associated with them. The container is then used just to see if a given key is stored there or not. In this case the container should be created with object size of zero.
The interest of hash tables is that the access to objects using the key is very fast.

6.1  The dictionary interface

This interface (like the string collection container) comes in two flavors: One with keys of 8 bit characters, another with keys in the wide character set. Both interfaces are identical, except for the keys parameter. This is the interface for the multi-byte character set.
typedef struct tagDictionaryInterface {
   int (*Add)(Dictionary *Dict,const char *key,const void *Data);
   int (*Apply)(Dictionary *Dict,int (*Applyfn)(const char *Key,
   Vector *(*CastToArray)(const Dictionary *);
   int (*Clear)(Dictionary *Dict);
   int (*Contains)(const Dictionary *dict,const char *key);
   Dictionary *(*Copy)(const Dictionary *dict);
   int (*CopyElement)(const Dictionary *Dict,const char *Key,
        void *outbuf);
   Dictionary *(*Create)(size_t ElementSize,size_t hint);
   Dictionary *(*CreateWithAllocator)(size_t elementsize,size_t hint,
               const ContainerAllocator *mm);
   int (*Equal)(const Dictionary *d1,const Dictionary *d2);
   int (*Erase)(Dictionary *Dict,const char *);
   int (*Finalize)(Dictionary *Dict);
   const ContainerAllocator *(*GetAllocator)(const Dictionary *Dict);
   void *(*GetElement)(const Dictionary *Dict,const char *Key);
   size_t (*GetElementSize)(const Dictionary *d);
   unsigned (*GetFlags)(const Dictionary *Dict);
   strCollection *(*GetKeys)(const Dictionary *Dict);
   double (*GetLoadFactor)(Dictionary *d);
   Dictionary *(*Init)(Dictionary *dict,size_t ElementSize,
               size_t hint);
   int (*InitIterator)(Dictionary *dict,void *buf);
   Dictionary *(*InitWithAllocator)(Dictionary *D,size_t elemsize,
               size_t hint,const ContainerAllocator *mm);
   Dictionary *(*InitializeWith)(size_t elementSize,size_t n,
                const char **Keys,const void *Values);
   int (*Insert)(Dictionary *Dict,const char *key,const void *Data);
   int (*InsertIn)(Dictionary *dst,Dictionary *src);
   Dictionary * (*Load)(FILE *stream, ReadFunction readFn, void *arg);
   Iterator *(*NewIterator)(Dictionary *dict);
   int (*Replace)(Dictionary *dict,const char *Key,const void *Data);
   int (*Save)(const Dictionary *Dict,FILE *stream,
         SaveFunction saveFn,void *arg);
   DestructorFunction (*SetDestructor)(Dictionary *v,
                      DestructorFunction fn);
   ErrorFunction (*SetErrorFunction)(Dictionary *Dict,
                 ErrorFunction fn);
   unsigned (*SetFlags)(Dictionary *Dict,unsigned flags);
   HashFunction (*SetHashFunction)(Dictionary *d,HashFunction newFn);
   size_t (*Size)(const Dictionary *Dict);
   size_t (*Sizeof)(const Dictionary *dict);
   size_t (*SizeofIterator)(const Dictionary *);
   int (*deleteIterator)(Iterator *);
} DictionaryInterface;

This is the interface for the wide character set.
typedef struct tagWDictionaryInterface {
   int (*Add)(WDictionary *Dict,const wchar_t *key,const void *Data);
   int (*Apply)(WDictionary *Dict,int (*Applyfn)(const wchar_t *Key,
   Vector *(*CastToArray)(const WDictionary *);
   int (*Clear)(WDictionary *Dict);
   int (*Contains)(const WDictionary *dict,const wchar_t *key);
   WDictionary *(*Copy)(const WDictionary *dict);
   int (*CopyElement)(const WDictionary *Dict,const wchar_t *Key,
        void *outbuf);
   WDictionary *(*Create)(size_t ElementSize,size_t hint);
   WDictionary *(*CreateWithAllocator)(size_t elementsize,size_t hint,
                const ContainerAllocator *mm);
   int (*Equal)(const WDictionary *d1,const WDictionary *d2);
   int (*Erase)(WDictionary *Dict,const wchar_t *);
   int (*Finalize)(WDictionary *Dict);
   const ContainerAllocator *(*GetAllocator)(const WDictionary *Dict);
   void *(*GetElement)(const WDictionary *Dict,const wchar_t *Key);
   size_t (*GetElementSize)(const WDictionary *d);
   unsigned (*GetFlags)(const WDictionary *Dict);
   WstrCollection *(*GetKeys)(const WDictionary *Dict);
   double (*GetLoadFactor)(WDictionary *d);
   WDictionary *(*Init)(WDictionary *dict,size_t ElementSize,
                size_t hint);
   int (*InitIterator)(WDictionary *dict,void *buf);
   WDictionary *(*InitWithAllocator)(WDictionary *D,size_t elemsize,
                size_t hint,const ContainerAllocator *mm);
   WDictionary *(*InitializeWith)(size_t elementSize,size_t n,
                 const wchar_t **Keys,const void *Values);
   int (*Insert)(WDictionary *Dict,const wchar_t *key,
        const void *Data);
   int (*InsertIn)(WDictionary *dst,WDictionary *src);
   WDictionary * (*Load)(FILE *stream, ReadFunction readFn,
                  void *arg);
   Iterator *(*NewIterator)(WDictionary *dict);
   int (*Replace)(WDictionary *dict,const wchar_t *Key,
        const void *Data);
   int (*Save)(const WDictionary *Dict,FILE *stream,
         SaveFunction saveFn,void *arg);
   DestructorFunction (*SetDestructor)(WDictionary *v,
                      DestructorFunction fn);
   ErrorFunction (*SetErrorFunction)(WDictionary *Dict,
                 ErrorFunction fn);
   unsigned (*SetFlags)(WDictionary *Dict,unsigned flags);
   WHashFunction (*SetHashFunction)(WDictionary *d,
                 WHashFunction newFn);
   size_t (*Size)(const WDictionary *Dict);
   size_t (*Sizeof)(const WDictionary *dict);
   size_t (*SizeofIterator)(const WDictionary *);
   int (*deleteIterator)(Iterator *);
} WDictionaryInterface;

6.2  The API


Add

    int (*Add)(Dictionary *Dict,char *key,const void *data);

Description: Adds the given element to the container using the given "key" string. It is assumed that "data" points to a contiguous memory area of at least ElementSize bytes. Both the key and the data are copied into the container. If the size of dictionary data elements is zero the data
argument is ignored and can be  NULL .
If an element exists with the given key, its contents are replaced with the new data. For a different behavior use Insert or Replace.
Errors:
CONTAINER_ERROR_BADARG The dictionary, or the key pointers are  NULL .
CONTAINER_ERROR_READONLY The dictionary is read-only. No modifications allowed.
CONTAINER_ERROR_NOMEMORY Not enough memory to complete the operation.
Returns: A positive number if the operation added a new element, zero if the data was written into an existing element, or a negative error code if an error occurred.
Example:

Dictionary *dict;
    double data = 4.5;
    int result = iDictionary.Add(dict,"Interest rate",&data);
    if (result < 0) { /* Error handling */ }


Apply

    int (*Apply)(Dictionary *Dict,
                  int (Applyfn)(const char *key,
                                void *data,
                                void *extraArg),
                  void *extraArg);

Description: Will call the given function for each element of the array. The first argument of the callback function receives they key ,the second is a pointer to the element of the Dictionary. The third argument of the callback is the ëxtraArg" argument that the Apply function receives and passes to the callback. This way some context can be passed to the callback, and from one element to the next. Note that the result of the callback is not used. This allows all kinds of result types to be accepted after a suitable function type cast. If the dictionary is read-only, a copy of the element will be passed to the callback function.
Errors:
CONTAINER_ERROR_BADARG Either the dictionary pointer or Applyfn are  NULL .
CONTAINER_ERROR_NOMEMORY The dictionary is read-only and there is no more memory to allocate the buffer to copy each element.
Example:

static int print(const char *key,
                      void *pElement,
                      void *pResult)
    {
        double *p = pElement;
        printf("%s: %g\n",key,*p);
        return 1;
    }
    int main(void) {
        Dictionary *dict = iDictionary.Create(sizeof(double),5);
        double d = 2;
        iDictionary.Add(dict,"First item",&d);
        d = 3;
        iDictionary.Add(dict,"Second item",&d);
        iDictionary.Apply(dict,print,NULL);
        return 0;
    }

Output should be:
First item: 2
Second item: 3


CastToArray

Vector *(*CastToArray)(Dictionary *);

Description: Returns a vector containing all the elements in the dictionary (without any keys). If the element size of the dictionary is zero the result is  NULL .
CONTAINER_ERROR_BADARG The dictionary pointer is  NULL .
CONTAINER_ERROR_NOMEMORY The creation of the resulting vector failed or the dictionary is read-only and there is no more memory to allocate the buffer to copy each element.
Returns:The new vector or  NULL .

Clear

    int (*Clear)(Dictionary *dict);

Description: Erases all stored data and releases the memory associated with it. The dictionary header is not destroyed, and its contents will be the same as when it was initially created. It is an error to use this function when there are still active iterators for the container.
Returns: The result is greater than zero if successful, or an error code if an error occurs.
Errors:
CONTAINER_ERROR_BADARG The vector pointer is  NULL .
CONTAINER_ERROR_READONLY The vector is read only.
Example:

Dictionary *Dict;
    int m = iDictionary.Clear(Dict);


Contains

    int (*Contains)(Dictionary *Dict,const char *Key);

Description: Returns one if the given key is stored in the dictionary, zero otherwise. If an error occurs it returns a negative error code.
Errors:
CONTAINER_ERROR_BADARG Either Dict or Key are  NULL .
Example:

Dictionary *dict;
    int r = iDictionary.Contains(dict,"Item 1");


Copy

    Dictionary *(*Copy)(Dictionary *Dict);

Description: A shallow copy of the given dictionary is performed. Only ElementSize bytes will be copied for each element. If the element contains pointers, only the pointers are copied, not the objects they point to. The new memory will be allocated using the allocator in the source dictionary.
Errors:
CONTAINER_ERROR_NOMEMORY There is not enough memory to complete the operation.
CONTAINER_ERROR_BADARG The given vector pointer is  NULL .
Returns:A pointer to a copy of the given dictionary or  NULL .
Example:

Dictionary *newDict,*Old;
    newDict = iDictionary.Copy(Old);


CopyElement

    int (*CopyElement)(Dictionary *Dict,cont char *Key, void *outbuf);

Description: A shallow copy of the given dictionary element is performed. Only element size bytes will be copied. If the element contains pointers, only the pointers are copied, not the objects they point to. The new memory will be allocated using the allocator in the source dictionary. If the element size is zero nothing is copied and the result is zero.
Errors:
CONTAINER_ERROR_NOMEMORY There is not enough memory to complete the operation.
CONTAINER_ERROR_BADARG The given vector pointer is  NULL .
Returns:A positive value for success, zero if the element size of the dictionary is zero, or a negative error code.
Example:

Dictionary *Dict;
    int r = iDictionary.CopyElement(Dict,"Key",outbuf);
    if (r < 0) 
          ; // handle error
    else if (r == 0)
          ; nothing was copied
    else // Use outbuf data here


Create

    Dictionary *(*Create)(size_t ElementSize,size_t hint);
    Dictionary *(*CreateWithAllocator)(size_t elementsize,size_t hint,
                                     ContainerAllocator *allocator);

Description: Creates a new dictionary with the given element size and with a table big enough to store hint entries. The Create function uses the current memory manager as the allocator for the new dictionary. CreateWithAllocator uses the given allocator object.
Errors:
CONTAINER_ERROR_NOMEMORY Not enough memory to complete the operation.
Returns:A pointer to the new dictionary or  NULL if there is not enough memory to create it.

deleteIterator

    int deleteIterator(Iterator *it);

Description: Reclaims the memory used by the given iterator object
Returns: Integer smaller than zero with error code or a positive number when the operation completes.
Errors:
CONTAINER_ERROR_BADARG The iterator pointer is  NULL .

Equal

    int (*Equal)(Dictionary *d1,Dictionary *d2);

Description: Compares the given dictionaries using their comparison function. If the dictionaries differ in their size, flags, or hash functions they compare unequal. If any of their elements differ, they compare unequal. If both d1 and d2 are  NULL they compare equal. If Both d1 and d2 are empty they compare equal.
Errors:
None
Returns: The result is one if the dictionaries are equal, zero otherwise.

Erase

    int (*Erase)(Dictionary *Dict,const char *key);

Description: Removes from the dictionary the element that matches the given key.
Returns: A positive value that indicates that a match was found and the element was removed. If no element matched the result is CONTAINER_ERROR_NOTFOUND . If an error occurs, a negative error code is returned.
Errors:
CONTAINER_ERROR_BADARG One or both arguments are  NULL .
Example:

double d = 2.3;
    Vector *AL;
    int r = iVector.Erase(AL,&d);
    if (r > 0)
        printf("2.3 erased|n");
    else if (r == 0)
        printf("No element with value 2.3 present\n");
    else
        printf("error code %d\n",r);


Finalize

    int (*Finalize)(Dictionary *dict);

Description: Reclaims all memory used by the dictionary, including the array header object itself.
Errors:
CONTAINER_ERROR_BADARG The given pointer is  NULL .
CONTAINER_ERROR_READONLY The dictionary is read-only. No modifications allowed.
Returns: A positive value means the operation completed. A negative error code indicates failure.
Example:

Dictionary *AL;
    int r = iDictionary.Finalize(AL);
    if (r < 0) { /* error handling */ }


GetAllocator

    ContainerAllocator (*GetAllocator)(Dictionary *Dict);

Description: Retrieves the memory manager of the given dictionary.
Errors:
CONTAINER_ERROR_BADARG The given pointer is  NULL .
Returns: The memory manager object or  NULL if an error occurs.

GetElementSize

    size_t (*GetElementSize)(const Dictionary *Dict);

Description: Retrieves the size of the elements stored in the given dictionary. Note that this value can be different than the value given to the creation function because of alignment requirements.
Errors:
CONTAINER_ERROR_BADARG The given pointer is  NULL .
Returns: The element size or zero if an error.
Example:

Dictionary *Dict;
    size_t siz = iDictionary.GetElementSize(Dict);


GetElement

void *(*GetElement)(Dictionary *Dict,const char *key);

Description: Returns a pointer to the element at the given index, or  NULL if the operation failed. This function will return  NULL if the dictionary is read only.
Use the CopyElement function to get a read/write copy of an element of the dictionary.
Errors:
CONTAINER_ERROR_BADARG The given dictionary pointer or the key are  NULL .
CONTAINER_ERROR_READONLY The array is read only.
Example:

Dictionary *Dict;
    double *d = iDictionary.GetElement(Dict,"Index");
    if (d == NULL) { /* Error handling */ }


GetFlags

unsigned (*GetFlags)(Dictionary *dict);

Description: Returns the flags element of the given dictionary.
Errors:
CONTAINER_ERROR_BADARG The given dictionary pointer is  NULL .

GetLoadFactor

double (*GetLoadFactor)(Dictionary *dict);

Description: Returns the number of elements divided by the size of the table.
Errors:
CONTAINER_ERROR_BADARG The given dictionary pointer is  NULL .

InsertIn

int (*InsertIn)(Dictionary *dst,Dictionary *src);

Description: Inserts all keys of the src dictionary into the dst dictionary. If the container changes during the insertion process the operation aborts.
Errors:
CONTAINER_ERROR_BADARG The given dictionary pointer is  NULL .
CONTAINER_ERROR_NOMEMORY . There is not enough memory to complete the operation.
Returns:A positive number if successful, zero if the container changed during the operation, or a negative error code.

Init

	Dictionary *(*Init)(Dictionary *Dict,size_t elementsize,size_t hint);

Description: Initializes the indicated storage for use asa dictionary object. This procedure is completely equivalent to Create with the difference that there is no allocation done for the dictionary header. Uses the current memory manager for the allocations of the slot table.
Returns: A pointer to its first argument if successfull or  NULL if there is no memory to complete the operation.

InitializeWith

Dictionary *(*InitializeWith)(size_t elemSize, size_t n, 
                         char **keys,void *Values);

Description: Construct a dictionary from the given keys and values. The Values argument should be either  NULL or a valid pointer to n elements of size elementSize. The keys argument should be a table of string pointers with each string associated with each element of the Values table.
Errors:
CONTAINER_ERROR_BADARG The keys argument is  NULL .
CONTAINER_ERROR_NOMEMORY . There is not enough memory to complete the operation.
Returns:The new dictionary object or  NULL if an error occurs

InitWithAllocator

    	Dictionary *(*InitWithAllocator)(Dictionary *Dict,
                        size_t elementsize, size_t hint,
                              ContainerAllocator *allocator);

Description: Initializes the indicated storage for use as a dictionary object. This procedure is completely equivalent to CreateWithAllocator with the difference that there is no allocation done for the dictionary header. Uses the given memory manager for the allocations of the slot table.
Returns: A pointer to its first argument if successfull or  NULL if there is no memory to complete the operation.

Insert

int (*Insert)(Dictionary *Dict, const char *key,void *Data);

Description: Inserts the new key and its corresponding data into the given dictionary. If the key is already present, nothing is changed. This contrasts with the behavior of Add that will replace an existing key.
Errors:
CONTAINER_ERROR_BADARG Any of the given pointers is  NULL .
CONTAINER_ERROR_READONLY The array is read only.
CONTAINER_ERROR_NOMEMORY Not enough memory to complete the operation.
Returns:A positive value if the key was inserted, zero if the key was already present, or a negative error code.

Load

 Dictionary *(*Load)(FILE *stream,ReadFunction readFn,void *arg);

Description: Reads a dictionary previously saved with the Save function from the stream pointed to by stream. If readFn is not  NULL , it will be used to read each element. The ärg" argument will be passed to the read function. If the read function is  NULL , this argument is ignored and a default read function is used.
Errors:
CONTAINER_ERROR_BADARG The given stream pointer is  NULL .
CONTAINER_ERROR_NOMEMORY There is not enough memory to complete the operation.
Returns: A new dictionary or  NULL if the operation could not be completed. Note that the function pointers in the array are NOT saved, nor any special allocator that was in the original dictionary. Those values will be the values by default. To rebuild the original state the user should replace the pointers again in the new array.

NewIterator

    Iterator *(*NewIterator)(Dictionary *Dict);

Description: Allocates and initializes a new iterator object to iterate this dictionary. The exact sequence is implementation defined but it will be the same for the same dictionary with the same number of elements.
Errors:
If no more memory is available it returns  NULL .
Returns: A pointer to a new iterator or  NULL if there is no more memory left.
Example:

Dictionary *Dict;
    Iterator *it = iDictionary.NewIterator(Dict);
    double *d;
    for (d=it->GetFirst(it); d != NULL; d = it->GetNext(it)) {
        double val = *d;
        // Work with the value here
    }
    iDictionary.deleteIterator(it);


SetDestructor

DestructorFunction SetDestructor(Dictionary *d,DestructorFunction fn);

Description: Sets the destructor function to its given argument. If the function argument is  NULL nothing is changed and the call is interpreted as a query since the return value is the current value of the destructor function. If the dictionary argument is  NULL , the result is  NULL .
Returns:The old value of the destructor.

SetHashFunction

size_t (*SetHashFunction)(Dicttionary *dict,HashFunction newFn);

Description: This function is both a query function and a function to change the hash function used by the given dictionary.

Size

    size_t (*Size)(const Dictionary *Dict);

Description: Returns the number of elements stored in the dictionary or SIZE_MAX if the dictionary pointer is  NULL .
Errors:
CONTAINER_ERROR_BADARG The given array pointer or the key are  NULL .
Example:

Dictionary *Dict;
    size_t elem = iDictionary.Size(Dict);


Save

int (*Save)(const Dictionary *D, FILE *out, SaveFunction Fn, void *arg);

Description: The contents of the given dictionary are saved into the given stream. If the save function pointer is not  NULL , it will be used to save the contents of each element and will receive the arg argument passed to Save, together with the output stream. Otherwise a default save function will be used and arg will be ignored. The output stream must be opened for writing and must be in binary mode.
Errors:
CONTAINER_ERROR_BADARG The dictionary pointer or the stream pointer are  NULL . EOF A disk input/output error occurred.
Returns: A positive value if the operation completed, a negative value or EOF otherwise.
Example:

Dictionary *Dict;
    FILE *outFile;
    if (iDictionary.Save(Dict,outFile,NULL,NULL) < 0) { 
        /* Handle error here */
    }


Sizeof

    size_t (*Sizeof)(Dictionary *Dict);

Description: Returns the total size in bytes of the dictionary, including the header, and all data stored in the dictionary, including the size of the dictionary header. If Dict is  NULL , the result is the size of the Dictionary structure.
Returns: The number of bytes used by the dictionary or the size of the Dictionary structure if the argument is  NULL .
Example:

Dictionary *Dict;
    size_t  size = iDictionary.Sizeof(Dict);


SetErrorFunction

ErrorFunction (*SetErrorFunction)(Dictionary *dict,ErrorFunction efn); 

Description: Replaces the current error function for the given dictionary with the new error function if the ErrorFunction parameter is different from  NULL . If the dict parameter is  NULL the function returns the value of the current default error function.
Errors:
CONTAINER_ERROR_BADARG The dictionary pointer is  NULL .
CONTAINER_ERROR_READONLY The dictionary is read only and the function argument is not  NULL .
Returns: The old value of the error function or  NULL if there is an error.

Size

    size_t (*Size)(const Dictionary *d);

Description: Returns the number of elements stored in the dictionary. If the argument is  NULL the result is zero.
Example:

Dictionary *d;
    size_t elem = iDictionary.Size(d);




7  The TreeMap interface: iTreeMap

The tree map container uses a tree to associate keys to values. Trees are extremely efficient data structures that allow access to millions of items with a few comparisons. Disadvantages include a greater overhead than other containers, and a complex machinery to maintain them.
This associative container is special in that it contains no separate key, the elements themselves are the key. Obviously they need imperatively a comparison function, and that comparison function could use some parts of the stored object as a key, but that is transparent to the interface.
An essential point in this container is the comparison function. Since all insertions searches and deletions from/to the tree are done using that function, it is essential that is defined correctly. Like all other comparison functions it can receive an extra argument that conveys some kind of context to it. This implies that functions like 'Add' have an extra argument to be able to pass this context to the comparison function.

7.0.1  The comparison function must be consistent

It is important to stress that for this container it is essential that the comparison function returns always the same result for two given elements. The context passed through this auxiliary arguments must not be used to change the result of the element comparison according to some external factor. Any inconsistency in the comparison function will destroy completely the whole container and the user will be unable to retrieve the data stored or (worst) retrieve the wrong data.

7.1  The interface

typedef struct tagTreeMapInterface {
    int (*Add)(TreeMap *ST, void *Data,void *ExtraArgs); 
    int (*Apply)(TreeMap *ST,
                 int (*Applyfn)(const void *data,void *arg),
                 void *arg);
    TreeMap *(*Copy)(TreeMap *src);
    TreeMap *(*CreateWithAllocator)(size_t ElementSize,
                               ContainerAllocator *m);
    TreeMap *(*Create)(size_t ElementSize);
    unsigned (*GetFlags)(TreeMap *ST);
    int (*Clear)(TreeMap *ST); 
    int (*Contains)(TreeMap *ST,void *element,void *ExtraArgs);
    int (*deleteIterator)(Iterator *);
    int (*Erase)(TreeMap *tree, void *element,void *ExtraArgs);  
    int (*Equal)(TreeMap *t1, TreeMap *t2);
    int (*Finalize)(TreeMap *ST);  
    void *(*Find)(TreeMap *tree,void *element,void *ExtraArgs);
    size_t (*GetElementSize)(TreeMap *d);
    TreeMap *(*InitializeWith)(size_t elementSize, size_t n,
                               void *Elements);
    int (*Insert)(TreeMap *RB, const void *Data, void *ExtraArgs);
    Iterator *(*NewIterator)(TreeMap *);
    TreeMap *(*Load)(FILE *stream, ReadFunction loadFn,void *arg);
    int (*Save)(TreeMap *src,FILE *stream, 
                SaveFunction saveFn,void *arg);
    CompareFunction (*SetCompareFunction)(TreeMap *ST,
                                          CompareFunction fn);
    DestructorFunction (*SetDestructor)(TreeMap *Tree,
                                       DestructorFunction fn);
    ErrorFunction (*SetErrorFunction)(TreeMap *ST, ErrorFunction fn);
    unsigned (*SetFlags)(TreeMap *ST, unsigned flags); 
    size_t (*Sizeof)(TreeMap *ST);
    size_t (*Size)(TreeMap *ST);  
} TreeMapInterface;

All the above functions were described for the sequential containers and their syntax is here the same.


8  Hash Table: iHashTable

Hash table is a similar container as dictionary, but allows for more features at the expense of a slightly more complicated interface. Keys aren't restricted to zero terminated strings but can be any kind of data. The table resizes itself as it grows. Merging two hash tables

8.1  The interface

typedef struct tagHashTableInterface {
   int (*Add)(HashTable *HT,const void *key,size_t klen,
        const void *Data);
   int (*Apply)(HashTable *HT,int (*Applyfn)(void *Key,size_t klen,
        void *data,void *arg),void *arg);
   int (*Clear)(HashTable *HT);
   int (*Contains)(const HashTable *ht,const void *Key,size_t klen);
   HashTable *(*Copy)(const HashTable *Orig,Pool *pool);
   HashTable *(*Create)(size_t ElementSize);
   int (*Erase)(HashTable *HT,const void *key,size_t klen);
   int (*Finalize)(HashTable *HT);
   void *(*GetElement)(const HashTable *HT,const void *Key,
         size_t klen);
   size_t (*GetElementSize)(const HashTable *HT);
   unsigned (*GetFlags)(const HashTable *HT);
   HashTable *(*Init)(HashTable *ht,size_t ElementSize);
   int (*InitIterator)(HashTable *SC,void *buf);
   HashTable *(*Load)(FILE *stream, ReadFunction readFn, void *arg);
   HashTable *(*Merge)(Pool *p, const HashTable *overlay,
         const HashTable *base, void * (*merger)(Pool *p,
         const void *key, size_t klen, const void *h1_val,
         const void *h2_val, const void *data), const void *data);
   Iterator *(*NewIterator)(HashTable *);
   HashTable *(*Overlay)(Pool *p, const HashTable *overlay,
               const HashTable *base);
   int (*Replace)(HashTable *HT,const void *key, size_t klen,
        const void *val);
   int (*Resize)(HashTable *HT,size_t newSize);
   int (*Save)(const HashTable *HT,FILE *stream, SaveFunction saveFn,
        void *arg);
   int (*Search)(HashTable *ht,int (*Comparefn)(void *rec,
         const void *key,size_t klen,const void *value), void *rec);
   DestructorFunction (*SetDestructor)(HashTable *v,
                      DestructorFunction fn);
   ErrorFunction (*SetErrorFunction)(HashTable *HT,ErrorFunction fn);
   unsigned (*SetFlags)(HashTable *HT,unsigned flags);
   GeneralHashFunction (*SetHashFunction)(HashTable *ht,
                        GeneralHashFunction hf);
   size_t (*Size)(const HashTable *HT);
   size_t (*Sizeof)(const HashTable *HT);
   size_t (*SizeofIterator)(const HashTable *ht);
   int (*deleteIterator)(Iterator *);
} HashTableInterface;

8.2  The API


Add

int (*Add)(HashTable *ht,
           void *key,
           size_t keyLength,
           const void *data);

Description: Adds the given element to the container using the given "key" string. It is assumed that "data" points to a contiguous memory area of at least ht->ElementSize bytes. Both the key and the data are copied into the container.
If an element exists with the given key, its contents are replaced with the new data.
Errors:
CONTAINER_ERROR_BADARG The hash table, the key or the data pointers are  NULL .
CONTAINER_ERROR_READONLY : The hash table is read-only. No modifications allowed.
CONTAINER_ERROR_NOMEMORY Not enough memory to complete the operation.
Returns: A positive number if the operation added a new element, zero if the data was written into an existing element, or a negative error code if an error occurred.
Example:

HashTable *ht;
    double data = 4.5;
    int result = iHashTable.Add(ht,"Interest rate",
                                  strlen("Interest rate"),&data);
    if (result < 0) { /* Error handling */ }


Apply

    int (*Apply)(HashTable *ht,
                  int (Applyfn)(const char *key,
                                size_t keyLength,
                                void *data,
                                void *extraArg),
                  void *extraArg);

Description: Apply will call the given function for each element of the array. The first argument of the callback function receives they key ,the second is the length of the key. The third is a pointer to one element of the table. The fourth argument of the callback is the ëxtraArg" argument that the Apply function receives and passes to the callback. This way some context can be passed to the callback, and from one element to the next.
Note that the result of the callback is not used. This allows all kinds of result types to be accepted after a suitable function type cast.
If the dictionary is read-only, a copy of the element will be passed to the callback function.
Errors:
CONTAINER_ERROR_BADARG Either the hash table pointer or Applyfn are  NULL .
CONTAINER_ERROR_NOMEMORY The hash table is read-only and there is no more memory to allocate the buffer to copy each element.
Example:

static int print(const char *key,
                      void *pElement,
                      void *pResult)
    {
        double *p = pElement;
        printf("%s: %g\n",key,*p);
        return 1;
    }
    int main(void) {
        Dictionary *dict = iDictionary.Create(sizeof(double),5);
        double d = 2;
        iDictionary.Add(dict,"First item",&d);
        d = 3;
        iDictionary.Add(dict,"Second item",&d);
        iDictionary.Apply(dict,print,NULL);
        return 0;
    }
Output should be:
First item: 2
Second item: 3


Clear

    int (*Clear)(HashTable *ht);

Description: Erases all stored data and releases the memory associated with it. The hash table header is not destroyed, and its contents will be the same as it was when initially created. It is an error to use this function when there are still active iterators for the container.
Returns: The result is greater than zero if successful, or an error code if an error occurs.
Errors:
CONTAINER_ERROR_BADARG The hash table pointer is  NULL .
CONTAINER_ERROR_READONLY The hash table is read only.
Example:

HashTable *ht;
    int m = iHashTable.Clear(ht);


Copy

    HashTable *(*Copy)(const HashTable *Orig,Pool *pool);

Description: Copies the given hash table using the given pool. If "pool" is  NULL ,the pool of the given hash table will be used.
Errors:
CONTAINER_ERROR_BADARG The hash table pointer is  NULL .
CONTAINER_ERROR_NOMEMORY Not enough memory to complete the operation.

Create

    HashTable *(*Create)(size_t ElementSize);

Description: Creates a new hash table and initializes all fields. The table will use the current memory manager for its pool.
Errors:
CONTAINER_ERROR_BADARG The parameter is zero or bigger than the maximum size the implementation supports.
CONTAINER_ERROR_NOMEMORY Not enough memory to complete the operation.

deleteIterator

    int (*deleteIterator)(Iterator *);

Description: Releases the memory used by the given iterator.
Errors:
CONTAINER_ERROR_BADARG The parameter is  NULL .
Returns:A positive value if successful or a negative error code.

Erase

    int (*Erase)(HashTable *HT,void *key,size_t keyLength);

Description: Removes from the hash table the element with the given key.
Errors:
CONTAINER_ERROR_BADARG The hash table parameter or the key pointer are  NULL , or the keyLength is zero.
Returns: A positive number if the operation completed, a negative error code otherwise.
Finalize Synopsis: int (*Finalize)(HashTable *HT); Description: Releases all memory used by the hash table and destroys the hash table header itself.
Errors:
CONTAINER_ERROR_BADARG The parameter is  NULL .

GetElement

void *(*GetElement)(const HashTable *H,const void *Key,size_t keyLen);

Description: Returns a pointer to the given hash table element.
Errors:
CONTAINER_ERROR_BADARG The hash table parameter or the key pointer are  NULL , or the keyLen parameter is zero.
Returns: A pointer to the element or  NULL if no element with the specified key exists.

GetFlags

    unsigned (*GetFlags)(const HashTable *HT);

Description: Returns an unsigned integer with the state of the table.

Load

 HashTable *(*Load)(FILE *stream,ReadFunction readFn,void *arg);

Description: Reads a table previously saved with the Save function from the stream pointed to by stream. If readFn is not  NULL , it will be used to read each element. The ärg" argument will be passed to the read function. If the read function is  NULL , this argument is ignored and a default read function is used.
Errors:
CONTAINER_ERROR_BADARG The given stream pointer is  NULL .
CONTAINER_ERROR_NOMEMORY There is not enough memory to complete the operation.
Returns: A new table or  NULL if the operation could not be completed. Note that the function pointers in the array are NOT saved in most implementations, nor any special allocator that was in the original table. In most implementations those values will be the values by default. To rebuild the original state the user should replace the pointers again in the new table.

Merge

    HashTable *(*Merge)(Pool *p, 
                        const HashTable *overlay, 
                        const HashTable *base,
                        void * (*merger)(Pool *p,
                                         const void *key,
                                         size_t keyLength,
                                         const void *h1_val,
                                         const void *h2_val,
                                         const void *data),
                        const void *data);

Description: Merge two hash tables into one new hash table. If the same key is present in both tables, call the supplied merge function to produce a merged value for the key in the new table. Both hash tables must use the same hash function. The arguments should be:
  1. The pool to use when allocating memory. If  NULL , the pool of the "base" hash table will be used.
  2. The first table to be used in the merge.
  3. The second table
  4. An argument to pass to the merger function.

NewIterator

    Iterator *(*NewIterator)(HashTable *HT);

Description: Allocates and initializes a new iterator object to iterate this table. The exact sequence of each object returned is implementation defined but it will be the same for the same dictionary with the same number of elements.
Errors:
CONTAINER_ERROR_BADARG The parameter is  NULL .
CONTAINER_ERROR_NOMEMORY Not enough memory to complete the operation.
Returns: A pointer to a new iterator or  NULL if the operation couldn't be completed.
Example:

HashTable *HT;
    Iterator *it = iHashTable.NewIterator(HT);
    double *d;
    for (d=it->GetFirst(it); d != NULL; d = it->GetNext(it)) {
        double val = *d;
        // Work with the value here
    }
    iHashTable.deleteIterator(it);


Overlay

    HashTable *(*Overlay)(Pool *p, 
                          const HashTable *overlay, 
                          const HashTable *base);

Description: Copies overlay into base. If conflicts arise, the data in base will be copied in the result.
Errors:
CONTAINER_ERROR_BADARG One of the arguments is  NULL .
CONTAINER_ERROR_NOMEMORY Not enough memory to complete the operation.

Resize

    int (*Resize)(HashTable *HT,size_t newSize);

Description: Will resize the given hash table to a new size. If the given new size is zero, the new size is implementation defined, and equal to the amount when automatic resizing occurs.
Errors:
CONTAINER_ERROR_BADARG The parameter is  NULL .
CONTAINER_ERROR_NOMEMORY Not enough memory to complete the operation.
Returns: A positive value if the operation completed, a negative error code otherwise.

Replace

    int  (*Replace)(HashTable *HT, const void *key,
                    size_t keyLength,const void *data);

Description: Will replace the contents of the given element if found.
Errors:
CONTAINER_ERROR_BADARG The hash table pointer, the key or the replacement data are  NULL , or the keyLength is zero.
Returns: A positive number if the element was replaced or zero if the element wasn't found. If the operation didn't complete a negative error code is returned.

Save

int (*Save)(const HashTable *HT, FILE *out, SaveFunction Fn, void *arg);

Description: The contents of the given table are saved into the given stream. If the save function pointer is not  NULL , it will be used to save the contents of each element and will receive the arg argument passed to Save, together with the output stream. Otherwise a default save function will be used and arg will be ignored. The output stream must be opened for writing and must be in binary mode.
Errors:
CONTAINER_ERROR_BADARG The array pointer or the stream pointer are  NULL .
EOF A disk input/output error occurred.
Returns: A positive value if the operation completed, a negative value or EOF otherwise.
Example:

HashTable *HT;
    FILE *outFile;
    if (iHashTable.Save(HT,outFile,NULL,NULL) < 0) { 
        /* Handle error here */
    }


SetErrorFunction

ErrorFunction (*SetErrorFunction)(HashTable *HT,ErrorFunction fn); 

Description: Replaces the current error function for the given table with the new error function if the ErrorFunction parameter is different from  NULL . If the HT parameter is  NULL the function returns the value of the current default error function.
Errors:
CONTAINER_ERROR_BADARG The table pointer is  NULL .
CONTAINER_ERROR_READONLY The table is read only and the function argument is not  NULL .
Returns: The old value of the error function or  NULL if there is an error.

Size

   size_t (*Size)(const HashTable *HT);

Description: Returns the number of elements stored in the given table.
Errors:
CONTAINER_ERROR_BADARG The table pointer is  NULL .
Returns: The number of elements stored in the table

Sizeof

   size_t (*Sizeof)(const HashTable *HT);

Description: Returns the number of bytes of storage used in the given table including the size of the elements stored in it. If HT is  NULL the result is the size of the HashTable header.
Returns: The number of elements stored in the table or the size of the HashTable header if the HT pointer is  NULL .



9  Queues: iQueue

Queues are a type of container adaptors, specifically designed to operate in a FIFO context (first-in first-out), where elements are inserted into one end of the container and extracted from the other.
The sample implementation shows how to implement this container as an ädaptor" container, i.e. based on another container. The implementation uses a linked list to implement a queue 38.

9.1  Interface

typedef struct tagQueueInterface {
   int (*Back)(Queue *Q,void *result);
   int (*Clear)(Queue *Q);
   Queue *(*Create)(size_t elementSize);
   Queue *(*CreateWithAllocator)(size_t elementSize,
           ContainerAllocator *allocator);
   int (*Dequeue)(Queue *Q,void *result);
   int (*Enqueue)(Queue *Q, void *Element);
   int (*Finalize)(Queue *Q);
   int (*Front)(Queue *Q,void *result);
   List *(*GetData)(Queue *q);
   size_t (*Size)(Queue *Q);
   size_t (*Sizeof)(Queue *Q);
} QueueInterface;

9.2  The API

All methods are exactly like the ones in other containers except for Enqueue, that is equivalent to Ädd" since adds one element at the end of the container, and Dequeue, that is the same as PopFront, i.e. pops the first element of the container.

Front

    int   (*Front)(Queue *Q,void *result);

Description: Returns the contents of the first element in the given memory area that should be at least the size of the element size of the queue. Note that nothing is changed, and the first element is not erased from the container.
Returns: A positive number for success, zero if the queue is empty or a negative error code.
Errors:
CONTAINER_ERROR_BADARG The Queue pointer is  NULL .

Back

    int   (*Back)(Queue *Q,void *result);

Description: Returns the contents of the last element in the given memory area that should be at least the size of the element size of the queue. Note that nothing is changed, and the last element is not erased from the container.
Returns: A positive number for success, zero if the queue is empty or a negative error code.
Errors:
CONTAINER_ERROR_BADARG The Queue pointer is  NULL .

GetData

List *(*GetData)(Queue *q);

Description: Queues are based on the list container. It is not necessary to duplicate all the list functions in the queue interface: this function allows you to access the underlying list and use all the list specific APIs with it.
Returns: A pointer to the list container or  NULL if the queue pointer passed is  NULL .

10  Deque: iDeque

Deque (usually pronounced like "deck") is an irregular acronym of double-ended queue. Double-ended queues are a kind of sequence containers. As such, their elements are ordered following a strict linear sequence. Deques may be implemented by specific libraries in different ways, but in all cases they allow for adding and retrieving elements at both ends, with storage always handled automatically (expanding and contracting as needed).
Operations to insert and retrieve elements in the middle are not provided because if users need a plain sequential container they can use one. Individual implementation can offer those if they think it is useful. This differs from the C++ implementation.
Here is a little table with a Rosetta stone for deque:
C Ada C++ Java Perl PHP Python
PushBack Append push_back offerLast push array_push append
PushFront Prepend push_front offerFirst unshift array_unshift appendleft
PopBack Delete_Last pop_back pollLast pop array_pop pop
PopFront Delete_First pop_front pollFirst shift array_shift popleft
Back Last_Element back peekLast $array[-1] end < obj > [-1]
Some functions that the C++ interface provides like is_empty() can be obtained in this implementation simply by invoking:
iDeque.Size(deque) == 0

10.1  Interface

The interface iDeque is as follows:
typedef struct tagDequeInterface {
   void (*Apply)(Deque *Q,int (*Applyfn)(void *,void * arg),
        void *arg);
   int (*Back)(Deque *d,void *outbuf);
   int (*Clear)(Deque *Q);
   size_t (*Contains)(Deque * d, void* item);
   Deque *(*Copy)(Deque *d);
   Deque *(*Create)(size_t elementSize);
   int (*Equal)(Deque *d1,Deque *d2);
   int (*Erase)(Deque * d, const void* item);
   int (*EraseAll)(Deque * d, const void* item);
   int (*Finalize)(Deque *Q);
   int (*Front)(Deque *d,void *outbuf);
   unsigned (*GetFlags)(Deque *Q);
   Deque *(*Init)(Deque *d,size_t elementSize);
   int (*InitIterator)(Deque *dc,void *buf);
   Deque *(*Load)(FILE *stream, ReadFunction readFn,void *arg);
   Iterator *(*NewIterator)(Deque *Deq);
   int (*PopBack)(Deque *d,void *outbuf);
   int (*PopFront)(Deque *d,void *outbuf);
   int (*PushBack)(Deque *Q,const void *Element);
   int (*PushFront)(Deque *Q, void *Element);
   int (*Reverse)(Deque * d);
   int (*Save)(const Deque *d,FILE *stream, SaveFunction saveFn,
        void *arg);
   DestructorFunction (*SetDestructor)(Deque *Q,
                      DestructorFunction fn);
   ErrorFunction (*SetErrorFunction)(Deque *d,ErrorFunction);
   unsigned (*SetFlags)(Deque *Q,unsigned newFlags);
   size_t (*Size)(Deque *Q);
   size_t (*Sizeof)(Deque *d);
   size_t (*SizeofIterator)(Deque *);
   int (*deleteIterator)(Iterator *);
} DequeInterface;

The deque container can be implemented as an adaptor container, for instance based on a double linked list or in an vector. In any case the underlying container interface is not visible.

Apply

    void (*Apply)(Deque *d,int (Applyfn)(void *,void *),void *arg);

Description: Will call the given function for each element. The first argument of the callback function receives an element of the array. The second argument of the callback is the arg argument that the Apply function receives and passes to the callback. This way some context can be passed to the callback, and from one element to the next. Note that the result of the callback is not used. This allows all kinds of result types to be accepted after a suitable cast. If the array is read-only, a copy of the element will be passed to the callback function.
Errors:
CONTAINER_ERROR_BADARG Either the deque or Applyfn are  NULL .
CONTAINER_ERROR_NOMEMORY The list is read-only and there is no more memory to allocate the buffer to copy each element.

Back

    int (*Back)(Deque *d,void *outbuf);

Description: Copies into the given buffer the last element stored in the Deque d.
Errors:
CONTAINER_ERROR_BADARG Either d or outbuf are  NULL .
Returns: A positive value of the operation completed, zero if the container is empty, or a negative error code otherwise.

Clear

    int     (*Clear)(Deque *Q);

Description: Erases all elements stored in the queue and reclaims the memory used. The Deque object itself is not destroyed.
Errors:
CONTAINER_ERROR_BADARG The deque pointer is  NULL .
CONTAINER_ERROR_READONLY The deque is read-only. No modifications allowed.

Contains

    size_t  (*Contains)(Deque * d, void* item);

Description: Searches the deque for the given data, returning its (index one based) position or zero if not found. Errors
CONTAINER_ERROR_BADARG The deque pointer is  NULL .
Returns: The index of element or zero if not found.

Copy

    Deque  *(*Copy)(Deque *d);

Description: Makes a copy of the given deque.
Errors:
CONTAINER_ERROR_BADARG The deque pointer is  NULL .
CONTAINER_ERROR_NOMEMORY Not enough memory to complete the operation.
Returns: A pointer to the new container or  NULL if the operation did not complete.

Create

    Deque  *(*Create)(size_t elementSize);

Description: Creates a new Deque container using ëlementSize" as the size that each element will have.
Errors:
CONTAINER_ERROR_BADARG The elementSize parameter is zero or bigger than what the implementation supports.
CONTAINER_ERROR_NOMEMORY Not enough memory to complete the operation.
Returns: A pointer to the new container or  NULL if the operation did not complete.
Example:

Deque *d = iDeque.Create(sizeof(myType));
    if (d == NULL) { /* Error handling */ }


Equal

    int (*Equal)(Deque *d1,Deque *d2);

Description: Compares the given deques using their comparison function. If they differ in their size, flags, or compare functions they compare unequal. If any of their elements differ, they compare unequal. If both d1 and d2 are  NULL they compare equal. If both are empty, they compare equal.
Errors:
None
Returns: The result is one if the deques are equal, zero otherwise.

Front

    int (*Front)(Deque *d,void *outbuf);

Description: Copies into the given buffer the first element stored in the Deque d.
Errors:
CONTAINER_ERROR_BADARG Either d or outbuf are  NULL .
Returns: A positive value of the operation completed, zero if the container is empty, or a negative error code otherwise.

Erase

    int (*Erase)(Deque * d, void* item);

Description: Erases the first occurrence of the given element from the container if found, starting from the front.
Errors:
CONTAINER_ERROR_BADARG The deque pointer or the item pointer are  NULL .
CONTAINER_ERROR_READONLY The deque is read-only. No modifications allowed.
Returns: A positive number if the item was found and erased, zero if the item wasn't found, or a negative error code if the operation did not complete.

Finalize

    int (*Finalize)(Deque *d);

Description: Reclaims all memory used by the container erasing all elements, if any. Then it destroys the container object itself.
Errors:
CONTAINER_ERROR_BADARG The deque pointer is  NULL .
CONTAINER_ERROR_READONLY The deque is read-only. No modifications allowed.
Returns: A positive number if the operation completed, a negative error code otherwise.

GetFlags

    unsigned (*GetFlags)(Deque *d);

Description: Retrieves the state of the flags. If the implementation doesn't support this field this function always returns zero.
Errors:
CONTAINER_ERROR_BADARG The deque pointer is  NULL .
Returns: The state of the flags field.

Load

 Deque *(*Load)(FILE *stream,ReadFunction readFn,void *arg);

Description: Reads a deque previously saved with the Save function from the stream pointed to by stream. If readFn is not  NULL , it will be used to read each element. The ärg" argument will be passed to the read function. If the read function is  NULL , this argument is ignored and a default read function is used.
Errors:
CONTAINER_ERROR_BADARG The given stream pointer is  NULL .
CONTAINER_ERROR_NOMEMORY There is not enough memory to complete the operation.
Returns: A new deque or  NULL if the operation could not be completed. Note that the function pointers in the deque are NOT saved in most implementations, nor any special allocator that was in the original table. In most implementations those values will be the values by default. To rebuild the original state the user should replace the pointers again in the new table.

PopBack

    int (*PopBack)(Deque *d,void *outbuf);

Description: Copies into the given buffer the last element stored in the Deque d, then erases the element from the deque.
Errors:
CONTAINER_ERROR_BADARG Either d or outbuf are  NULL .
Returns: A positive value of the operation completed, zero if the container is empty, or a negative error code otherwise.

PopFront

    int (*PopFront)(Deque *d,void *outbuf);

Description: Copies into the given buffer the first element stored in the Deque d, thnen erases the element from the deque.
Errors:
CONTAINER_ERROR_BADARG Either d or outbuf are  NULL .
Returns: A positive value of the operation completed, zero if the container is empty, or a negative error code otherwise.

PushBack

    int (*PushBack)(Deque *d,void *element);

Description: Adds the given element to the end of the deque. It is assumed that ëlement" points to a contiguous memory area of at least ElementSize bytes.
Errors:
CONTAINER_ERROR_BADARG The deque or the element pointers are  NULL .
CONTAINER_ERROR_READONLY The deque is read-only. No modifications allowed.
CONTAINER_ERROR_NOMEMORY Not enough memory to complete the operation.
Returns: A positive number if the operation added a new element, or a negative error code if an error occurred.
Example:

Deque *d;
    double data = 4.5;
    int result = iDeque.PushBack(d,&data);
    if (result < 0) { /* Error handling */ }


PushFront

    int (*PushFront)(Deque *d,void *element);

Description: Adds the given element to the start of the deque. It is assumed that ëlement" points to a contiguous memory area of at least ElementSize bytes.
Errors:
CONTAINER_ERROR_BADARG The deque or the element pointers are  NULL .
CONTAINER_ERROR_READONLY The deque is read-only. No modifications allowed.
CONTAINER_ERROR_NOMEMORY Not enough memory to complete the operation.
Returns: A positive number if the operation added a new element, or a negative error code if an error occurred.
Example:

Deque *d;
    double data = 4.5;
    int result = iDeque.PushFront(d,&data);
    if (result < 0) { /* Error handling */ }


Save

int (*Save)(const Deque *d, FILE *out, SaveFunction Fn, void *arg);

Description: The contents of the given deque are saved into the given stream. If the save function pointer is not  NULL , it will be used to save the contents of each element and will receive the arg argument passed to Save, together with the output stream. Otherwise a default save function will be used and arg will be ignored. The output stream must be opened for writing and must be in binary mode.
Errors:
CONTAINER_ERROR_BADARG The deque pointer or the stream pointer are  NULL . EOF A disk input/output error occurred.
Returns: A positive value if the operation completed, a negative value or EOF otherwise.
Example:

Deque *d;
    FILE *outFile;
    if (iDeque.Save(d,outFile,NULL,NULL) < 0) { 
        /* Handle error here */
    }




11  Priority queues

Priority queues are queues where each element has a priority associated with it. In this implementation the elements with the lowest priority associated with the data are served first.
The value of the priority key must be within the bounds set up by the two manifest constants:
CCL_PRIORITY_MIN
CCL_PRIORITY_MAX

They are defined by the implementation and they define a subset of an integer or long integer range. This allows the implementation to save some values for special "markers" if needed. If the implementation doesn't need this feature it can define the bounds as to cover the full possible range for the key type.
The key type is defined as the C99 type intptr_t: the integer type that can hold a pointer.

11.1  Interface

typedef struct tagPQueueInterface {
   int (*Add)(PQueue *Q,intptr_t key,const void *Element);
   int (*Clear)(PQueue *Q);
   PQueue *(*Copy)(const PQueue *src);
   PQueue *(*Create)(size_t elementSize);
   PQueue *(*CreateWithAllocator)(size_t elementSize,
            ContainerAllocator *allocator);
   int (*Equal)(const PQueue *q1,const PQueue *q2);
   int (*Finalize)(PQueue *Q);
   intptr_t (*Front)(const PQueue *Q,void *result);
   intptr_t (*Pop)(PQueue *Q,void *result);
   int (*Push)(PQueue *Q,intptr_t key,const void *Element);
   size_t (*Size)(const PQueue *Q);
   size_t (*Sizeof)(const PQueue *Q);
   PQueue *(*Union)(PQueue *left, PQueue *right);
} PQueueInterface;

11.2  A complete example

              
#include <stdlib.h>
#include "containers.h"
#define MAX_ITERATIONS 10
int main(void)
{
    char buf[20];
    PQueue *pq = iPQueue.Create(20);
    int r,i;
    for (i=0; i<MAX_ITERATIONS;i++) {
        char buf[20];
        int d = rand();
        snprintf(buf,20,"%d",d);
        r = iPQueue.Push(pq,d,buf);
        if (r < 0) break;
    }
    printf("The queue has %zu elements\n",iPQueue.Size(pq));
    printf("It uses %zu bytes. The size of the header structure is %zu\n",
            iPQueue.Sizeof(pq),iPQueue.Sizeof(NULL));
    r = iPQueue.Size(pq);
    for (i=0; i<r; i++){
        iPQueue.Pop(pq,buf);
        printf("%s ",buf);
    }
    printf("\n");
}
OUTPUT
The queue has 10 elements
It uses 968 bytes. The size of the header structure is 88
16807 101027544 282475249 470211272 984943658 1144108930 
1457850878 1458777923 1622650073 2007237709

The example uses two loops: one for filling the priority queue, the other for printing all its elements.
We create the priority queue with an element size of 20. This is more than enough to hold a character string containing the textual representation of the priority.
In the first loop (lines 9 to 15) we associate a priority that we obtain from the random number generator, to a character string containing the text representation of it. We use the Push primitive to add to the priority queue (line 13). In case of any error we stop.
We print some information about the queue in lines 16-18. In line 19 we store the length of the queue in a temporary variable. This is necessary since we will use the Pop primitive that erases the lowest priority element, so the length of the queue will change. We can't use MAX_ITERATIONS either because it could be that the queue doesn't have its full length because an error in line 13 forced us to break the first loop in line 14 withoutb reaching MAX_ITERATIONS 39.
The output is obviously insorted order, since the queue releases the data from the lowest priority to the highest.

11.3  The API


Add

int (*Add)(PQueue *q, intptr_t key,void *Element);

Description: Adds to the given queue q the given element Element with its associated priority key. This function is identical to the Push function below.

Clear

    int     (*Clear)(PQueue *Q);

Description: Erases all elements stored in the queue and reclaims the memory used. The priority queue object itself is not destroyed.
Errors:
CONTAINER_ERROR_BADARG The PQueue pointer is  NULL .
CONTAINER_ERROR_READONLY The queue is read-only. No modifications allowed.
Returns:A positive number if the queue is cleared, a negative error code otherwise.

Create

    PQueue  *(*Create)(size_t elementSize);

Description: Creates a new priority queue container using ëlementSize" as the size that each element will have.
Errors:
CONTAINER_ERROR_BADARG The elementSize parameter is zero or bigger than what the implementation supports.
CONTAINER_ERROR_NOMEMORY Not enough memory to complete the operation.
Returns: A pointer to the new container or  NULL if the operation did not complete.

Copy

    PQueue  *(*Copy)(PQueue *d);

Description: Makes a copy of the given queue.
Errors:
CONTAINER_ERROR_BADARG The queue pointer is  NULL .
CONTAINER_ERROR_NOMEMORY Not enough memory to complete the operation.
Returns: A pointer to the new queue or  NULL if the operation did not complete.

Create

    PQueue  *(*Create)(size_t elementSize);

Description: Creates a new priority queue container using ëlementSize" as the size that each element will have.
Errors:
CONTAINER_ERROR_BADARG The elementSize parameter is zero or bigger than what the implementation supports.
CONTAINER_ERROR_NOMEMORY Not enough memory to complete the operation.
Returns: A pointer to the new container or  NULL if the operation did not complete.

Equal

    int (*Equal)(PQueue * q1, PQueue *q2);

Description: Compares two priority queues for equality. Two  NULL pointers compare equal.
Errors:
None.
Returns: A positive number if the two queues are equal, zero otherwise.

Finalize

    int (*Finalize)(PQueue *d);

Description: Reclaims all memory used by the container erasing all elements, if any. Then it destroys the container object itself.
Errors:
CONTAINER_ERROR_BADARG The priority queue pointer is  NULL .
CONTAINER_ERROR_READONLY The deque is read-only. No modifications allowed.
Returns: A positive number if the operation completed, a negative error code otherwise.

Front

    intptr_t (*Front)(PQueue *q,void *result);

Description: Copies into the given buffer the element with the lowest priority in the queue q.
Errors:
CONTAINER_ERROR_BADARG Either the queue pointer or the result pointer is  NULL .
Returns: The value of the priority associated with the lowest priority element. If the queue is empty the result is INT_MIN.

Pop

    intptr_t (*Pop)(PQueue *d,void *outbuf);

Description: Copies into the given buffer the element with the lowest priority in the Deque d, then erases the element from the deque.
Errors:
CONTAINER_ERROR_BADARG Either d or outbuf are  NULL .
Returns: The priority value associated with the element or INT_MIN if the queue is empty.

Push

int (*Push)(PQueue *q, intptr_t key,void *Element);

Description: Adds to the given queue q the given element Element with its associated priority key

Size

size_t (*Size)(PQueue *q);

Description: Computes the number of elements in the given priority queue.

Sizeof

size_t (*Sizeof)(PQueue *q);

Description: Computes the number of bytes used in the given priority queue. If the argument q is  NULL it returns the number of bytes used by the header structure.


12  Bloom filters

Bloom filters allow you to determine cheaply and quickly if an element is member of a set without actually looking into the large set. This container doesn't store any data, just a series of bits indicating whether the element is there. It can return false answers, specifically a false positive meaning it can answer ÿes, the element is there" when in fact it is not. When it tells you however that the element is not there you can be sure it is not in the set. The probability that a false answer occurs can be calculated in function of the size reserved for the bit table: the bigger the table, the smaller the probability of a false answer for a fixed number of elements. 40 Figure

12.1  The interface: iBloomFilter

typedef struct tagBloomFilterInterface {
    size_t (*CalculateSpace)(size_t maxfElements,double probability);
    BloomFilter *(*Create)(size_t maxElements,double probability);
    size_t (*Add)(BloomFilter *b,const void *key,size_t keylen);
    int (*Find)(BloomFilter *b,const void *key,size_t keylen);
    int (*Clear)(BloomFilter *b);
    int (*Finalize)(BloomFilter *b);
} BloomFilterInterface;

12.2  The API


CalculateSpace

    size_t (*CalculateSpace)(size_t maxElements,double probability);

Description: Returns the space in bytes that would occupy a bloom filter to hold the given number of elements with the given probability. The probability parameter should be greater than zero and smaller than 1.0. For values very close to the values zero and one, a huge number of bits can be necessary and the filter creation function will return  NULL because of lack memory problems.
Errors:
CONTAINER_ERROR_BADARG The probability is smaller or equal than zero, or bigger or equal than one.
Returns: The number of bytes needed or zero in case of error.

Create

    BloomFilter *(*Create)(size_t maxElements,double probability);

Description: Creates and initializes a filter with space enough to hold MaxElements with the given probability for a false answer. The probability parameter should be greater than zero and smaller than 1.0. For values very close to the values zero and one, a huge number of bits can be necessary and the filter creation function will return NULL because of lack memory problems.
Errors:
CONTAINER_ERROR_BADARG The probability is smaller or equal than zero, or bigger or equal than one.
CONTAINER_ERROR_NOMEM There is no memory for the allocation of the necessary data structures.
Returns: A pointer to a newly allocated bloom filter or  NULL in case of error.

Add

    size_t (*Add)(BloomFilter *b,const void *key,size_t keylen);

Description: Adds the given key to the filter. The keylen argument should be the length of the key, that should never be zero.
Errors:
CONTAINER_ERROR_BADARG The filter pointer or the key pointer are  NULL , or the keylen is zero.
CONTAINER_ERROR_CONTAINER_FULL . The maximum number of elements has been reached.
Returns: The number of elements in the filter or zero if there is an error.

Find

    int (*Find)(BloomFilter *b,const void *key,size_t keylen);

Description: Searches the given key in the filter.
Errors:
CONTAINER_ERROR_BADARG The filter pointer or the key pointer are  NULL , or the keylen is zero.
Returns: One if the element is found, zero if it is not, or a negative error code if an error occurs.

Clear

    int (*Clear)(BloomFilter *b);

Description: Removes all elements from the filter. No memory is released.
Errors:
CONTAINER_ERROR_BADARG The given pointer is  NULL .
Returns: One if all elements were cleared, a negative error code otherwise.

Finalize

    int (*Finalize)(BloomFilter *b);

Description: Releases all memory held by the filter.
Errors:
CONTAINER_ERROR_BADARG The given pointer is  NULL .
Returns: One if all elements were cleared, a negative error code otherwise.


13  Value arrays

Value arrays are a group of containers that store the basic types of the language: short, int, long, long long, float, double, long double and have some specialized operations that should be done in hardware when the underlying CPU allows it. The objective here is to simplify the vector interface replacing the void * with the concrete type that these arrays hold.
We have the following ValArrays:
NameInterface nameElement type
ValArrayShortiValArrayShortshort
ValArrayIntiValArrayIntint
ValArrayUIntiValArrayUIntunsigned
ValArrayLongiValArrayLonglong
ValArrayDoubleiValArrayDoubledouble
ValArrayFloatiValArrayFloatfloat
ValArrayLongDoubleiValArrayLongDoublelong double
ValArrayLLongiValArrayLLonglong long
ValArrayULLongiValArrayULLongunsigned long long
ValArraySize_tiValArraySize_tsize_t
Some types can be just aliases for other types. For instance when int and long have the same size there is no point in providing a separate implementation. This will be always the case with the type size_t that will be an alias for one of the unsigned types. This type is needed to represent arrays of indices that can be used to select elements into another array.
The operations supported are the same as the vector data type with several differences:

13.1  Operations

Each operation described below needs two compatible arrays, i.e. arrays that have the same number of elements. If that is not the case an error occurs. A single number can be used in place of an array, extending it to the shape of the array. The naming convention is to add the "Scalar" token to the operation name, so we have for instance MultiplyWith and MultiplyWithScalar.
In general all operations use the data of the left argument and write their results into the left argument. The right argument remains unmodified. This allows to construct efficient RPN evaluators to avoid allocating intermediate results.

13.2  Slices and masks

A slice is a description of a certain portion of the array. It has three fields:
  1. Start, The zero based index of the element that starts the slice.
  2. Length. The number of elements that are selected by the slice.
  3. Increment. The number of elements that are skipped between elements when passing from one element of the slice to the next.
When a ValArray is created, the slice used is the default one: 0, Size(ValArray), 1. The slice starts at element zero, has the same number of elements that the number of elements in the array, and its increment is 1. Using the API SetSlice and ResetSlice you can modify the elements that will be selected for all operations. When a slice is active, all elements that aren't in the selected slice are ignored.
Slices are maintained by the library automatically. If you erase elements from the array until the slice is empty, the library automatically resets the slice. If you add elements, the length of the slice will increase if necessary.
A Mask is a boolean vector of ones or zeroes that selects elements from the array. It can be implemented as a bitstring or as a sequence of bytes, this is implementation defined. When used in combination with some ValArray operation, it selects the elements that will be affected by the operation.

13.3  The interface

This is a generic interface description. The ElementType token is replaced in each ValArray by the underlying type: int, double, etc. In the same style, ValArray and the ValArrayInterface tokens are replaced with the corresponding value array name and interface name.
typedef struct tagValArrayInterface {
   int (*Abs)(ValArray *src); 
   ElementType (*Accumulate)(const ValArray *src);
   int (*Add)(ValArray *AL,ElementType newval);
   int (*AddRange)(ValArray *AL,size_t n,
         const ElementType *newvalues);
   int (*And)(ValArray *left, const ValArray *right);
   int (*AndScalar)(ValArray *left, const ElementType right);
   int (*Append)(ValArray *AL1, ValArray *AL2);
   int (*Apply)(ValArray *AL,int (*Applyfn)(ElementType element,
        void * arg),void *arg);
   ElementType (*Back)(const ValArray *src);
   int (*BitLeftShift)(ValArray *data,int shift);
   int (*BitRightShift)(ValArray *data, const int shift);
   int (*Clear)(ValArray *AL);
   char *(*Compare)(const ValArray *left, const ValArray *right,
         char *bytearray);
   Mask *(*CompareEqual)(const ValArray *left,const ValArray *right,
         Mask *bytearray);
   Mask *(*CompareEqualScalar)(const ValArray *left,
          const ElementType right, Mask *bytearray);
   char *(*CompareScalar)(const ValArray *left,
          const ElementType right,char *bytearray);
   int (*Contains)(const ValArray *AL,ElementType data);
   ValArray *(*Copy)(const ValArray *AL);
   int (*CopyElement)(const ValArray *AL,size_t idx,
        ElementType *outbuf);
   ElementType *(*CopyTo)(ValArray *AL);
   ValArray *(*Create)(size_t startsize);
   ValArray *(*CreateSequence)(size_t n,ElementType start,
              ElementType increment);
   ValArray *(*CreateWithAllocator)(size_t startsize,
              ContainerAllocator *allocator);
   int (*DivideBy)(ValArray *left, const ValArray *right);
   int (*DivideByScalar)(ValArray *left, ElementType right);
   int (*DivideScalarBy)(ValArray *left,ElementType right);
   int (*Equal)(const ValArray *first, const ValArray *second);
   int (*Erase)(ValArray *AL,ElementType elem);
   int (*EraseAt)(ValArray *AL,size_t idx);
   Mask *(*FCompare)(const ValArray *left, const ValArray *right,
          Mask *bytearray,ElementType tolerance);
   int (*FillSequential)(ValArray *dst,size_t length,ElementType start,
         ElementType increment);
   int (*Finalize)(ValArray *AL);
   int (*ForEach)(ValArray *src,ElementType (*ApplyFn)(ElementType));
   int (*Fprintf)(const ValArray *src,FILE *out,const char *fmt);
   ElementType (*Front)(const ValArray *src);
   ContainerAllocator *(*GetAllocator)(const ValArray *AL);
   size_t (*GetCapacity)(const ValArray *AL);
   ElementType *(*GetData)(const ValArray *src);
   ElementType (*GetElement)(const ValArray *AL,size_t idx);
   size_t (*GetElementSize)(const ValArray *AL);
   unsigned (*GetFlags)(const ValArray *AL);
   ValArray *(*GetRange)(const ValArray *AL, size_t start,
              size_t end);
   int (*GetSlice)(ValArray *array,size_t *start,size_t *length,
         size_t *increment);
   ValArray *(*IndexIn)(const ValArray *SC,const ValArraySize_t *AL);
   int (*IndexOf)(ValArray *AL,ElementType data,size_t *result);
   ValArray *(*Init)(ValArray *AL,size_t startsize);
   int (*InitIterator)(ValArray *AL,void *buf);
   ValArray *(*InitializeWith)(size_t n, ElementType *data);
   int (*Insert)(ValArray *AL,ElementType);
   int (*InsertAt)(ValArray *AL,size_t idx,ElementType newval);
   int (*InsertIn)(ValArray *AL, size_t idx, ValArray *newData);
   int (*Inverse)(ValArray *src);
   ValArray *(*Load)(FILE *stream);
   ElementType (*Max)(const ValArray *src);
   int (*Memset)(ValArray *dst,ElementType fillValue,size_t length);
   ElementType (*Min)(const ValArray *src);
   int (*Mismatch)(const ValArray *a1,const ValArray *a2,
        size_t *mismatch);
   int (*Mod)(ValArray *left,const ValArray *right);
   int (*ModScalar)(ValArray *left,const ElementType right);
   int (*MultiplyWith)(ValArray *left, const ValArray *right);
   int (*MultiplyWithScalar)(ValArray *left, ElementType right);
   Iterator *(*NewIterator)(ValArray *AL);
   int (*Not)(ValArray *left);
   int (*Or)(ValArray *left, const ValArray *right);
   int (*OrScalar)(ValArray *left, const ElementType right);
   int (*PopBack)(ValArray *AL,ElementType *result);
   ElementType (*Product)(const ValArray *src);
   int (*PushBack)(ValArray *AL,ElementType data);
   int (*RemoveRange)(ValArray *src,size_t start,size_t end);
   int (*ReplaceAt)(ValArray *AL,size_t idx,ElementType newval);
   int (*ResetSlice)(ValArray *array);
   int (*Resize)(ValArray *src, size_t newSize);
   int (*Reverse)(ValArray *AL);
   int (*RotateLeft)(ValArray *AL, size_t n);
   int (*RotateRight)(ValArray *AL,size_t n);
   int (*Save)(const ValArray *AL,FILE *stream);
   int (*Select)(ValArray *src,const Mask *m);
   ValArray *(*SelectCopy)(const ValArray *src,const Mask *m);
   int (*SetCapacity)(ValArray *AL,size_t newCapacity);
   CompareFunction (*SetCompareFunction)(ValArray *l,
                   CompareFunction fn);
   DestructorFunction (*SetDestructor)(ValArray *cb,
                      DestructorFunction fn);
   ErrorFunction (*SetErrorFunction)(ValArray *AL,ErrorFunction);
   unsigned (*SetFlags)(ValArray *AL,unsigned flags);
   int (*SetSlice)(ValArray *src,size_t start,size_t length,
        size_t increment);
   size_t (*Size)(const ValArray *AL);
   size_t (*Sizeof)(const ValArray *AL);
   size_t (*SizeofIterator)(const ValArray *);
   int (*Sort)(ValArray *AL);
   int (*SubtractFrom)(ValArray *left, const ValArray *right);
   int (*SubtractFromScalar)(ElementType left, ValArray *right);
   int (*SubtractScalarFrom)(ValArray *left, ElementType right);
   int (*SumScalarTo)(ValArray *left,ElementType right);
   int (*SumTo)(ValArray *left,const ValArray *right);
   int (*Xor)(ValArray *left, const ValArray *right);
   int (*XorScalar)(ValArray *left, const ElementType right);
   int (*deleteIterator)(Iterator *);
} ValArrayInterface;




Figure

Abs

    int (*Abs)(ValArray *src);

Description: If any of the values in the source array is smaller than zero it will be negated. This function is only defined for signed or floating point types. It has no meaning with unsigned types.
Returns:Zero if the array was empty, a positive number if successful.

Accumulate

ElementType (*Accumulate)(ValArray *src);

Description: Calculates the sum of all the elements of the given vector. If a slice definition is active only the slice elements are considered.
Errors:
None are mandatory but implementations should check for overflow when possible.
Returns:The sum of the elements.

Add

    int (*Add)(ValArray *AL,ElementType newval);

Description: Adds an element at the end of the array. If a slice is active, the increment field (stride) will be used: the new element will be separated by the increment field of the slice and empty fields will be filed with zeroes. The length of the slice will be incremented by one.
Errors:
CONTAINER_ERROR_NOMEMORY Not enough memory to complete the operation.
Returns: A positive number if the element was added or a negative error code otherwise.

AddRange

    int (*AddRange)(ValArray *AL,size_t n,ElementType *newvalues);

Description: Adds a range of elements at the end of the array. If a slice is active, the increment field (stride) will be used: each new element will be separated by the increment field of the slice and empty fields will be filed with zeroes. The length of the slice will be incremented by n. If n is zero no error will be issued and the result is a positive number.
Errors:
CONTAINER_ERROR_NOMEMORY Not enough memory to complete the operation.
Returns: A positive number if the elements were added or a negative error code otherwise.

Apply

int (*Apply)(ValArray *AL,
             int (*Applyfn)(ElementType element,void * arg),
             void *arg);

Description: Calls the given function for each element of the array. If a slice is active only the elements in the slice will be used.
Errors:
None
Returns:A positive number

And

    int (*And)(ValArray *left,ValArray *right);

Description: Performs a bitwise AND operation between each element of the right argument with the corresponding element of the left argument. Conceptually this operation is: left &= right. This operation is allowed only between unsigned integer types. For floating point data this operation has no meaning. If a slice is active only the slice elements are affected. If both arrays have slices they must be compatible, i.e. they must have the same length.
Errors:
CONTAINER_ERROR_INCOMPATIBLE The arrays or slices have a different number of elements.
Returns:A positive number for success, or a negative error code.

BitLeftShift

    int (*BitLeftShift)(ValArray *dst,int shift);

Description: Shifts left each element of the given ValArray by shift bits. If shift is negative it performs a right shift instead.
Errors:
No errors.
Returns:A positive number or a negative error code if an implementation detects an invalid pointer. This error is not required to be detected.

BitRightShift

    int (*BitRightShift)(ValArray *dst,int shift);

Description: Shifts right each element of the given ValArray by shift bits. If shift is negative it performs a left shift instead.
Errors:
No errors.
Returns:A positive number or a negative error code if an implementation detects an invalid pointer. This error is not required to be detected.

Clear

   int (*Clear)(ValArray *array);

Description: Sets the number of elements to zero but doesn't release any memory. Any slice definitions are cleared.
Errors:
None.
Returns:A positive integer.

Compare

char *(*Compare)(ValArray *left,ValArray *right,
                       char *bitarray);

Description: Assigns to each byte of the bitarray the result of comparing the corresponding elements of the left and right arrays. Conceptually this operation is:
byte[i] = (left[i] < right[i]) ? -1 : (left[i] == right[i]) ? 0 : 1

If the bytearray argument is  NULL it will be allocated and returned. The allocator used is the one from the left argument. If it is not  NULL it will be assumed that it contains at least GetSize(left) positions available.
Errors:
CONTAINER_ERROR_NOMEMORY . The given byte array argument was  NULL but there is no memory to allocate the result.
CONTAINER_ERROR_INCOMPATIBLE The arrays are of different length.
Returns:A pointer to the bitarray or  NULL if an error occurs.

CompareScalar

char *(*CompareScalar)(const ValArray *left,
                       const ElementType right, char *bitarray);

Description: Assigns to each byte of the bitarray the result of comparing the elements of the left array with the right argument. Conceptually this operation is:
byte[i] = (left[i] < right) ? -1 : (left[i] == right) ? 0 : 1

If the bitarray argument is  NULL it will be allocated and returned. The allocator used is the one from the left argument. If it is not  NULL it will be assumed that it contains at least GetSize(left) positions available.
Errors:
CONTAINER_ERROR_NOMEMORY . The given byte array argument was  NULL but there is no memory to allocate the result.
Returns:A pointer to the bitarray or  NULL if an error occurs.

CompareEqual

Mask *(*CompareEqual)(ValArray *left,ValArray *right,
                       Mask bitarray);

Description: Assigns to each element of the mask the result of comparing the corresponding elements of the left and right arrays. Conceptually this operation is:
bit[i] = (left[i] == right[i])

If the bitarray argument is  NULL it will be allocated and returned. The allocator used is the global memory manager. If it is not  NULL it should contain at least enough positions to hold the data.
Errors:
CONTAINER_ERROR_NOMEMORY . The given byte array argument was  NULL but there is no memory to allocate the result.
CONTAINER_ERROR_BADARG The given mask hasn't enough positions available.
CONTAINER_ERROR_INCOMPATIBLE The arrays are of different length.
Returns:A pointer to the bitarray passed or allocated, or  NULL if an error occurs.

CompareEqualScalar

Mask *(*CompareEqual)(ValArray *left,ElementType right,
                       Mask *bitarray);

Description: Assigns to each bit of the bitarray the result of comparing the elements of the left array with the right argument. Conceptually this operation is:
bit[i] = (left[i] == right)

If the bitarray argument is  NULL it will be allocated and returned. The allocator used is the one from the left argument. If it is not  NULL it will be assumed that it contains at least
1+GetSize(left)/CHAR_BIT
positions available.
Errors:
CONTAINER_ERROR_NOMEMORY . The given byte array argument was  NULL but there is no memory to allocate the result.
Returns:A pointer to the bitarray or  NULL if an error occurs.

Contains

int (*Contains)(ValArray *a,ElementType data);

Description: Searches the given data in the array. If any slice specifications are active, only the slice is searched.
Errors:
None
Returns: One if the given data is stored in the array, zero otherwise.

Copy

    ValArray *(*Copy)(const ValArray *A);

Description: A copy of the given array is performed. The new memory will be allocated using the given array's allocator. If any slice specifications are current, only the elements of the slice will be copied into the resulting vector, that will have the size of the slice. Slice specifications are not copied.
Errors:
CONTAINER_ERROR_NOMEMORY There is not enough memory to complete the operation.
Returns:The new array.

CopyTo

ElementType *(*CopyTo)(ValArray *AL);

Description: Copies the whole contents of the given array into a table of newly allocated elements. If a slice specification is active only the slice will be returned.
Errors:
CONTAINER_ERROR_NOMEMORY There is not enough memory to complete the operation.
Returns: A pointer to a table or  NULL if an error occurs.

Create

ValArray *(*Create)(size_t capacity);

Description: Creates an empty array with enough storage to hold capacity elements.
Errors:
CONTAINER_ERROR_NOMEMORY Not enough storage to complete this operation.
Returns:A pointer to the empty array or  NULL if an error occurs.

CreateSequence

ValArray *(*CreateSequence)(size_t size,
                            ElementType startValue,
                            ElementType increment);

Description: Creates an array with size elements and fills it with elements of the ValArray data type starting with the startValue argument, and increasing it by the value of increment at each step. The increment value can be negative or zero. If it is zero the array is filled with the same value. This is equivalent to the Fill API43.
Errors:
CONTAINER_ERROR_NOMEMORY Not enough storage to complete this operation.
Returns:A pointer to the sequence or  NULL if an error occurs.

DivideBy

    int (*DivideBy)(ValArray *left,ValArray *right);

Description: Divides each element of the left argument by the corresponding element of the right argument. Conceptually this operation is: left /= right. If any of the elements of the right argument is zero, an error occurs and the computation stops, leaving the left argument with some elements divided and others not44.
Errors:
CONTAINER_ERROR_INCOMPATIBLE The arrays have a different number of elements.
CONTAINER_ERROR_DIVIDE_BY_ZERO The second argument has an element that is zero.
Returns:A positive number for success, or a negative error code.

DivideByScalar

    int (*DivideByScalar)(ValArray *left, ElementType right);

Description: Divides each element of the left argument by the right argument. Conceptually this operation is: left /= right. If the right argument is zero an error occurs and the left argument remains unchanged.
Errors:
CONTAINER_ERROR_DIVIDE_BY_ZERO The second argument is zero.
Returns:A positive number for success, or a negative error code.

DivideScalarBy

    int (*DivideScalarBy)(ElementType left, ValArray *right);

Description: Divides each element of the left argument by the right argument. Conceptually this operation is: right = left / right. If the left argument is zero an error occurs and the right argument remains unchanged.
Errors:
CONTAINER_ERROR_DIVIDE_BY_ZERO The first argument is zero.
Returns:A positive number for success, or a negative error code.

Equal

int (*Equal)(const ValArray *src1,const ValArray *src2);

Description: Returns 1 if both arrays are equal, zero otherwise. It is legal to compare an array with  NULL . If both arrays are  NULL they compare equal. Any slice definitions in the arrays must be equal. If equal, they restrict the number of elements compared.
Errors:
None.
Returns:True or false depending if the arrays are equal or not.

Erase

    int (*Erase)(ValArray *AL,ElementType data);

Description: Removes from the vector the element that matches the given data.
Errors:
CONTAINER_ERROR_NOTFOUND No match was found.
Returns: A negative error code if an error occurred, or a positive value that indicates that a match was found and the element was removed. If the element is not in the ValArray the result value is CONTAINER_ERROR_NOTFOUND .

EraseAll

    int (*EraseAll)(ValArray *v,const void *data);

Description: Removes from the array all elements that match the given data, that is assumed to be a pointer to an element.
Returns: A negative error code if an error occurred, or a positive value that indicates that at least a match was found and the element was removed. If the element is not in the list the result is CONTAINER_ERROR_NOTFOUND .

EraseAt

    int (*EraseAt)(ValArray *AL,size_t idx);

Description: Removes from the array the element at the given position. If a slice specification is defined for the array, the index is understood as an index within the slice and not as an index in the array.
Errors:
CONTAINER_ERROR_BADARG The given vector pointer is  NULL .
CONTAINER_ERROR_INDEX The given position is out of bounds.
Returns: A negative error code if an error occurred or a positive value that indicates that the element was removed.

FCompare

char *(*FCompare)(const ValArray *left, 
                  const ValArray *right,
                  unsigned char *bytearray, 
                  ElementType tolerance);

Description: This function is exactly like the Compare function but designed for comparing floating point numbers. Direct comparison of floating point numbers are known to be problematic. This comparison will be realized within the tolerance defined by the fourth parameter
The sample implementation uses the ideas of Donald Knuth 45 as implemeted by Theodore C. Belding 46 In the documentation of its software, Mr Belding writes:
What is needed is a comparison operator that takes into account a certain amount of uncertainty:
if (fabs(x - y) <= epsilon) {
  /* code to be executed if x == y */
}

if (x - y > epsilon) {
  /* code to be executed if x > y */
}

if (x - y < -epsilon) {
  /* code to be executed if x < y */
}

In the above code, a neighborhood is defined that extends a distance epsilon to either side of y on the real number line. If x falls within epsilon of y, x is declared to be equal to y (the first case, above). If x is greater than y by an amount that is greater than epsilon, x is declared to be greater than y (the second case, above). If x is less than y by an amount that is greater than epsilon, x is declared to be less than y (the third case, above).
The problem then becomes to determine an appropriate value of epsilon. A fixed value of epsilon would not work for all x and y; epsilon should be scaled larger or smaller depending on the magnitudes of the numbers to be compared.
A floating point number is represented by two numbers, the significand (also called the fraction or mantissa) and the exponent, and a sign, where
0 <= significand < 1
and
number = sign * significand * pow(2, exponent).
Knuth's suggestion is to scale epsilon by the exponent of the larger of the two floating point numbers to be compared:
∆ = epsilon ×maxExponent
where maxExponent is the exponent of max(x, y). Delta can then be substituted for epsilon in the code snippets above.
Determining epsilon
Now that we have found a way to scale epsilon to work with a wide range of x and y, we still need to choose an appropriate epsilon, before scaling.
If the number of binary digits of error, e, is known, then epsilon can be calculated as follows:
epsilon = (pow(2, e) - 1) * FLT_EPSILON         (for floats)
epsilon = (pow(2, e) - 1) * DBL_EPSILON         (for doubles)

FLT_EPSILON and DBL_EPSILON are equivalent to 1 ulp for single- and double-precision numbers, respectively; they are defined in the standard C header file <float.h>. (An ulp is one unit in the last place of the significand, or fraction part, of a floating point number; see Knuth for more details.)
Errors:
CONTAINER_ERROR_NOMEMORY . The given byte array argument was  NULL but there is no memory to allocate the result.
Returns:A pointer to the bitarray or  NULL if an error occurs.

FillSequential

int (*FillSequential)(ValArray *a,
                      ElementType start,ElementType increment);

Description: Assigns to all members of the array a sequence that starts at start, and is incremented by the given amount at each array position. The start and increment arguments can hold any value without restrictions, unless they go beyond the maximum value allowed for the given data type
Errors:
No errors.
Returns:A positive number.
Example:

ValARray *myValARray = iValArray.Create(6);
iValArray.FillSequential(myValArray,5,2);
// Now the contents of myValArray are:
5,7,9,11,13,15


Finalize

    int (*Finalize)(ValArray *AL);

Description: Reclaims all memory used by the container, including the array header object itself.
Errors:
None
Returns: A positive value means the operation completed.

ForEach

int (*ForEach)(ValArray *src,ElementType (*ApplyFn)(ElementType));

Description: Applies the one argument ApplyFn to each element of the array and stores the result in that element.
Returns:Always 147.

Fprintf

int (*Fprintf)(ValArray *src,FILE *out,const char *fmt);

Description: Prints in the indicated stream each element of the given array using the indicated format string. If the array is empty nothing is printed and the result is zero. There is a newline character appended to the output if the array wasn't empty and no error occurred.
Errors:
CONTAINER_ERROR_EOF An output error occurred: impossible to write to the stream.
Returns: The number of characters written to the stream, zero if the array was empty, or a negative error code.

GetCapacity

    size_t (*GetCapacity)(const ValArray *AL);

Description: Returns the number of elements the array can hold before it needs to reallocate its data.
Errors:
None
Returns: The array capacity.

GetData

ElementType *(*GetData)(const ValArray *AL);

Description: Returns a pointer to the data area of the container, or  NULL if an error occurs.
Errors:
CONTAINER_ERROR_READONLY The container is read-only.
CONTAINER_ERROR_BADARG The given pointer is  NULL
Returns:The pointer to the array's data or  NULL .

GetElement

    ElementType (*GetElement)(const ValArray *AL,size_t idx);

Description: Returns the value stored at the element with the given index.
Errors:
CONTAINER_ERROR_INDEX The given position is out of bounds.
Returns:The element's value or the minimum value that can be stored in ElementType if the index is out of bounds.

GetElementSize

  size_t (*GetElementSize)(const ValArray *AL);

Description: Returns the size of the elements stored in the ValArray. The argument is not used and can be  NULL .
Errors:
None.

GetRange

    ValArray *(*GetRange)(const ValArray *AL,size_t start,size_t end);

Description: Selects a series of consecutive elements starting at position start and ending at position one less than end. If start > end or start > Size(ValArray),  NULL is returned. If end is bigger than the number of elements in the array, only elements up to the number of elements will be used.
If a slice is active in the array, the arguments will be understood as indices in the slice and not in the original array.
The selected elements are copied into a new array. The original array remains unchanged.
Errors:
None
Returns:: A pointer to a new ValArray containing the selected elements or  NULL if an error occurs.

GetSlice

int (*GetSlice)(ValArray *array,size_t *start, size_t *length,
                 size_t *increment);

Description: Copies into the given pointers the contents of the current slice specifications for the given array. If any of the given pointers is  NULL nothing is copied into it and no error is issued. If the array has no slice specification all fields will be set to zero if not  NULL , and the result is zero.
Errors:
No errors
Returns:Zero if there isn't any slice specification, a positive number otherwise.
Example:

/* This expression allows you to determine if there 
   is a slice defined for a given array */
if (iValArray.GetSlice(myValArray,NULL,NULL,NULL)) {
    /* The array has a slice defined into it */
}


IndexIn

ValArray *(*IndexIn)(const ValArray *source,ValArraySize_t *indices);

Description: Returns an array built from indexing the first argument (ßource") with the array of indexes ïndices" that should be an array of size_t elements. The number of elements of the resulting array is equal to the number of elements of the indexes array.
Errors:
CONTAINER_ERROR_INDEX Any given position is out of bounds.
CONTAINER_ERROR_NOMEMORY There is not enough memory to complete the operation.
Returns: A new array or  NULL if an error occurs. No partial results are returned. If any index is out of bounds the whole operation fails.

IndexOf

int (*IndexOf)(const ValArray *l,ElementType data,size_t *result);

Description: Searches for an element in the array. If found its zero based index is returned in the pointer "result". Otherwise the result of the search is CONTAINER_ERROR_NOTFOUND . The ëxtraArgs" argument will be passed to the comparison function, that is used to compare elements.
Errors:
CONTAINER_ERROR_BADARG The given array pointer or the element given are  NULL .
Returns: A positive number if the element is found, or a negative number containing an error code or the negative constant CONTAINER_ERROR_NOTFOUND .

InitializeWith

    ValArray *(*InitializeWith)(size_t n, ElementType *data);

Description: Creates and initializes a new ValArray with the given data table. The first argument is the number of items in the table and the second is a pointer to a storage area that should contain at least the given number of items. The data is copied into the new array.
Errors:
CONTAINER_ERROR_NOMEMORY There is not enough memory to complete the operation.
Returns:A pointer to the new array or  NULL if an error occurs.

InsertAt

    int (*InsertAt)(ValArray *src,size_t idx,ElementType newData);

Description: Inserts the new element. The new element will have the given index, that can go from zero to the vector count inclusive, i.e. one more than the number of elements in the vector.
Errors:
CONTAINER_ERROR_INDEX The given position is out of bounds.
CONTAINER_ERROR_NOMEMORY There is not enough memory to complete the operation.
Returns: A positive value if the operation succeeded, or a negative error code if the operation failed.

Inverse

    int (*Inverse)(ValArray *src);

Description: Computes for each element the inverse (1/element) and stores it in-place. If a slice definition is active only their elements will be used. If any of the elements is zero the computation stops and the result is an error code.
This function is defined for the floating point types only48.
Errors:
CONTAINER_ERROR_DIVISION_BY_ZERO One of the elements is zero.
Returns:A positive number if successful, a negative error code otherwise.

Max

    ElementType (*Max)(const ValArray *src);

Description: Returns the biggest element in the container. If the container is empty it returns the smallest element that can be stored into the array's data type49.

Memset

    int (*Memset)(ValArray *dst,ElementType data,size_t length);

Description: Assigns to each element of the argument the given data. Conceptually this operation is: dst = data. If a slice is active in the destination array only the elements described by the slice are modified. If the given length is bigger than the number of elements in the array, the array will be expanded to accomodate the new elements.
Errors:
CONTAINER_ERROR_NOMEMORY There is not enough ressources to expand the array to the desired length.
Returns:A positive number for success, a negative error code otherwise.

Min

    ElementType (*Min)(const ValArray *src);

Description: Returns the smallest element in the container. If the container is empty it returns the biggest element that can be stored into the array's data type50.

Mismatch

int (*Mismatch)(const ValArray *left,const ValArray *a2,
                                         size_t *mismatch);

Description: Returns the index of the first element that is different when comparing both arrays in the passed pointer mismatch. If one array is shorter than the other the comparison stops when the last element from the shorter array is compared. The comparison stops when the first difference is spotted.
If there are slice definitions in one or both arrays, they will be used.
Errors:
None
Returns: If a mismatch is found the result is greater than zero and the mismatch argument will contain the index of the first element that compared unequal. This will be always the case for arrays of different length.
If both arrays are the same length and no differences are found the result is zero and the value pointed to by the mismatch argument is one more than the length of the arrays.
If an error occurs, a negative error code is returned. The mismatch argument contains zero.

MultiplyWith

    int (*MultiplyWith)(ValArray *left,ValArray *right);

Description: Multiplies each element of the right argument with the corresponding element of the left argument. Conceptually this operation is: left *= right.
Errors:
CONTAINER_ERROR_INCOMPATIBLE The arrays have a different number of elements.
Returns:A positive number for success, or a negative error code.

MultiplyWithScalar

    int (*MultiplyWithScalar)(ValArray *left, ElementType right);

Description: Multiplies each element of the left argument with the right argument. Conceptually this operation is: left *= right.
Errors:
None. Some implementations could detect a bad pointer.
Returns:A positive number for success, or a negative error code.

Not

    int (*Not)(ValArray *v);

Description: Performs a bitwise Not operation of each element of the argument. Conceptually this operation is: v = ~v. This operation is allowed only between unsigned integer types. For floating point data this operation has no meaning. If a slice is active, only its elements will be affected.
Errors:
CONTAINER_ERROR_INCOMPATIBLE The arrays have a different number of elements.
Returns:A positive number for success, or a negative error code.

Or

    int (*Or)(ValArray *left,const ValArray *right);

Description: Performs a bitwise or operation between each element of the right argument with the corresponding element of the left argument. Conceptually this operation is: left |= right. This operation is allowed only between unsigned integer types. For floating point data this operation has no meaning. This operation will only affect the elements in the active slice, if a slice is active.
Errors:
CONTAINER_ERROR_INCOMPATIBLE The arrays have a different number of elements.
Returns:A positive number for success, or a negative error code.

OrScalar

    int (*Or)(ValArray *left,ElementType right);

Description: Performs a bitwise or operation between each element of the left argument with the right argument. Conceptually this operation is: left |= right. This operation is allowed only between unsigned integer types. For floating point data this operation has no meaning.
Errors:
CONTAINER_ERROR_INCOMPATIBLE The arrays have a different number of elements.
Returns:A positive number for success, or a negative error code.

PopBack

int (*PopBack)(ValArray *AL,ElementType *result);

Description: Copies the last element into the given result buffer and deletes the element from the container. If the result buffer is  NULL , no copy is performed. If a slice specification is active, the element deleted will be the last element of the slice, and the length of the slice will be reduced by one. If the slice becomes empty, the slice specifications are reset.
Errors:
None
Returns: A negative value if an error occurs, zero if the array is empty or greater than zero if the operation succeeded.

Product

ElementType (*Product)(ValArray *src);

Description: Calculates the product of all the elements of the given vector. If a slice definition is active only the slice elements are considered.
Errors:
None are mandatory but implementations should check for overflow when possible.
Returns:The product of the elements.

Reverse

int (*Reverse)(ValArray *AL);

Description: Reverses the order of the elements of the given array.
Errors:
CONTAINER_ERROR_NOMEMORY Not enough memory for intermediate storage available
Returns: A negative error code if an error occurs, or a positive value if the operation succeeded.

ResetSlice

    int (*ResetSlice)(ValArray *array);

Description: Eliminates any slices specifications from the given array.
Errors:
None.
Returns:If a slice specification was removed returns 1, if no slice was defined in the given array returns zero.

RotateLeft

    int (*RotateLeft)(ValArray *src,size_t n);

Description: Rotates left the array by the indicated amount. The first n elements will be written to the end of the array, and the rest will be shifted left to fill the empty n places.
Errors:
None
Returns:A positive number if something was moved, zero otherwise (the input was zero or a modulo of the array size).
Example:

#include "containers.h"
int main(void)
{
        ValArrayInt * vInt = iValArrayInt.CreateSequence(24,0,1);

        printf("Original array: \n");
        iValArrayInt.Fprintf(vInt,stdout,"%d ");

        printf("Rotating left by 4\n");
        iValArrayInt.RotateLeft(vInt,4);
        iValArrayInt.Fprintf(vInt,stdout,"%d ");

        printf("Rotating left by 48\n");
        iValArrayInt.RotateLeft(vInt,48);
        iValArrayInt.Fprintf(vInt,stdout,"%d ");

        printf("Rotating left by 55\n");
        iValArrayInt.RotateLeft(vInt,55);
        iValArrayInt.Fprintf(vInt,stdout,"%d ");

        iValArrayInt.Finalize(vInt);
}

OUTPUT
Original array: 
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 
Rotating left by 4
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 0 1 2 3 
Rotating left by 48
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 0 1 2 3 
Rotating left by 55
11 12 13 14 15 16 17 18 19 20 21 22 23 0 1 2 3 4 5 6 7 8 9 10


RotateRight

    int (*RotateRight)(ValArray *src,size_t n);

Description: Rotates right the array by the indicated amount. The last n elements will be written to the start of the array, and the rest will be shifted right.
Errors:
None
Returns:A positive number if something was moved, zero otherwise (the input was zero or a modulo of the array size).

Save

int (*Save)(const ValArray *AL, FILE *out);

Description: The contents of the given ValArray are saved into the given stream. This function is a simplified version of the Save function in the Vector container since it doesn't feature a save function. Since ValArrays hold primitive types they are saved in a single write into the output stream. The output stream must be opened for writing and must be in binary mode.
Errors:
EOF A disk input/output error occurred.
Returns: A positive value if the operation completed, a negative value or EOF otherwise.

SetCompareFunction

CompareFunction (*SetCompareFunction)(ValArray *AL,
                                         CompareFunction f);

Description: This function does nothing and returns always  NULL . It is retained for compatibility purposes with other containers.
Errors:
None
Returns:Always  NULL .

Select

int (*Select)(ValArray *va,Mask *m);

Description: Using the given mask, the elements where the corresponding mask element is zero are eliminated, those with a mask value different of zero are retained. The mask must have the same length as the array.
Errors:
CONTAINER_ERROR_INCOMPATIBLE The mask and the array are of different length.
Returns:A positive value for success, or a negative error code otherwise.
Example:

#include "containers.h"
int main(void)
{
    Mask *m = iMask.Create(18);
    ValArrayInt *va = iValArrayInt.CreateSequence(18,0,1);
    size_t i;

    for (i=0; i<18; i++) {
        iMask.Set(m,i,(i&1) ? 0 : 1);
    }
    iValArrayInt.Fprintf(va,stdout,"%3d ");
    iValArrayInt.Select(va,m);
    iValArrayInt.Fprintf(va,stdout,"%3d ");
}
OUTPUT:
0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17
0   2   4   6   8  10  12  14  16 


SelectCopy

ValArray *(*SelectCopy)(ValArray *va,Mask *m);

Description: Using the given mask, the elements where the corresponding mask element is different from zero are copied into a new array, those with a mask value different of zero are ignored. The mask must have the same length as the array.
Errors:
CONTAINER_ERROR_INCOMPATIBLE The mask and the array are of different length.
Returns:A positive value for success, or a negative error code otherwise.

SetSlice

int (*SetSlice)(ValArray *s,size_t start,
                size_t length,size_t increment);

Description: Defines a slice for the given array. The slice starts at the given index, and has length elements. Between each element and the next there are increment positions. All operations done with the array will be done to the elements defined by the slice.
Constraints: If any slice specification was defined for the given array it will be replaced by the new one.
Errors:
CONTAINER_ERROR_BADARG One of the arguments doesn't meet the above constraints.
CONTAINER_ERROR_NOMEMORY . There is no memory to allocate the slice specifications.
Returns: Positive number if successful; negative error code otherwise.

Size

    size_t (*Size)(const ValArray *AL);

Description: Returns the total number of elements stored in the array. If there is a slice definition it will not be used. To know the size of a slice use theGetSlice API.

Sizeof

    size_t (*Sizeof)(ValArray *AL);

Description: Returns the total size in bytes of the ValArray, including the header, and all data stored in it. If the argument is  NULL , the size of the header only is returned.
Returns: The number of bytes used by the vector or the size of the ValArray header if the argument is  NULL .

Sort

    int (*Sort)(ValArray *AL);

Description: Sorts the given array. The order of the original array is destroyed. You should copy it if you want to preserve it. If a slice specification is active only the elements in the slice will be sorted.
Errors:
CONTAINER_ERROR_NOMEMORY Temporary storage for the operation is absent.
Returns: A positive number if sorting succeeded, a negative error code if not.

SubtractFrom

    int (*SubtractFrom)(ValArray *left,const ValArray *right);

Description: Subtracts each element of the right argument from the corresponding element of the left argument. Conceptually this operation is: left -= right.
Errors:
CONTAINER_ERROR_INCOMPATIBLE The arrays have a different number of elements.
Returns:A positive number for success, or a negative error code.

SubtractFromScalar

    int (*SubtractFromScalar)(ElementType left,ValArray *right);

Description: Subtracts from the left argument the right argument. Conceptually this operation is: right = left - right.
Errors:
None.
Returns:A positive number for success, or a negative error code.

SubtractScalarFrom

    int (*SubtractScalarFrom)(ValArray *left,ElementType right);

Description: Subtracts from the left argument the right argument. Conceptually this operation is: left -= right.
Errors:
None.
Returns:A positive number for success, or a negative error code.

SumTo

    int (*SumTo)(ValArray *left,ValArray *right);

Description: Adds each element of the right argument to the corresponding element of the left argument. Conceptually this operation is: left += right.
Errors:
CONTAINER_ERROR_INCOMPATIBLE The arrays have a different number of elements.
Returns:A positive number for success, or a negative error code.

SumToScalar

    int (*SumTo)(ValArray *left,ElementType right);

Description: Adds each element of the left argument the right argument. Conceptually this operation is: left += right.
Errors:
No errors.
Returns:A positive number for success, or a negative error code.

Xor

    int (*Xor)(ValArray *left,ValArray *right);

Description: Performs a bitwise Xor operation between each element of the right argument with the corresponding element of the left argument. Conceptually this operation is: left ^= right. This operation is allowed only between unsigned integer types. For floating point data this operation has no meaning.
Errors:
CONTAINER_ERROR_INCOMPATIBLE The arrays have a different number of elements.
Returns:A positive number for success, or a negative error code.

XorScalar

    int (*XorScalar)(ValArray *left,ElementType right);

Description: Performs a bitwise Xor operation between each element of the left argument with the right argument. Conceptually this operation is: left ^= right. This operation is allowed only between unsigned integer types. For floating point data this operation has no meaning.
Errors:
Returns:A positive number for success, or a negative error code.



14  Buffers

The buffers interface is greatly simplified compared to the interface of a general container. The usage of a buffer as an intermediate storage means there is no sense in including all the functionality of a container. The library provides two types:
  1. Stream buffers provide linear storage that grows automatically as needed.
  2. Circular buffers store the last n items of a stream
Other languages provide similar features: The design objective in this library is to keep buffers small and, while providing functionality, reduce the interface to a minimum. Compacting is not feasible in C due to the wide use of pointers. If there is a pointer to the data in the buffer, moving it would invalidate the pointer making for hard to debug crashes 51.

14.1  Stream buffers

Figure
This objects are designed to store sequentially arbitrary data, resizing themselves as necessary. There is a cursor, a pointer that indicates where the next data item will be written. You can move the cursor, overwriting old data, or leaving holes in the buffer structure 52 .

14.1.1  The interface

typedef struct tagStreamBufferInterface {
   int (*Clear)(StreamBuffer *b);
   StreamBuffer *(*Create)(size_t startsize);
   StreamBuffer *(*CreateFromFile)(const char *FileName);
   StreamBuffer *(*CreateWithAllocator)(size_t startsize,
   int (*Finalize)(StreamBuffer *b);
   char *(*GetData)(const StreamBuffer *b);
   size_t (*GetPosition)(const StreamBuffer *b);
   size_t (*Read)(StreamBuffer *b, void *data, size_t siz);
   int (*ReadFromFile)(StreamBuffer *b,FILE *f);
   int (*Resize)(StreamBuffer *b,size_t newSize);
   int (*SetPosition)(StreamBuffer *b,size_t pos);
   size_t (*Size)(const StreamBuffer *b);
   size_t (*Write)(StreamBuffer *b,void *data, size_t siz);
   int (*WriteToFile)(StreamBuffer *b,FILE *f);
} StreamBufferInterface;

14.1.2  The API

Noteworthy are the two functions that work with with buffers and files: ReadFromFile and WriteToFile. The allow you to work with a buffer when the file can't be read in one buffer, allowing piecewise processing of portions of the file.
The function CreateFromFile allows you to read all the file contents into a buffer.

Clear

    int (*Clear)(StreamBuffer *b);

Description: Sets the cursor at position zero and zeroes the whole buffer.
Errors:
CONTAINER_ERROR_BADARG The given buffer pointer is  NULL
Returns:A positive value if successful, a negative error code otherwise.

Create

    StreamBuffer *(*Create)(size_t startsize);

Description: Creates a new buffer with the given start size. If the size is zero it will use a default start value. The allocator used is the current memory manager.
Errors:
CONTAINER_ERROR_NOMEMORY . There is no more memory to create the buffer.
Returns:A pointer to a newly created buffer or  NULL if there is no more memory left.

CreateFromFile

    StreamBuffer *(*CreateFromFile)(const char *FileName);

Description: Creates a new buffer with the contents of the given file. The allocator used is the current memory manager. The file is read in binary mode.
Errors:
CONTAINER_ERROR_NOMEMORY . There is no more memory to create the buffer.
CONTAINER_ERROR_NOENT The file given can't be found (or it exists but the current user has no read permission).
Returns:A pointer to a newly created buffer or  NULL if there is no more memory left or the file can't be opened.

CreateWithAllocator

StreamBuffer *(*CreateWithAllocator)(size_t startsize, 
                                     ContainerAllocator *allocator);

Description: Creates a new buffer using the given allocator and start size. If the start size is zero a default value is used.
Errors:
CONTAINER_ERROR_NOMEMORY There is no more memory to complete the operation.
Returns:A pointer to the new buffer or  NULL if there is no memory left.

Finalize

    int (*Finalize)(StreamBuffer *b);

Description: Releases all memory used by the buffer.
Errors:
CONTAINER_ERROR_BADARG The given buffer pointer is  NULL .
Returns:A positive value if successful or anegative error code.

GetData

    const char *(*GetData)(const StreamBuffer *b);

Description: Returns a pointer to the data stored in the buffer. The data is read-only (const).
Errors:
CONTAINER_ERROR_BADARG The given buffer pointer is  NULL
Invariants: The given buffer is not modified. The returned pointer should not be modified.
Returns:A pointer to the buffer's data or NULL, if an error occurs.

GetPosition

    size_t (*GetPosition)(const StreamBuffer *b);

Description: Returns the current cursor position.
Errors:
CONTAINER_ERROR_BADARG The stream buffer pointer is  NULL
Invariants: The stream buffer is not modified.
Returns:The cursor position or zero if there is an error. Note that zero is also a valid cursor position.53

Read

    size_t (*Read)(StreamBuffer *b, void *data, size_t siz);

Description: Reads siz bytes from the given buffer, starting from the position of the cursor. If the buffer finishes before siz characters are read, reading stops, and less characters than requested are returned. It is assumed that the data buffer contains at least siz characters.
Errors:
CONTAINER_ERROR_BADARG Either the stream buffer, the data buffer are  NULL .
Invariants: None. The given buffer is modified since the cursor is updated to the new position. The given buffer is modified since the data is copied into it.
Returns:The number of characters copied or zero if there is an error. Note that if the number of requested characters is zero, this function will also return zero.

ReadFromFile

int (*ReadFromFile)(StreamBuffer *b,FILE *f);

Description: Fills the given buffer with data from the given file. The cursor is reset to position zero.
Errors:
CONTAINER_ERROR_BADARG The stream buffer pointer is  NULL
Returns:The number of bytes read or a negative error code.

Resize

   int (*Resize)(StreamBuffer *b,size_t newSize);

Description: Resizes the buffer to the requested size. The new size can be bigger or smaller than the current size. All pointers to the data in the buffer are invalid after this operation.
Errors:
CONTAINER_ERROR_BADARG The stream buffer pointer is  NULL .
CONTAINER_ERROR_NOMEMORY There is not enough memory to satisfy the request.
Returns:A negative error code if an error occurs, zero if the requested size is equal to the current size, or a positive number if the request was satisfied.

SetPosition

int (*SetPosition)(StreamBuffer *b, size_t pos);

Description: Sets the cursor at the given position. If the position is bigger than the size of the buffer the cursor is moved to the end of the buffer.
Errors:
CONTAINER_ERROR_BADARG The given buffer pointer is  NULL
Returns:A positive value if successful, a negative error code otherwise.

Size

size_t (*Size)(const StreamBuffer *b);

Description: Returns the allocated size of the buffer. If the buffer pointer is  NULL returns the size of the buffer header.
Errors:
None
Invariants: The given buffer is not modified.
Returns:The size of the buffer.

Write

    size_t (*Write)(StreamBuffer *b,void *data, size_t siz);

Description: Writes into the buffer siz characters from the passed pointer data. The characters are written starting at the cursor position. If the buffer is too small to hold the data, it will be enlarged using its allocator.
Errors:
CONTAINER_ERROR_NOMEMORY . There is no more memory to enlarge the buffer.
CONTAINER_ERROR_BADARG The stream buffer pointer or the data pointer is  NULL .
Returns:The number of characters written.
Example:

#include <containers.h>
int main(void)
{
        StreamBuffer *sb = iStreamBuffer.Create(10);
        int i;
        char buf[20],*p;

        for (i=0; i<10; i++) {
                sprintf(buf,"item %d",i+1);
                iStreamBuffer.Write(sb,buf,1+strlen(buf));
        }
        buf[0]=0;
        iStreamBuffer.Write(sb,&buf,1);
        printf("Buffer size is: %d, position is %d\n",
                            (int)iStreamBuffer.Size(sb),
                            (int) iStreamBuffer.GetPosition(sb));
        iStreamBuffer.SetPosition(sb,0);
        p = iStreamBuffer.GetData(sb);
        while (*p) {
                printf("%s\n",p);
                p += 1 + strlen(p);
        }
        iStreamBuffer.Finalize(sb);
        return 1;
}
OUTPUT:
Buffer size is: 82, position is 72
item 1
item 2
item 3
item 4
item 5
item 6
item 7
item 8
item 9
item 10

This example does the following:

WriteToFile

int (*WriteToFile)(StreamBuffer *b,FILE *outfile);

Description: Writes all the contents of the given buffer to the given file. The cursor is reset to the begin of the buffer.
Errors:
CONTAINER_ERROR_BADARG The stream buffer pointer or the file pointer is NULL returns The number of bytes written.

14.2  Circular buffers

Figure
This objects are designed to store the last n items of a stream. When they are full, the new items are stored in the same place as the oldest item 54.

14.2.1  The interface: iCircularBuffer

typedef struct tagCircularBufferInterface {
    int (*Add)( CircularBuffer * b, const void *data_element);
    int (*Clear)(CircularBuffer *cb);
    CircularBuffer *(*CreateWithAllocator)(size_t sizElement,
                              size_t sizeBuffer,
                              const ContainerAllocator *allocator);
    CircularBuffer *(*Create)(size_t sizElement,size_t sizeBuffer);
    int (*Finalize)(CircularBuffer *cb);
    int (*PopFront)(CircularBuffer *b,void *result);
    int (*PeekFront)(CircularBuffer *b,void *result);
    size_t (*Size)(const CircularBuffer *cb);
    DestructorFunction SetDestructor(CircularBuffer *cb,
                                       DestructorFunction NewFn);
} CircularBufferInterface;

14.2.2  The API


Add

	int (*Add)( CircularBuffer * b, const void *data_element);

Description: Adds the given data element to the circular buffer. If the buffer is full, the oldest element's place will be overwritten with the new data and the container remains full with the same number of elements.
Errors:
CONTAINER_ERROR_BADARG One or both arguments are  NULL .
Invariants: The given data element is not modified but copied into the container.
Returns:A negative error code if an error occurs. If the container is full zero is returned. If a new element was added a positive number is returned.

Clear

	int (*Clear)(CircularBuffer *cb);

Description: Resets the number of elements inside the container to empty without freeeing the memory used by the buffer.
Errors:
CONTAINER_ERROR_BADARG The buffer pointer b is  NULL .
Returns:A negative error code if an error occurs, or a positive number when the container is reset.

CreateWithAllocator

	CircularBuffer *(*CreateWithAllocator)(size_t ElementSize,
                 size_t sizeBuffer, ContainerAllocator *allocator);

Description: Creates an empty circular buffer that can hold at most sizeBuffer elements, each element being of size ElementSize. Uses the given allocator to allocate memory.
Errors:
CONTAINER_ERROR_BADARG One or both sizes are zero, or the allocator pointer is  NULL .
CONTAINER_ERROR_NOMEM There is no memory left.
Returns:A pointer to a new circular buffer or  NULL if an error occurs.

Create

	CircularBuffer *(*Create)(size_t ElementSize, size_t sizeBuffer);

Description: Creates an empty circular buffer that can hold at most sizeBuffer elements, each element being of size ElementSize. Uses the CurrentAllocator to allocate memory.
Errors:
CONTAINER_ERROR_BADARG One or both arguments are zero.
CONTAINER_ERROR_NOMEM There is no memory left.
Returns:A pointer to a new circular buffer or  NULL if an error occurs.

Finalize

	int (*Finalize)(CircularBuffer *cb);

Description: Reclaims all memory used by the given buffer.
Errors:
CONTAINER_ERROR_BADARG The buffer pointer is  NULL .
Returns:A positive value if the container is destroyed, a negative error code otherwise.

PeekFront

	int (*PeekFront)(CircularBuffer *b,void *result);

Description: Copies one item from the front of the circular buffer into the given buffer without removing the item from the container.
Errors:
CONTAINER_ERROR_BADARG The buffer pointer or the result buffer are  NULL .
Returns:A negative error code if an error occurs, zero if the buffer was empty, or a positive number if an item was copied.

PopFront

	int (*PopFront)(CircularBuffer *b,void *result);

Description: Copies one item from the front of the circular buffer into the given buffer and removes the item from the container. If the result pointer is  NULL the item is removed but nothing is copied.
Errors:
CONTAINER_ERROR_BADARG The buffer pointer is  NULL .
Returns:A negative error code if an error occurs, zero if the buffer was empty, or a positive number if an item was removed.
Example:

#include <containers.h>
int main(void)
{
    CircularBuffer *cb = iCircularBuffer.Create(sizeof(int),10);
    int i,integer;

    for (i=0; i<20;i++) {
        iCircularBuffer.Add(cb,&i);
    }
    printf("There are %d elements\n",iCircularBuffer.Size(cb));
    printf("The container uses %d bytes\n",iCircularBuffer.Sizeof(cb));
    printf("The size of the header is %d\n",
                          iCircularBuffer.Sizeof(NULL));
    /* Print all elements */
    while (iCircularBuffer.PopFront(cb,&integer) > 0) {
        printf("%d ",integer);
    }
    return 0;
}
OUTPUT:
There are 10 elements
The container uses 88 bytes
The size of the header is 48
10 11 12 13 14 15 16 17 18 19


Size

size_t (*Size)(const CircularBuffer *cb);

Description: Computes the number of items in the given circular buffer.
Errors:
CONTAINER_ERROR_BADARG The buffer pointer is  NULL .
Invariants: The given buffer is not modified.
Returns:The number of items in the buffer.

Sizeof

size_t (*Sizeof)(const CircularBuffer *cb);

Description: Computes the number of bytes used by given circular buffer. If the pointer is  NULL returns the size of the circular buffer header structure.
Invariants: the given buffer is not modified.
Returns:The number of bytes used by the buffer.


15  The generic interfaces

This interface allows the user to use containers in a generic way, ignoring its specific type. Note that there is no "GenericContainer" object; you can't create any generic container. Once a specific container is created, it can be used as a generic container at any time since all containers comply with the generic interface. This interface just dispatches internally to the actual container and therefore incurs in a slight performance cost. 55
Conceptually, the generic interfaces represent a base class (GenericContainer) and two derived classes: Sequential and Associative containers. It would be possible to derive more classes, for instance a numeric container class that could be implemented in the future, This is left open for future releases of this specification. 56

15.1  Generic containers

The general generic interface that encloses associative and sequential containers is as follows:
typedef struct GenericContainer GenericContainer;
typedef struct tagGenericContainerInterface {
    size_t (*Size)(const GenericContainer *Gen);
    unsigned (*GetFlags)(GenericContainer *Gen);
    unsigned (*SetFlags)(GenericContainer *Gen,unsigned flags);
    int (*Clear)(GenericContainer *Gen);
    int (*Contains)(GenericContainer *Gen,void *Value);
    int (*Erase)(GenericContainer *Gen,void *objectToDelete);
    int (*Finalize)(GenericContainer *Gen);
    void (*Apply)(GenericContainer *Gen,
                  int (*Applyfn)(void *,void * arg),
                  void *arg);
    int (*Equal)(GenericContainer *Gen1,GenericContainer *Gen2);
    GenericContainer *(*Copy)(GenericContainer *Gen);
    ErrorFunction (*SetErrorFunction)(GenericContainer *Gen,
                                      ErrorFunction fn);
    size_t (*Sizeof)(GenericContainer *Gen);
    Iterator *(*NewIterator)(GenericContainer *Gen);
    int (*deleteIterator)(Iterator *);
    int (*Save)(GenericContainer *Gen,FILE *stream, 
                SaveFunction saveFn,void *arg);
    GenericContainer *(*Load)(FILE *stream, 
                              ReadFunction readFn,void *arg);
    size_t GetElementSize(GenericContainer *Gen);
} GenericContainerInterface;
extern GenericContainerInterface iGenericContainer;

This functions return the obvious results already described in the documentation of their container-specific counterparts and not repeated here. We only note the absence of a creation function, or any means to add an object. 57
Based on the generic interface, we have generic sequential and associative interfaces. They contain generic functions for adding and removing objects.

15.2  Sequential containers

These containers include all the functions of the GenericContainer interface, adding functions to use any sequential container as a stack, and functions for managing object replacement or addition.
typedef struct SequentialContainer SequentialContainer;
typedef struct tagSequentialContainerInterface {
    GenericContainerInterface Generic;
    int (*Add)(SequentialContainer *SC,void *Element);
    void *(*GetElement)(SequentialContainer *SC,size_t idx);
    int (*Push)(SequentialContainer *Gen,void *Element);
    int (*Pop)(SequentialContainer *Gen,void *result);
    int (*InsertAt)(SequentialContainer *SC,size_t idx, void *newval);
    int (*EraseAt)(SequentialContainer *SC,size_t idx);
    int (*ReplaceAt)(SequentialContainer *SC, 
                     size_t idx, void *element);
    int (*IndexOf)(SequentialContainer *SC,
                   void *ElementToFind,size_t *result);
    int (*Append)(SequentialContainer *SC1,SequentialContainer *SC2);
} SequentialContainerInterface;
extern SequentialContainerInterface iSequentialContainer;

15.3  Associative containers

These containers include all the functions of the GenericContainer interfaces and add functions for inserting and removing objects.
typedef struct AssociativeContainer AssociativeContainer;
typedef struct tagAssociativeContainerInterface {
    GenericContainerInterface Generic;
    int (*Add)(SequentialContainer *SC,void *key,void *Element);
    void *(*GetElement)(AssociativeContainer *SC,void *Key);
    int (*Replace)(AssociativeContainer *SC, void *Key, void *element);
} AssociativeContainerInterface;
extern AssociativeContainerInterface iAssociativeContainer;

Chapter 6
Enhancing the library

No design can ever cover all special cases that can arise during development. The advantage of the interface design is that you can enhance the library by subclassing functions that add functionality you need when absent. Subclassing means in this context that you replace a function of the library with a new function written by you that either replaces completely the functionality of the library or that either before or after the library function adds some code that implements an enhancement.
There are several ways to enhance the library in this way:
  1. Replace the function in the container interface object. This affects all containers of this type, including those that are already created. This involves simply assigning to the function you want to replace a new function pointer that points to a compatible function. You can save the old value and add some functionality, call the old function pointer to do what the library does, then you can add code that runs after the old library function has finished.
  2. Replace the function in a copy of the functions table of a single object. This way is less intrusive than the former, since only one container is affected: the one where you modify the function table. The downside is that instead of using the simple syntax:
    iList.Add
    you have to use the container's table:
    Container->VTable->Add(...)
    This represents quite a different syntax, but this can be less of a problem if you hide it under some convenient macros 58.
    On the up side, another advantage of this syntax is that you do not need to change your source code if you change the type of the container. If you write:
    myContainer->Vtable->Add(myContainer,object);
    this will stay the same for lists, arrays, string collections or whatever sequential container you are using. You can then change completely the type of the container just by changing the declaration.

1  Adding conversions between containers

Converting one type of container into another, or creating a new container with some or all the data of an existing one are routine operations not specified in the core library.
The reason is that there is a downside to the interface definitions as presented here: all functions within an interface module are declared static to avoid polluting the user name space with those names. This has the consequence that interfaces are a monolithic piece of code that can't be splitted.
Converting an object from type Ä" to another of type "B" implies then that we have both interfaces present. If for every container we would define a conversion into all others, the function table of each container would need all other interfaces and if a user uses just a single container it would need to link with the whole library. To avoid this problem, no conversions are specified even when surely converting a ValArrayInt into a ValArrayDouble is an operation that will be needed sooner or later.
There are many solutions to this problem using the existing APIs:
  1. Using a simple loop:
    ValArrayDouble *CastToDouble(ValArrayInt *src)
    {
        size_t siz = iValArrayInt.Size(src);
        ValArrayDouble *result = iValArrayDouble.Create(siz);
        size_t i;
    
        for (i=0; i<siz;i++) {
            double d = iValArrayInt.GetElement(src,i);
            int r = iValArrayDouble.Add(result,d);
            if (r < 0)
                break;
        }
        return result;
    }
    
    
    This function stops at an error returning a partial result. Other error handling strategies could be to finalize the incomplete container and return  NULL , or call the iError interface and then do a long jump to a recovery point, etc.
  2. Using iterators:
    ValArrayDouble *CastToDouble(ValArrayInt *src)
    {
        size_t siz = iValArrayInt.Size(src);
        ValArrayDouble *result = iValArrayDouble.Create(siz);
        Iterator *it = iValArrayInt.NewIterator(src);
        int *ip;
    
        for (ip = it->GetFirst(src); ip;ip = it->GetNext(it)) {
            int r = iValArrayDouble.Add(result,*ip);
            if (r < 0)
                break;
        }
        iValArrayInt.DeleteIterator(it);
        return result;
    }
    
    
  3. Using the Apply API. There are surely more complex requirements for conversions. For instance we could need to extract only certain parts of the input container. In that case writing a special function to be called by Apply is justified. For instance if we have an array of structures representing customers we could want to make a string collection with the names of all of them.
    // We want to extract the second field of this structure
    struct customer {
        long long NumericID;
        char *Name;
        long double Balance;
    };
    
    int ApplyFn(void *data,void *container)
    {
        struct customer *pData = (struct customer *)data;
        strCollection *Collection = (strCollection *)container;
        return istrCollection.Add(Collection,pData->Name);
    }  
    
    strCollection *GetAllNames(Vector *CustomerDB)
    {
        size_t siz = iVector.Size(CustomerDB);
        strCollection *result = istrCollection.Create(siz);
        iVector.Apply(CustomerDB,ApplyFn,result);
        return result;
    }
    
    

2  Infinite arrays

We can conceptually define an array as a function that maps an input value index into some output that is the value of the array at that position.
In this context, an infinite array is a function that maps any member from the set of positive natural numbers (a size_t) into some resulting value. This function must have a value for all possible input values of its size_t argument. For instance the function value=(index+5)/(index−5) is not usable since it would provoke a division by zero at index=5.
Infinite arrays exist in many computer languages.
Since in the C language arrays must contain elements of the same type, obvious restrictions apply: all C types have specific bounds (defined in the appropiate headers) so that a conceptually correct function like the Fibonacci function for instance, is not usable beyond a certain value of the input index because of output overflow: the Fibonacci numbers grow without limit.
To implement an infinite array using the library is relatively easy. The iVector interface has the necessary hooks for doing this. When an index error occurs, the library calls the error function of the given vector passing it the name of the function, the integer constant CONTAINER_ERROR_INDEX and a pointerto the array and the requested index. If the error function returns any other value than  NULL , the Library will assume that it is a valid pointer to some result where the real value of the array at that position is stored.
Using this information we can write this first simple implementation of an infinite array. The array function will be the identity function i.e. the array will contain the value of the index at each position.
              
#include <stdarg.h>
#include <stdlib.h>
#include "containers.h"
static ErrorFunction oldErrorFn;

static void *Fn(const char *msg,int errorCode,...)
{
    va_list ap;
    size_t idx;
    static int value;
    Vector *v;
    if (errorCode != CONTAINER_ERROR_INDEX)
        return oldErrorFn(msg,errorCode);
    va_start(ap,errorCode);
    v = va_arg(ap,Vector *);
    idx = va_arg(ap,size_t);
    value = idx;
    va_end(ap);
    return &value;
}

Vector *CreateInfiniteArray(void)
{
    VectorInterface *ivct;
    Vector *result = iVector.Create(sizeof(int),1);
    if (result == NULL) return result;
    oldErrorFn = iVector.SetErrorFunction(result, Fn);
    return result;
}

int main(void)
{
    Vector *v = CreateInfiniteArray();
    int i;

    for (i=20; i<30;i++) {
        printf("%d ",*(int *)iVector.GetElement(v,i));
    }
    printf("\n");
    iVector.Finalize(v);
}


The central piece of the implementation is the Fn function (lines 6 to 20) that will be our replacement of the default vector error function. This function will only return something if the error is an error index (line 12). Otherwise it calls the default function stored in a static pointer.
If the error is the expected index error, we fetch the arguments (lines 15 and 16) and we set the value. The address of the static area is returned.
We have to write a special creation function (lines 22 to 29) that creates a vector and replaces its error function with our own, saving the old value in a global variable. This value will be used if the error is not an index error.
We can now write our test program that returns 10 integers from our array. Its output is
20 21 22 23 24 25 26 27 28 29 
Note that our ïnfinite" array is still a perfectly valid vector object and if you use it in a "normal" way it will store the data you give it and return that data when you index it as any array. What it is shown in the example above is just how we can change the return value of the functions when an index error is detected by the library. All other uses are untouched and the vector will still behave as a normal vector.
Note also that if the error is something different than the error the software wants to modify the old procedure is called. This means that this type of changes can be built in a cascade, each one handling only a definite type of error.
We have used here the generic interface using void pointers. Obviously we could use an integer vector instead of the generic one. To do that it would be necessary to change all lines that contain Vector into intVector and then call the iintVector.Create function instead of the plain iVector.Create.

2.1  Zero extensible arrays

To make a zero extensible array (used in signal processing) you would just change the line 17 above and instead of writing
value = idx;
you would write:
value = 0;
This type of array would return always zero for any index outside the limits of the array.

2.2  Arrays extensible by insert

Within the error procedure it is possible to call any function of the library. For instance, we can detect that the index error is the result of the ÏnsertAt" API, and decide to enlarge the array automatically. To do this we should return a non  NULL value from within our error procedure.
As you can see, that is already the case! That is why the next section is called:

2.3  Pitfalls

When you change the default procedure you must be careful to ensure that the only situations where you modify the library's behavior are the ones you want to modify.

Chapter 7
Applications

1  Unique

The main point in using the C containers library is the increase in program abstraction. In this example we will see how the library can be used to solve in a few lines a classroom problem.

1.0.1  Task description

Given a text file, print in standard output the lines that are unique in it, i.e. filtering all duplicated lines.

1.0.2  Algorithm

Normally this involves keeping a sorted list/array of lines and testing if a line is in the set or not.

1.0.3  Solution using the CCL

              
 #include <containers.h>
 int main(int argc,char *argv[])
 {
         FILE *f;
         int i=1,r;
         Dictionary *dict;
         char buf[8192];
         if (argc < 2) {
                 fprintf(stderr,"%s <file name>\n",argv[0]);
                 return -1;
         }
         f = fopen(argv[1],"r");
         if (f == NULL)
                 return -1;
         dict = iDictionary.Create(0,500);
         if (dict == NULL)
                 return -1;
         while (fgets(buf,sizeof(buf),f)) {
                 r= iDictionary.Add(dict,buf,NULL);
                 if (r > 0)
                         printf("[%3d] %s",i,buf);
                 else if (r < 0) break;
                 i++;
         }
         iDictionary.Finalize(dict);
         fclose(f);
 }

1.0.4  Algorithm

A hash table will be used to determine if a line is a duplicate or not.

1.0.5  Commentary

We use the following local variables (lines 4-7):
Name Usage
f Input stream bound to the file to read
i Counter for lines read
r Result of adding a line
dict Dictionary (Hash table)
buf Line buffer limited to 8K per line
Lines 9-15 are concerned with opening the input file, with some error checking.
In line 16 we create a dictionary, requesting a size of zero for the data associated with the key since we aren't storing any data, just the key, and we suppose that the table will contain more or less 500 entries. If the file contains much more lines performance could suffer but the algorithm would still work.
Lines 19-25 are the main loop of the program. We read each line into the buffer and add it to then dictionary. If the Ädd" API returns a positive number the line wasn't there, if it returns zero the line was already in the dictionary. If the result is negative it is an error code and we stop the loop aborting the operation. Failure can be provoked only by lack of memory.
If the result is positive we print the line.
Cleanup is performed in lines 26 and 27: we dispose of the dictionary and close the file.

2  Paste

2.0.6  Task description

Given two text files, print each line of them in the same line separated by a tab character. If files have a different number of lines stop when the first file is exhausted.
Note that this specifications may be different from the Unix utility of the same name.

2.0.7  Solution

              
#include "containers.h"
int main(int argc,char *argv[])
{
    FILE *f1,*f2;
    char *line1,*line2;
    strCollection *file1,*file2;
    Iterator *it1,*it2;
  
    if (argc < 3) {
        fprintf(stderr,"%s: file1 file2\n",argv[0]);
        return -1;
    }
    file1 = istrCollection.CreateFromFile(argv[1]);
    file2 = istrCollection.CreateFromFile(argv[2]);
    if (file1 != NULL && file2 != NULL) {
        it1 = istrCollection.NewIterator(file1);
        it2 = istrCollection.NewIterator(file2);
        line2 = it2->GetFirst(it2); line1 = it1->GetFirst(it1);
        for (; line1; line1 = it1->GetNext(it1)) {
            if (line2) {
                printf("%s\t%s\n",line1,line2);
                line2 = it2->GetNext(it2);
            }
            else printf("%s\n",line1);
        }
        istrCollection.deleteIterator(it1);
        istrCollection.deleteIterator(it2);
    }
    istrCollection.Finalize(file1);
    istrCollection.Finalize(file2);
    return 0;
}

2.0.8  Commentary

We create two string collections containing the text (lines 13-14). Two iterators are used to get each line of both files( lines 16 17). The rest is just cleanup: we delete the iterators (lines 26-27) and finalize the string collections (lines 29 30).
This solution using the library is shorter and easier to write than a solution reading each line with fgets() but needs enough memory to hold both files in memory at the same time.

3  Mapcar

The lisp function "mapcar" produces a map by applying a given function to each element of a list.
(mapcar #'abs '(3 -4 2 -5 -6)) => (3 4 2 5 6) 

We can reproduce this function by using Äpply". In the extra argument we pass a structure of two members: Our function receives then (as all functions called by Apply) two arguments, the element and a pointer to our structure. Here is a sketch of how could it be done:
#include <containers.h>
struct MapcarArgs {
    void  *(*fn)(void *);
     List *Result;
};

We keep some generality by using a general prototype and definition for the function we are using. We could have defined the callback as:
    int (*fn)(int *);

That prototype would have been unusable for lists that use doubles, for instance. With the current definition we can use this "MapcarArgs" structure with any other list.
The actual function we are calling encapsulates all knowledge about the data stored in the list and the operation we perform with that data. The other parts of the software do not need to know anything about it. It returns a static pointer to the result of the operation it performs using the given element as data that will be overwritten at each call. The intended usage is to save that result before making the next call. It can be defined as follows:
void *DoAbsValue(void *element)
{
    static int result = *(int *)element;
    if (result < 0)
        result = -result;
    return &result;
}
/* This function will be directly called by "Apply". */
static int Applyfn(void *element, struct MapcarArgs *args)
{
    void *result = args->fn(element);
    int r = iList.Add(args->Result,result);
    return r;
}
List *mapcar(List *li,void *(*fn)(void *))
{
        struct MapcarArgs args;

        args.fn = fn;
        args.Result = iList.Create(iList.GetElementSize(li));
        if (args.Result == NULL)
                return NULL;
        iList.Apply(li,Applyfn,(void *)&args);
        return args.Result;
}

int main(void)
{
    List *li = iList.Create(sizeof(int));
    List *newList;
    int i;
    int tab[] = {3,-4,2,-5,6};
    for (i=0; i<5;i++) {
        iList.Add(li,&tab[i]);
    }
    newList = mapcar(li,DoAbsValue);
}

Still, our version of mapcar is still specific to lists. A more general version would use a sequential container to make a mapcar function that would be able to work with any type of sequential container.
The basic idea is to provide an empty container of the desired result type as an extra argument to mapcar. We use an iterator instead of Äpply", obtaining a single compact function that will take any sequential container as input an add the result of the function to any type of sequential container.
int mapcar(SequentialContainer *src,         /* The source container */
           void *(*fn)(void *),/* Function to call with each element */
           SequentialContainer *result)   /* The resulting container */
{
        Iterator *it = iSequentialContainer.NewIterator(src);
        int r=1;
        void *obj;
        if (it == NULL)
               return CONTAINER_ERROR_NOMEMORY;
        for (obj = it->GetFirst(it);
              obj != NULL;
              obj = it->GetNext(it)) {
              void *tmp = fn(obj);
              int r = iSequentialContainer.Add(result,tmp);
              if (r < 0) {
                    /* In case of any error return a partial result 
                    and the error code */
                    break;
               }
        }
        deleteIterator(it);
        return r;
}

Other similar functions can be built from this model. For instance "mapcon", a function that needs two containers to build a resulting container. The result is made out of the results of a binary function that will receive one element from each container.
Its implementation is trivially deduced from the above function:
int mapcon(SequentialContainer *src1,
           SequentialContainer *src2,  /* The input containers */
           void *(*fn)(void *,void *),  /* Function with 2 arguments */
           SequentialContainer *result)   /* The resulting container */
{
        Iterator *it1 = iSequentialContainer.NewIterator(src1);
        Iterator *it2 = iSequentialContainer.NewIterator(src2);
        int r=1;
        void *obj1,*obj2;
        if (it == NULL)
               return CONTAINER_ERROR_NOMEMORY;
        if (iSequentialContainer.GetElementSize(src1) != 
            iSequentialContainer.GetElementSize(src2)) {
            return CONTAINER_ERROR_INCOMPATIBLE;
        }
        for (obj1 = it1->GetFirst(it1),obj2 = it2->GetFirst(it2);
              obj1 != NULL && obj2 != NULL;
              obj2 = it2->GetNext(it2),
              obj1 = it1->GetNext(it1)) {
              void *tmp = fn(obj1,obj2);
              int r = iSequentialContainer.Add(result,tmp);
              if (r < 0) {
                    /* In case of any error return a partial result 
                    and the error code */
                    break;
               }
        }
        deleteIterator(it1);
        deleteIterator(it2);
        return r;
}

We can use it with a function that adds its two arguments to add two containers:
void *DoAdd(void *element1,void *element2)
{
    static int result = *(int *)element1 + *(int *)element2;
    return &result;
}

Note that not all errors are detected, and we stop at the smallest container, producing a result compatible with the smallest of both arguments. Note too that we make a very superficial compatibility test to see if the arguments contain the same type of object, using their size as an indication. This test would ignore elements of the same size but incompatible, for instance floats and 32 bit integers, or 64 bit integers and double precision elements, etc.
The standard answer to the above problems is to point out that C has a tradition of keeping things simple and expecting programmers that take care of low level details. If you want more error support, you will find out with minimal research a lot of languages ready to make all kinds of hand holding for you.

Chapter 8
The sample implementation

The objective of the sample implementation is to serve as a guide for the implementers of this proposal. It is not the fastest implementation and it is not the most efficient or compact one. As any other software, it contains bugs, that I hope to iron out with time.
Please note that all the decisions done for the sample implementation are not part of the specifications of the containers library. Other implementations could do completely different things.

1  The different source files

The sample implementation has two types of files:
  1. Source files that implement a specific container: list.c vector.c, etc. This containers use a void pointer to receive thir arguments and return a void pointer when retrieving their data.
  2. Source files that implement a templated container, i.e. the file needs a parameter file and a templated implementation file. In this type of files we have a small file that defines the templated file parameters (in the form of pre-processor macros) and then just includes the templated implementation file. This containers receive data of a concrete type passed by value and return the same data passed by value.
FileDescription
bitstrings.cThe bitstring container.
bloom.cThe bloom filter container
buffer.cGrowable buffers and circular buffers
ccl_internal.hDefinitions of all the types defined internally by the library.
containers.hMain header file of the library. It defines all the user visible interfaces.
deque.cThe deque container
dlistgen.hGeneric definition of the double linked list data-type specific containers.
dictionary.cHash tables using ASCII text as key. This is just a small file that sets the parameters for the dictionarygen.c file
dictionarygen.cCommon code for the dictionary container using either ASCII or wide characters keys
dlist.cDouble linked list container using void pointers to store the data.
dlistgen.cDouble linked list container using a specific data type. Needs a parameter file
dlistgen.hDouble linked list container data definitions and interface definitions using a specific data type. Needs a parameter file
doubledlist.cDouble linked list for double data. This is a parameter file for dlistgen.c
doubledlist.hSingle linked list header file for double data. This is a parameter file for dlistgen.h
doublelist.cSingle linked list: Parameter file for listgen.c
doublelist.hSingle linked list: parameter file for listgen.h using double data
error.cThe error interface
fgetline.cReads a line of text from a file. Used in the string container
generic.cThe generic container interface
hashtable.cHash table featuring binary keys
heap.cSmall object allocator
iMask.cMask interface implementation
intdlist.cParameter file for dlistgen.c
intdlist.hParameter header file for dlistgen.h
intlist.cParameter file for listgen.c
intlist.hParameter header file for listgen.h
list.cGeneric type list container using void pointers
listgen.cTemplate file for list container
longlongdlist.cParameter file for dlistgen.c.
longlonglist.cParameter file for listgen.c
malloc_debug.cDebug implementation of malloc
MakefileMake file for Unix systems using the gcc compiler
Makefile.lccMake file for windows systems using the 32 bit lcc compiler
Makefile.lcc64Make file for windows systems using the 64 bit lcc compiler
Makefile.msvcMake file for windows systems using the Microsoft compiler
memoryanager.cVery short file describing the CurrentAllocator mechanism.
observer.cThe implementation of the observer pattern
pool.cPooled memory manager
pooldebug.cDebug version of the pool memory manager
priorityqueue.cPriority queues implementation
qsortex.cQuick sort algorithm
queue.cQueue container
redblacktree.cRed black tree implementation. Not yet documented
scapegoat.c"Scapegoat" trees implementation
searchtree.cAVL trees. Not documented
sequential.cImplementation of the general sequential container interface
smallpool.cNot documented yet
strcollection.cSpecialized vector for ascii character strings. This is a parameter file for strcollectiongen.c.
stringlist.cSpecialized version of single linked lists using strings
stringlistgen.cCommon code for ascii and wide character strings specialized vector
valarraydouble.cValue array of double data. Parameter file for valarraygen.c
valarrayfloat.cValue array of float data. Parameter file for valarraygen.c
valarrayint.cValue array of int data. Parameter file for valarraygen.c
valarraylongdouble.cValue array of long double data. Parameter file for valarraygen.c
valarraylonglong.cValue array of long long data. Parameter file for valarraygen.c
valarrayshort.cValue array of short data. Parameter file for valarraygen.c
valarraysize_t.cValue array of size_t data. Parameter file for valarraygen.c
valarrayuint.cValue array of unsigned int data. Parameter file for valarraygen.c
valarrayulonglong.cValue array of unsigned long long data. Parameter file for valarraygen.c
valarraygen.cCommon code for all val array implementations.
valarraygen.hCommon header file for all val array implementations. Needs a parameter file
vector.cCode for the vector container
vectorgen.hHeader file for the template form of the vector container
vectorgen.cImplementation of the template form of the vector container
wdictionary.cDictionary for a dictionary using wide character strings
wstrcollection.cString collection using wide character data. Parameter file for strcollectiongen.c

1.1  Building the software

The library comes with several "makefiles" to build it automatically. Those makefiles build a library called libccl.a under Unix systems, or ccl.lib under windows.

2  Partitioning

An interface is a table of functions. This tables are a monolithic construct: if you use only one of the functions of the interface you will have to link with all of the interface functions, whether you use them or not.
It is important then, that each interface doesn't "pull in" other interfaces since then you would end up linking with the whole library even if you use a small fraction of it.
Some of this is inevitable though. All interfaces use the observer interface, so if you use any interface the observer interface will be pulled in. It is important then, that the dependencies of the observer interface be kept to a minimum 61. Problem is, it is necessary for the observer interface to set the flags of the container being registered as an observed object. This means that the generic interface is needed.
Go avoid pulling in the whole generic interface, the observer functions use the vTable. In the sample implementation all containers have a table of functions as first interface field. Since the description of the generic container object is published in "containers.h" it can use the generic notation.
GenericContainer *gen = (GenericContainer *)InputObject;
unsigned flags = gen->vTable->GetFlags(gen);

This works because the sample implementation has carefully designed all interface to be binary compatible, allowing an easy implementation of the generic interface. All vTables have the GetFlags function at the same place, so we always call the correct function.
Another interface that is used by all other interfaces is the error interface. It has been kept as small as possible to avoid pulling in too much data into the fixed overhead.

3  Data structures

All container data structures are composed of two parts:
  1. A header part, containing a pointer to the functions table and some other fields. This 'generic' part is at the start of all container header structures.
  2. A container specific part, containing auxiliary structures and data needed for the specific container at hand.

3.1  The generic part

The first part of all container data structures is the same for each container. This allows to implement conceptually an abstract class of objects: the 'generic' container.
struct GenericContainer {
    GenericContainerInterface *vTable;
    size_t Size;
    unsigned Flags;
    size_t ElementSize;
};

  1. Vtable. All containers in the sample implementation contain a pointer to the table of functions of their interface.
  2. Size. The number of elements this container stores.
  3. Flags. Stores the state of the container. The only flag the sample implementation uses is the READ_ONLY_FLAG but many others are possible, for instance a 'locked' flag for multi-threading access, or a 'copy on write' flag for lazy copy, etc.
  4. ElementSize. All containers in the sample implementation can store objects of the same size. This is not really a limitation since you can store objects of ANY size by storing a pointer in the container. An alternative design would store objects of any size but it would need to store the size of each object in addition to the data used by the object. The specialized containers like bitstrings, string collections or integer/double arrays do not need this field obviously, and its presence is optional.

3.2  Lists

Figure
Single linked lists use a single pointer to the next element. The data for the element comes right behind that pointer to avoid the overhead that yet another pointer would represent.
typedef struct _list_element {
    struct _list_element *Next;
    char Data[MINIMUM_ARRAY_INDEX];     // See below
} list_element;

The list header uses this structure to store the elements62. As you can see, there is no space wasted in a pointer to the element stored. The element stored is placed just behind the Next pointer. The downside of this decision is that we can't recycle this object to store other different objects of different size.

3.3  Source files

Figure
The figure shows the files associated with the two implementations of the list container. The generic pointers implementation is list.c and the templated implementation is in listgen.c. The files depending on listgen.c are parameter files for listgen.c.

3.3.1  Alignment

Some machines require that data be stored at particular addresses, always a multiple of two. For instance SPARC machines require that doubles be aligned at a multiple of 8. The structure for our list element above would provoke a crash when used to store doubles 63.
In those machines the list element structure is defined as follows:
typedef struct _ListElement {
    struct _ListElement *Next;
#ifdef SPARC32
    void *alignment;
#endif
    char Data[MINIMUM_ARRAY_INDEX];
} ListElement;

This assumes that sizeof(void *) is 4.
In machines that handle unaligned data gracefully without crashing alignment requirements aren't useless, since in most cases they provoke a performance loss.
struct _List {
    ListInterface *VTable;  
    size_t count;           
    unsigned Flags;	
    unsigned timestamp;   
    size_t ElementSize;   
    list_element *Last;   
    list_element *First;  
    CompareFunction Compare; 
    ErrorFunction RaiseError; 
    ContainerHeap *Heap;
    ContainerAllocator *Allocator;
};

In the public containers.h header file we refer always to an abstract structure _List. We define it here. This schema allows other implementation to use the same header with maybe radically different implementations of their data structure.
  1. Vtable, count, Flags, ElementSize. This fields were described in the generic container section.
  2. timestamp. This field is incremented at each modification of the list, and allows the iterators to detect if the container changes during an iteration: they store the value of this field at the start of the iteration, and before each iteration they compare it with its current value. If there are any changes, they return  NULL .
  3. Last. Stores a pointer to the last element of the list. This allows the addition of an element at the end of the list to be fast, avoiding a complete rescan of the list. This field is an optimization, all algorithms of a single linked list would work without this field.
  4. First. The start of the linked list.
  5. Compare. A comparison function for the type of elements stored in the list.
  6. RaiseError. A function that will be called when an error occurs. This field is necessary only if you want to keep the flexibility of having a different error function for each list that the client software builds. An alternative implementation would store a pointer to an error function in the interface.
  7. Allocator. A set of functions that allocates memory for this list. In an implementation that needs less flexibility and is more interested in saving space it could be replaced by the default allocator.
The sample implementation has certainly a quite voluminous header because of a design decision to keep things very flexible. Other implementations could trim most of the fields, and an absolute minimal implementation would trim Last, Compare, RaiseError, Heap, and Allocator. If the implementation assumes that only one iterator per container is allowed, the timestamp field could be replace by a single bit ('changed') in the Flags field.64

3.4  Double linked lists

This container has a very similar structure to the single linked ones
typedef struct _dlist_element {
    struct _dlist_element *Next;
	struct _dlist_element *Previous;
    char Data[MINIMUM_ARRAY_INDEX];   /* (1) */
} dlist_element;

(1): In the templated versions of the container this field is defined as: TYPE Data; where TYPE is a type definition passed to the file as a parameter.
We have now two pointers followed by the stored data. All other fields are exactly identical to the ones in the single linked list. The single difference is the existence of a free list. This could have been done in the single linked list implementation too.
struct Dlist {
    DlistInterface *VTable;
    size_t count;        
    unsigned Flags;
    unsigned timestamp;
    size_t ElementSize;
    dlist_element *Last; 
    dlist_element *First;
    dlist_element *FreeList;
    CompareFunction Compare;
    ErrorFunction RaiseError; 
    ContainerHeap *Heap;
    ContainerAllocator *Allocator;
};

3.5  Vector

Arrays are the containers that use the smallest overhead per element: zero. The only overhead is the header structure, whose cost is amortized since it is fixed for all elements that the array can hold.
This is a 'flexible' array however, what means that there is some spare space allocated for allowing further growth, and that different allocation strategies can be followed when allocating a new chunk of array space when the existing array is full.
struct _Vector {
    VectorInterface *VTable;
    size_t count;          
    unsigned int Flags;    
    size_t ElementSize;	
    void *contents;        
    size_t capacity;       
    unsigned timestamp;
    CompareFunction CompareFn;
    ErrorFunction RaiseError;
    ContainerAllocator *Allocator;
} ;

  1. Vtable, count, Flags, ElementSize. This fields were described in the generic container section.
  2. CompareFn, RaiseError, timestamp and Allocator were described in the List container.
  3. capacity. Stores the number of elements (of ElementSize bytes each) this container can hold without resizing.
  4. contents. Points to an array of capacity elements, each of size ElementSize.

3.6  Dictionary

This container consists of an array of single linked lists. It could have been done with an Vector of List containers but a dedicated implementation is justified because of a greater efficiency. The advantages of the Vector container (secured access, flexible expansion) are not needed since the array has a fixed length that never changes.
struct _Dictionary {
    DictionaryInterface *VTable;
    size_t count;
    unsigned Flags;
    size_t size;
    ErrorFunction RaiseError;
    unsigned timestamp;
    size_t ElementSize;
    ContainerAllocator *Allocator;
    unsigned (*hash)(const char *Key);
    struct DataList {
        struct DataList *Next;
        char *Key;     /* (1) */
        char *Value;
    } **buckets;
};

(1): In the wide character version of this structure, this field will be defined as: wchar_t *Key;
  1. Vtable, count, Flags, ElementSize. This fields were described in the generic container section.
  2. RaiseError, timestamp and Allocator were described in the List container.
  3. size. The number of different lists that the hash table can contain. This is normally a prime number.
  4. hash. A hash function for character strings.
  5. buckets. A table of pointers to lists of DataList structures.

3.7  String collection

String collections are just flexible arrays of pointers to C character strings. They share all the fields of the Vector container, the only specific field is a context that is passed to the string comparison function. This context can contain flags or other information to use with special text encodings (wide characters for instance) or other data like regular expressions, etc.
struct strCollection {
    strCollectionInterface *VTable;
    size_t count; 
    unsigned int Flags;
    char **contents;      /* (1) */
    size_t capacity;
    size_t timestamp;
    ErrorFunction RaiseError;
    StringCompareFn strcompare;
    CompareInfo *StringCompareContext;
    ContainerAllocator *Allocator;
};

(1): In the wide character version of this structure this field is defined as:
    wchar_t ** contents;

3.8  Masks

There were two alternatives for implementing masks:
struct _Mask {
    size_t length;
    const ContainerAllocator *Allocator;
    char data[];
};

3.9  Bit strings

Bit strings do not need the ElementSize field obviously. The BIT_TYPE macro is defined as unsigned char. In general it should be an unsigned integer type that could be different from char, maybe a 32 bit type or even larger.
struct _BitString {
    BitStringInterface *VTable; /* The table of functions */
    size_t count;         /* number of bits in the array */
    BIT_TYPE *contents;   /* The contents of the collection */
    size_t capacity;      /* allocated space in the contents vector */
    unsigned timestamp;   /* Modifications counter */
    unsigned int Flags;   /* Read-only or other flags */
    const ContainerAllocator *Allocator;
} ;

3.10  The iterator implementation

This data structure has two main parts:

3.11  The timestamp field

This field will be incremented by each modification of the list. When an iterator is created it will copy the current value of the time stamp, and for each subsequent operation it will compare the value of the container with its saved copy. They should be always equal, otherwise the iterator will return always  NULL . The only exception to this rule is the iterator Replace function that will modify the container without invalidating the iterator that calls it. Other iterators to the same container will be invalidated.
The timestamp field should be incremented in all operations that somehow modify the container, not only in the operations that modify the number of elements.
Notes:
Implementation issues

4  The code

Only one container will be shown here in full: the List container. For the others, only some functions will be explained to save space. You are invited to read the distributed code of course that is part of this work.

4.1  List


Add
              
static int Add_nd(List *l,void *elem)
{
    list_element *newl;

    newl = new_link(l,elem,"iList.Add");
    if (newl == 0)
        return CONTAINER_ERROR_NOMEMORY;
    if (l->count ==  0) {
        l->First = newl;
    }
    else {
        l->Last->Next = newl;
    }
    l->Last = newl;
    l->timestamp++;
    ++l->count;
    return 1;
}

static int Add(List *l,void *elem)
{
    int r;
    if (l == NULL || elem == NULL) return NullPtrError("Add");
    if (l->Flags &CONTAINER_READONLY) return ErrorReadOnly(l,"Add");
    r = Add_nd(l,elem);
    if (r && (l->Flags & CONTAINER_HAS_OBSERVER))
        iObserver.Notify(l,CCL_ADD,elem,NULL);
    return r;
}

This function adds one element at the end. The Add entry point performs the error checking and calls Add_nd an internal function that does the actual work. This is needed because other functions call internally Add after they have already performed the error checking.
The Add_nd function requests a new list element (5). If that suceeds the new element must be inserted in the list. If the list is empty it just establishes the start of the list (9), if not, it adds it after the last element (12). The new list element is the last one (14). Errors leave the list unchanged. Exclusive access to the list is needed between the line 8 and the line 16 in the code. This operation is a modification of the list, and it needs to update the timestamp value to notify possible iterators that they are invalid.
If the Add_nd function was successfull and this container has a registered observer we notify the observer of this event.
AddRange
              
static int AddRange(List * AL,size_t n, void *data)
{
    unsigned char *p; 
    list_element *oldLast;
    
    if (AL == NULL) return NullPtrError("AddRange");
    if (AL->Flags & CONTAINER_READONLY) {
        AL->RaiseError("iList.AddRange",CONTAINER_ERROR_READONLY);
        return CONTAINER_ERROR_READONLY;
    }   
    if (n == 0) return 0;
    if (data == NULL) {
        AL->RaiseError("iList.AddRange",CONTAINER_ERROR_BADARG);
        return CONTAINER_ERROR_BADARG;
    }   
    p = data;
    oldLast = AL->Last;
    while (n > 0) {
        int r = Add_nd(AL,p);
        if (r < 0) {
            AL->Last = oldLast;
            if (AL->Last) {
                list_element *removed = oldLast->Next;
                while (removed) {
                    list_element *tmp = removed->Next;
                    if (AL->Heap) 
                        iHeap.FreeObject(AL->Heap,removed);
                    else    AL->Allocator->free(removed);
                    removed = tmp;
                }   
                AL->Last->Next = NULL;
            }   
            return r;
        }   
        p += AL->ElementSize; /* Point to the next element */
        n--; /* Count the items added so far */
    }           
    AL->timestamp++;
    if (AL->Flags & CONTAINER_HAS_OBSERVER)
        iObserver.Notify(AL,CCL_ADDRANGE,data,(void *)n);
    return 1;
}

This function calls repeatedly Add_nd for each element of the given array. Any error provokes an abort and the original list is left unchanged.
Error checking is done in lines 6 to 15, testing for  NULL for the list and the data. If the number of elements is zero the function does nothing and returns zero. The code accepts data as  NULL if the number of elements is zero. If n is zero this code still checks that the list is not  NULL , and that the list is not read only, considering both to be errors. Nothing is specified for those cases and you can't rely on this behavior for other implemetations.
Note that at compile time we do not know the size of each element and we can't index into this array. We just setup a generic pointer to the start of the data area (16), and increment it by the size of each element at each iteration (line 35). This implementation supposes that the size of the elements as assumed by the list is the same as the size of then element as assumed by the calling program.
If an error occurs when adding elements the new elements are discarded, the list is reset to its previous state and an error code is returned. (lines 20-33). The eventually added elements are discarded (lines 24-30).
Notes:
It would be far more efficient to test at the start of the loop if there is enough space for the n list elements than doing it within the loop. That would eliminate the code for reclaiming the already allocated items. This isn't done because the list allocator could be the default malloc function that doesn't allow queries of this type.

Append
              
static int Append(List *l1,List *l2)
{

    if (l1 == NULL || l2 == NULL) {
        if (l1)
         l1->RaiseError("iList.Append",CONTAINER_ERROR_BADARG);
        else
         iError.RaiseError("iList.Append",CONTAINER_ERROR_BADARG);
        return CONTAINER_ERROR_BADARG;
    }
    if ((l1->Flags & CONTAINER_READONLY) 
      || (l2->Flags & CONTAINER_READONLY)) {
        l1->RaiseError("iList.Append",CONTAINER_ERROR_READONLY);
        return CONTAINER_ERROR_READONLY;
    }   
    if (l2->ElementSize != l1->ElementSize) {
        l1->RaiseError("iList.Append",CONTAINER_ERROR_INCOMPATIBLE);
        return CONTAINER_ERROR_INCOMPATIBLE;
    }           
    if (l1->Flags & CONTAINER_HAS_OBSERVER)
        iObserver.Notify(l1,CCL_APPEND,l2,NULL);
 
    if (l2->Flags & CONTAINER_HAS_OBSERVER)
        iObserver.Notify(l2,CCL_FINALIZE,NULL,NULL);
 
    if (l1->count == 0) {
        l1->First = l2->First; 
        l1->Last = l2->Last;
    }       
    else if (l2->count > 0) {
        if (l2->First)
            l1->Last->Next = l2->First;
        if (l2->Last)
            l1->Last = l2->Last;
    }       
    l1->count += l2->count;
    l1->timestamp++;
    l2->Allocator->free(l2);
    return 1;
}

This function adds the second argument list to the first one. The second list is destroyed because all its elements are inserted into the first one. The result is obtained by pointer manipulation: no data is moved at all, and any pointers to the objects in the second list remain valid.
Error checking is done in lines 4 to 19. Then, the observer interface is considered. Since the second list will be destroyed a notification is sent to any observers that listen to events in that list. A notification is sent to the first list also, informing the observers of this event.
The actual work can then begin (lines 26 to 36): l2 is appended to l1 and the list header of l2 is freed.
Notes:
The test for compatibility between both lists is done with the size of an element, assuming elements of the same size are of the same type. This could very well be false but there is no portable way of test this at run time. Anyway, since a container doesn't care what is inside the objects it manages you can store elements of different types but the same size in a single container.

Apply
              
static int Apply(List *L,int (Applyfn)(void *,void *),void *arg)
{
    list_element *le;
    void *pElem=NULL;

    if (L == NULL || Applyfn == NULL) {
        if (L)
            L->RaiseError("iList.Apply",CONTAINER_ERROR_BADARG);
        else
            iError.RaiseError("iList.Apply",CONTAINER_ERROR_BADARG);
        return CONTAINER_ERROR_BADARG;
    }
    le = L->First;
    if (L->Flags&CONTAINER_READONLY) {
        pElem = L->Allocator->malloc(L->ElementSize);
        if (pElem == NULL) {
            L->RaiseError("iList.Apply",CONTAINER_ERROR_NOMEMORY);
            return CONTAINER_ERROR_NOMEMORY;
        }
    }
    while (le) {
        if (pElem) {
            memcpy(pElem,le->Data,L->ElementSize);
            Applyfn(pElem,arg);
        }
        else Applyfn(le->Data,arg);
        le = le->Next;
   }
    if (pElem)
        L->Allocator->free(pElem);
    return 1;
}

This function calls the given function for each element. If the container is read only, a copy of each element is passed to the called function. This copy is allocated with "malloc" because it is used for internal purposes, and the standard allocator for the list could be a heap based, i.e. one that doesn't really free any memory. That could be a problem if repeated calls to Apply are done.
This function does not pass any pointer to the called function to mark the list as changed if the data passed to it is rewritten. This means that there is no way to let the called function inform the rest of the software of any modifications. This can be justified by the fact that only the data, not the container itself can be modified, but this can be tricky in multi-threaded environments. Other implementations could pass some pointer or away to inform the rest of the software that a modification has been done.
Clear
              
static int Clear_nd(List *l)
{
    if (l->Flags & CONTAINER_HAS_OBSERVER)
        iObserver.Notify(l,CCL_CLEAR,NULL,NULL);
#ifdef NO_GC
    if (l->Heap)
        iHeap.Finalize(l->Heap);
    else {
        list_element *rvp = l->First,*tmp;

        while (rvp) {
            tmp = rvp;
            rvp = rvp->Next;
                        if (l->DestructorFn)
                                l->DestructorFn(tmp);
            l->Allocator->free(tmp);
        }
    }
#endif
    l->count = 0;
    l->Heap = NULL;
    l->First = l->Last = NULL;
    l->Flags = 0;
    l->timestamp = 0;
    return 1;
}   

static int Clear(List *l)
{
    if (l == NULL) {
        return NullPtrError("Clear");
    }   
    if (l->Flags & CONTAINER_READONLY) {
        l->RaiseError("iList.Clear",CONTAINER_ERROR_READONLY);
        return CONTAINER_ERROR_READONLY;
    }   
    return Clear_nd(l);
} 

This function should clear all stored elements and reset some fields of the header structure so that the resulting list header is almost the same as when it was created. The only difference is that any functions like the comparison function or the error function are not cleared. If they were changed by the user they still remain changed.
Like in other functions we have a no-debug function (named Clear_nd) that assumes all its parameters are correct, and the official entry point that checks its arguments. If we are compiling with a garbage collector in mind we can save us all the work of releasing each element since the collector will do that automatically.
Copy
              
static List *Copy(List *l)
{
    List *result;
    list_element *elem,*newElem;

    if (l == NULL) {
        NullPtrError("Copy");
        return NULL;
    }
    result = iList.CreateWithAllocator(l->ElementSize,l->Allocator);
    if (result == NULL) {
        l->RaiseError("iList.Copy",CONTAINER_ERROR_NOMEMORY);
        return NULL;
    }
    result->Flags = l->Flags; 
    result->VTable = l->VTable; 
    result->Compare = l->Compare;
    result->RaiseError = l->RaiseError;
    elem = l->First;
    while (elem) {
        newElem = new_link(result,elem->Data,"iList.Copy");
        if (newElem == NULL) {
            l->RaiseError("iList.Copy",CONTAINER_ERROR_NOMEMORY);
            result->VTable->Finalize(result);
            return NULL;
        }
        if (elem == l->First) {
            result->First = newElem;
            result->count++;
        }
        else {
            result->Last->Next = newElem;
            result->count++;
        }
        result->Last = newElem;
        elem = elem->Next;
    }
    if (l->Flags & CONTAINER_HAS_OBSERVER)
        iObserver.Notify(l,CCL_COPY,result,NULL);
    return result;
}


This function requires a non null list pointer. It creates a header structure, and fills some of it fields with the corresponding fields of the source list:
  1. The allocator
  2. The flags.
  3. The table of functions. This is necessary in case some of those functions have been sub-classed.
  4. The comparison function
  5. The error function
Note that the timestamp is not copied, and starts in the copy with zero.
If an error occurs during the copy, probably because of lack of memory, the new list is destroyed and the result is  NULL . Otherwise elements are added at the growing end of the list.
Contains
              
static int Contains(List *l,void *data)
{
    size_t idx;
    return (IndexOf(l,data,NULL,&idx) < 0) ? 0 : 1;
}

The Contains function is just a cover function for IndexOf.
CopyElement
              
static int CopyElement(List *l,size_t position,void *outBuffer)
{
    list_element *rvp;

    /* Error checking ellided */
    rvp = l->First;
    while (position) {
        rvp = rvp->Next;
        position--;
    }
    memcpy(outBuffer,rvp->Data,l->ElementSize);
    return 1;
}

After the error checking, this function positions at the given element and copies its contents into the given buffer. Other designs are obviously possible.
Create
              
static List *Create(size_t elementsize)
{
    return CreateWithAllocator(elementsize,CurrentAllocator);
}

This function just calls CreateWithAllocator using the current memory manager.
CreateWithAllocator
              
static List *CreateWithAllocator(size_t elementsize,
                       ContainerAllocator *allocator)
{
    List *result;

    if (elementsize == 0) {
        iError.RaiseError("iList.Create",CONTAINER_ERROR_BADARG);
        return NULL;
    }
    result = allocator->malloc(sizeof(List));
    if (result == NULL) {
        iError.RaiseError("iList.Create",CONTAINER_ERROR_NOMEMORY);
        return NULL;
    }
    memset(result,0,sizeof(List));
    result->ElementSize = elementsize;
    result->VTable = &iList;
    result->Compare = DefaultListCompareFunction;
    result->RaiseError = iError.RaiseError;
    result->Allocator = allocator;
    return result;
}

After doing some error checking, the creation function allocates and initializes the new container with its default values.
A big question is the alignment problem for the given size. This can't be checked and could lead to problems if you pass to this function any argument that is not the product of a sizeof expression.
DefaultListCompareFunction
              
static int DefaultListCompareFunction(const void *left,
                                      const void *right,
                                      CompareInfo *ExtraArgs)
{
        size_t siz=((List *)ExtraArgs->Container)->ElementSize;
        return memcmp(left,right,siz);
}

The default element compare function is just a cover for memcmp. It is assumed that the user will replace it with a comparison function of its own if necessary.
DefaultListLoadFunction
              
static size_t DefaultLoadFunction(void *element,void *arg, FILE *Infile)
{
    size_t len = *(size_t *)arg;

    return fread(element,1,len,Infile);
}

This function just reads an element from the disk file. Returns the result value of fread, what is OK for our purposes.
DefaultSaveFunction
              
static size_t DefaultSaveFunction(const void *element,void *arg, 
                                  FILE *Outfile)
{
    const unsigned char *str = element;
    size_t len = *(size_t *)arg;

    return fwrite(str,1,len,Outfile);
}

This function just writes the given element to the disk. Together with the default load function they allow for a very effective serialization package for containers. Obviously here we have a shallow copy, and all this will never work for recursive saves, i.e. for elements that contain pointers.
deleteIterator
              
static int deleteIterator(Iterator *it)
{
    struct ListIterator *li;
    List *L;

    if (it == NULL) {
        iError.RaiseError("deleteIterator",CONTAINER_ERROR_BADARG);
        return CONTAINER_ERROR_BADARG;
    }
    li = (struct ListIterator *)it;
    L = li->L;
    L->Allocator->free(it);
    return 1;
}

This routine retrieves the list header object from the hidden part of the iterator and uses its allocator object to free the memory used by the iterator.
The functions NewIterator and deleteIterator should occur in pairs like many others in C: malloc and free, fopen and fclose, etc. It would be very easy to have in the header object a counter of iterators that should be zero when the list is destroyed or cleared.
Equal
              
static int Equal(List *l1,List *l2)
{
    list_element *link1,*link2;
    CompareFunction fn;
    CompareInfo ci;

    if (l1 == l2)
        return 1;
    if (l1 == NULL || l2 == NULL)
        return 0;
    if (l1->count != l2->count)
        return 0;
    if (l1->ElementSize != l2->ElementSize)
        return 0;
    if (l1->Compare != l2->Compare)
        return 0;
    if (l1->count == 0)
        return 1;
    fn = l1->Compare;
    link1 = l1->First;
    link2 = l2->First;
    ci.Container = l1;
    ci.ExtraArgs = NULL;
    while (link1 && link2) {
        if (fn(link1->Data,link2->Data,&ci))
            return 0;
        link1 = link1->Next;
        link2 = link2->Next;
    }
    if (link1 || link2)
        return 0;
    return 1;
}

If two null pointers are passed to the Equal function it returns true. This is a design decision: Equal doesn't have any error result. Either the two objects are equal or not.
A redundant test is done at the end of the function: if the lists have the same count and all elements are equal, link1 and link2 should be  NULL . If they aren't that means there is a memory overwrite problem somewhere...
Erase
              
static int Erase(List *l,void *elem)
{
    size_t idx;
    int i;

    if (l == NULL) {
        iError.RaiseError("iList.Erase",CONTAINER_ERROR_BADARG);
        return CONTAINER_ERROR_BADARG;
    }
    if (elem == NULL) {
        l->RaiseError("iList.Erase",CONTAINER_ERROR_BADARG);
        return CONTAINER_ERROR_BADARG;
    }
    if (l->count == 0) {
        return CONTAINER_ERROR_NOTFOUND;
    }
    i = IndexOf(l,elem,NULL,&idx);
    if (i < 0)
        return i;
    return RemoveAt(l,idx);
}

This is a very inefficient implementation. The list will be traversed twice, the first by IndexOf, and the second by RemoveAt. The obvious solution is to merge both into one function.
EraseRange
              
static int EraseRange(List *l,size_t start,size_t end)
{
    list_element *rvp,*start_pos,*tmp;
    size_t toremove;
    if (end > l->count)
        end = l->count;
    if (start >= l->count)
        return 0;
    if (start >= end)
        return 0;
    toremove = end - start+1;
    rvp = l->First;
    while (rvp && start > 1) {
        rvp = rvp->Next;
        start--;
    }
    start_pos = rvp;
    rvp = rvp->Next;
    while (toremove > 1) {
        tmp = rvp->Next;
        if (l->Heap)
            iHeap.FreeObject(l->Heap,rvp);
        else {
            l->Allocator->free(rvp);
        }
        rvp = tmp;
        toremove--;
        l->count--;
    }
    start_pos->Next = rvp;
    return 1;
}
This function positions the cursor 65 at the element before the one where the range starts, and then erases until it reaches the end of the range.
Finalize
              
static int Finalize(List *l)
{
    int t=0;

    t = Clear(l);
    if (t < 0)
        return t;
    l->Allocator->free(l);
    return 1;
}

This function should free the memory used by the header object. It is fundamental that this will never be done with an object not allocated with that iterator in the first place, i.e. when the user has called Init instead of Create. This can't be tested in a portable manner since there is no function to verify that a given memory space belongs or not to a given allocator.66
GetCurrent
              
static void *GetCurrent(Iterator *it)
{
    struct ListIterator *li = (struct ListIterator *)it;

    if (li->L->count == 0)
        return NULL;    
    if (li->index == (size_t)-1) {
        li->L->RaiseError("GetCurrent",CONTAINER_ERROR_BADARG);
        return NULL;
    }
    if (li->L->Flags & CONTAINER_LIST_READONLY) {
        return li->ElementBuffer;
    }
    return li->Current->Data;
}

Returns the current object pointed by the given iterator. This function should be called only after GetFirst is called. It verifies this by testing if a correct value is stored in the index field. This value is stored by the NewIterator function. This simple algorithm avoids the usage of an uninitialized iterator at the cost of one integer comparison per call.

GetFirst
              
static void *GetFirst(Iterator *it)
{
    struct ListIterator *li = (struct ListIterator *)it;
    List *L;


    L = li->L;
    if (L->count == 0)
        return NULL;
    if (li->timestamp != L->timestamp) {
        L->RaiseError("iList.GetFirst",CONTAINER_ERROR_OBJECT_CHANGED);
        return NULL;
    }
    li->index = 0;
    li->Current = L->First;
    if (L->Flags & CONTAINER_LIST_READONLY) {
        memcpy(li->ElementBuffer,L->First->Data,L->ElementSize);
        return li->ElementBuffer;
    }
    return L->First->Data;
}

This function should set the iteration at the first element of the container, ready to get the iteration started. After the error checking phase it returns a pointer to the data in the first element, or a pointer to a copy of that data if the container is read only.
GetFlags
              
static unsigned GetFlags(List *l)
{
    if (l == NULL) {
            iError.RaiseError("iList.GetFlags",CONTAINER_ERROR_BADARG);
            return (unsigned)CONTAINER_ERROR_BADARG;
    }
    return l->Flags;
}

Just returns the value of the flags.
GetNext
              
static void *GetNext(Iterator *it)
{
    struct ListIterator *li = (struct ListIterator *)it;
    List *L;
    void *result;


    if (li == NULL) {
        iError.RaiseError("iList.GetNext",CONTAINER_ERROR_BADARG);
        return NULL;
    }
    L = li->L;
    if (li->index >= (L->count-1) || li->Current == NULL)
        return NULL;
    if (li->L->count == 0)
        return NULL;
    if (li->timestamp != L->timestamp) {
        L->RaiseError("GetNext",CONTAINER_ERROR_OBJECT_CHANGED);
        return NULL;
    }
    li->Current = li->Current->Next;
    li->index++;
    if (L->Flags & CONTAINER_LIST_READONLY) {
        memcpy(li->ElementBuffer,li->Current->Data,L->ElementSize);
        return li->ElementBuffer;
    }
    result = li->Current->Data;
    return result;
}

Advances the cursor to the next element and returns either a pointer to it or a pointer to a copy if the list is read only. The test for the cursor being  NULL avoids using GetNext with an uninitialized iterator.
GetPrevious
              
static void *GetPrevious(Iterator *it)
{
    struct ListIterator *li = (struct ListIterator *)it;
    List *L;
    list_element *rvp;
    size_t i;

    L = li->L;
    if (li->index >= L->count || li->index == 0)
        return NULL;
    if (li->timestamp != L->timestamp) {
        L->RaiseError("GetPrevious",CONTAINER_ERROR_OBJECT_CHANGED);
        return NULL;
    }
    rvp = L->First;
    i=0;
    li->index--;
    if (li->index > 0) {
        while (rvp && i < li->index) {
            rvp = rvp->Next;
            i++;
        }
    }
    li->Current = rvp;
    return rvp->Data;
}

There were heated discussions about this function. In single linked lists it is necessary to go through the whole list at each call to this function. This is extremely inefficient and its usage should be avoided, it is much better to use double linked lists if you are interested in bi-directional cursor positioning. In the other hand this should be a required iterator feature, and rather than filling this function pointer with a function that just returns an error, the user is better served with a function that actually returns the previous item. Besides for short lists the performance lost is quite small, and would justify using lists with smaller overhead per item.67.
GetRange
              
static List *GetRange(List *l,size_t start,size_t end)
{
    size_t counter;
    List *result;
    list_element *rvp;;

    result = iList.Create(l->ElementSize);
    result->VTable = l->VTable;
    if (l->count == 0)
        return result;
    if (end >= l->count)
        end = l->count;
    if (start > end || start > l->count)
        return NULL;
    if (start == l->count-1)
        rvp = l->Last;
    else {
        rvp = l->First;
        counter = 0;
        while (counter < start) {
            rvp = rvp->Next;
            counter++;
        }
    }
    while (start < end && rvp != NULL) {
        int r = result->VTable->Add(result,&rvp->Data);
        if (r < 0) {
            Finalize(result);
            result = NULL;
            break;
        }
        rvp = rvp->Next;
        start++;
    }
    return result;
}

A new list is constructed from the given range of elements. The elements are copied. Any error during the construction of the new list provokes a  NULL result: the copied elements are destroyed. Only correctly constructed ranges are returned. A recurring problem arises because it is impossible to report any details about the error that stops the copy. The result is actually boolean, either everything worked and there is a non  NULL result, or something didn't.
An alternative design would have an integer return code, and a pointer to a result. This option was discarded because it is cumbersome and the most likely reason for Add to fail is lack of memory.
IndexOf
              
static int IndexOf(List *l,void *ElementToFind,
                   void *ExtraArgs,size_t *result)
{
    list_element *rvp;
    int r,i=0;
    CompareFunction fn;
    CompareInfo ci;

    if (l == NULL || ElementToFind == NULL) {
        if (l)
            l->RaiseError("iList.IndexOf",CONTAINER_ERROR_BADARG);
        else
            iError.RaiseError("iList.IndexOf",CONTAINER_ERROR_BADARG);
        return CONTAINER_ERROR_BADARG;
    }
    rvp = l->First;
    fn = l->Compare;
    ci.Container = l;
    ci.ExtraArgs = ExtraArgs;
    while (rvp) {
        r = fn(&rvp->Data,ElementToFind,&ci);
        if (r == 0) {
            *result = i;
            return 1;
        }
        rvp = rvp->Next;
        i++;
    }
    return CONTAINER_ERROR_NOTFOUND;
}

The design of this function went through several iterations. The big problem was the result type: a size_t, that in most cases is an unsigned quantity. A negative error result then was out of the question. But then, how would you indicate an error? 68
A first solution was to return a 1 based index and reserve zero for the 'not found' value. That could work, but was the source of many bugs in the rest of the software when the value was used without decrementing it first.
A second solution was to reserve a value within the size_t range to represent the 'not found' result. That works, and it is doable, but produced other, more subtle, problems in the rest of the sofwtare since in all checks of a size_t, it could be that this size_t has a value that is actually the sentinel value of IndexOf: the tests tended to multiply and the handling of those tests started to become a problem.
Here you see the third iteration: the function receives a pointer to a size_t that will be set if the function returns with a result greater than zero.
Another, completely different issue is the fact that in lists, this function is inefficient since it forces the function that uses the result to restart a list traversal to access the nth element. Much more efficient would be to do something immediately with the result, or to return a list element that allows the calling software to use it without going again through the list.
Problems with those solutions is that they are not portable, and that they would expose the inner workings of the list container to the users. The list_element structure is not even mentioned in the public containers.h.

InitWithAllocator
              
static List *InitWithAllocator(List *result,size_t elementsize,
	                       ContainerAllocator *allocator)
{
    if (elementsize == 0) {
        iError.RaiseError("iList.Init",CONTAINER_ERROR_BADARG);
        return NULL;
    }
    memset(result,0,sizeof(List));
    result->ElementSize = elementsize;
    result->VTable = &iList;
    result->Compare = DefaultListCompareFunction;
    result->RaiseError = iError.RaiseError;
    result->Allocator = allocator;
    return result;
}

This function initializes a piece of storage to a list container. This allows the user to use stack storage for the list container, saving an allocation from the heap, and the corresponding need to free that storage.
Init
              
static List *Init(List *result,size_t elementsize)
{
    return InitWithAllocator(result,elementsize,CurrentAllocator);
}

Uses the current memory manager to call InitWithAllocator.
InsertAt
              
static int InsertAt(List *l,size_t pos,void *pdata)
{
    list_element *elem;
    if (l == NULL || pdata == NULL) {
        if (l)
            l->RaiseError("iList.InsertAt",CONTAINER_ERROR_BADARG);
        else
            iError.RaiseError("iList.InsertAt",CONTAINER_ERROR_BADARG);
        return CONTAINER_ERROR_BADARG;
    }
    if (pos > l->count) {
        l->RaiseError("iList.InsertAt",CONTAINER_ERROR_INDEX);
        return CONTAINER_ERROR_INDEX;
    }
    if (l->Flags & CONTAINER_LIST_READONLY) {
        l->RaiseError("iList.InsertAt",CONTAINER_ERROR_READONLY);
        return CONTAINER_ERROR_READONLY;
    }
    if (pos == l->count) {
        return l->VTable->Add(l,pdata);
    }

    elem = new_link(l,pdata,"iList. InsertAt");
    if (elem == NULL) {
        l->RaiseError("iList.InsertAt",CONTAINER_ERROR_NOMEMORY);
        return CONTAINER_ERROR_NOMEMORY;
    }
    if (pos == 0) {
        elem->Next = l->First;
        l->First = elem;
    }
    else {
        list_element *rvp = l->First;
        while (--pos > 0) {
            rvp = rvp->Next;
        }
        elem->Next = rvp->Next;
        rvp->Next = elem;
    }
    l->count++;
    l->timestamp++;
    return 1;
}

This inserts before the given index. It would have been equally possible to insert after, that is a more or less random decision.
InsertIn
              
static int InsertIn(List *l, size_t idx,List *newData)
{
    size_t newCount;
    list_element *le,*nle;

    if (idx > l->count) {
        l->RaiseError("iList.InsertIn",CONTAINER_ERROR_INDEX);
        return CONTAINER_ERROR_INDEX;
    }
    if (l->ElementSize != newData->ElementSize) {
        l->RaiseError("iList.InsertIn",CONTAINER_ERROR_INCOMPATIBLE);
        return CONTAINER_ERROR_INCOMPATIBLE;
    }
    if (newData->count == 0)
        return 1;
    newData = Copy(newData);
    if (newData == NULL) {
        l->RaiseError("iList.InsertIn",CONTAINER_ERROR_NOMEMORY);
        return CONTAINER_ERROR_NOMEMORY;
    }
    newCount = l->count + newData->count;
    if (l->count == 0) {
        l->First = newData->First;
        l->Last = newData->Last;
    }
    else {
        le = l->First;
        while (le && idx > 1) {
            le = le->Next;
            idx--;
        }
        nle = le->Next;
        le->Next = newData->First;
        newData->Last->Next = nle;
    }
    newData->Allocator->free(newData);
    l->timestamp++;
    l->count = newCount;
    return 1;
}

Inserts the given list at the specified position.
  1. Error checking. First argument must be non  NULL and read/write. Second must be non  NULL .
  2. If the position given is exactly the same as the length of the receiving list, the second list is just appended to the first one.
  3. Otherwise search the position and insert a copy of the elements in the second list.

Load
              
static List *Load(FILE *stream, ReadFunction loadFn,void *arg)
{
    size_t i,elemSize;
    List *result,L;
    char *buf;
    int r;
    guid Guid;

    if (loadFn == NULL) {
        loadFn = DefaultLoadFunction;
        arg = &elemSize;
    }
    if (fread(&Guid,sizeof(guid),1,stream) <= 0) {
        iError.RaiseError("iList.Load",CONTAINER_ERROR_FILE_READ);
        return NULL;
    }
    if (memcmp(&Guid,&ListGuid,sizeof(guid))) {
        iError.RaiseError("iList.Load",CONTAINER_ERROR_WRONGFILE);
        return NULL;
    }
    if (fread(&L,1,sizeof(List),stream) <= 0) {
        iError.RaiseError("iList.Load",CONTAINER_ERROR_FILE_READ);
        return NULL;
    }
    elemSize = L.ElementSize;
    buf = malloc(L.ElementSize);
    if (buf == NULL) {
        iError.RaiseError("iList.Load",CONTAINER_ERROR_NOMEMORY);
        return NULL;
    }
    result = iList.Create(L.ElementSize);
    if (result == NULL) {
        iError.RaiseError("iList.Load",CONTAINER_ERROR_NOMEMORY);
        return NULL;
    }
    result->Flags = L.Flags;
    r = 1;
    for (i=0; i < L.count; i++) {
        if (loadFn(buf,arg,stream) <= 0) {
            r = CONTAINER_ERROR_FILE_READ;
            break;
        }
        if ((r=Add(result,buf)) < 0) {
            break;
        }
    }
    free(buf);
    if (r < 0) {
        iError.RaiseError("iList.Load",r);
        iList.Finalize(result);
        result = NULL;
    }
    return result;
}

The load function is long and complex. As always, the process starts with error checking. All streams written to by its counterpart Save are marked with a container specific globally unique identifier (GUID). This ensures that a load function from the list container will not crash if passed a file that belongs to an array or a dictionary, or a totally unrelated file. The guids can be changed to mark the versions of the software and allow more advanced versions to read older versions.
Then, the header object is read, what gives the data to continue the process, since we now know the number of elements and the size of each element.
A new list is created with the given element size, and we start reading count elements from the stream. Any error provokes the destruction of the elements read so far and a result of NULL.
NewIterator
              
static Iterator *NewIterator(List *L)
{
    struct ListIterator *result;
    
    if (L == NULL) {
        iError.RaiseError("iList.NewIterator",CONTAINER_ERROR_BADARG);
        return NULL;
    }
    result = L->Allocator->malloc(sizeof(struct ListIterator));
    if (result == NULL) {
        L->RaiseError("iList.NewIterator",CONTAINER_ERROR_NOMEMORY);
        return NULL;
    }
    result->it.GetNext = GetNext;
    result->it.GetPrevious = GetPrevious;
    result->it.GetFirst = GetFirst;
    result->it.GetCurrent = GetCurrent;
    result->L = L;
    result->timestamp = L->timestamp;
    result->index = (size_t)-1;
    result->Current = NULL;
    return &result->it;
}

The creation of a new iterator involves just allocating and initializing values to their defaults.
PopFront
              
static int PopFront(List *l,void *result)
{
    list_element *le;

    if (l->count == 0)
        return 0;
    le = l->First;
    if (l->count == 1) {
        l->First = l->Last = NULL;
    }
    else l->First = l->First->Next;
    l->count--;
    if (result)
        memcpy(result,&le->Data,l->ElementSize);
    if (l->Heap) {
        iHeap.FreeObject(l->Heap,le);
    }
    else l->Allocator->free(le);
    l->timestamp++;
    return 1;
}

Contrary to most versions of this function, PopFront does not return the data of the element but stores it in a pointer that it receives. If the pointer is  NULL , the data is just discarded.
The problem with returning a pointer to the first element, is that the user code should remember to discard it when no longer needed, and it should discard it using the same allocator that the list used to allocate it. That would be a very error prone interface.
PushFront
              
static int PushFront(List *l,void *pdata)
{
    list_element *rvp;

    rvp = new_link(l,pdata,"Insert");
    if (rvp == NULL)
        return CONTAINER_ERROR_NOMEMORY;
    rvp->Next = l->First;
    l->First = rvp;
    if (l->Last == NULL)
        l->Last = rvp;
    l->count++;
    l->timestamp++;
    return 1;
}

Lists are a good base to implement a stack. PushFront and PopFront take a constant and small time to complete and they would be much smaller if we would eliminate the error checking.
RemoveAt
              
static int RemoveAt(List *l,size_t position)
{
    list_element *rvp,*last,*removed;


    rvp = l->First;
    if (position == 0) {
        removed = l->First;
        if (l->count == 1) {
            l->First = l->Last = NULL;
        }
        else {
            l->First = l->First->Next;
        }
    }
    else if (position == l->count - 1) {
        while (rvp->Next != l->Last)
            rvp = rvp->Next;
        removed = rvp->Next;
        rvp->Next = NULL;
        l->Last = rvp;
    }
    else {
        last = rvp;
        while (position > 0) {
            last = rvp;
            rvp = rvp->Next;
            position --;
        }
        removed = rvp;
        last->Next = rvp->Next;
    }
    if (l->Heap) {
        iHeap.FreeObject(l->Heap,removed);
    }
    else l->Allocator->free(removed);
    l->timestamp++;
    --l->count;
    return 1;
}

The operation when RemoveAt is called with the index of the last element is equivalent to the PopBack function, that is absent in the single linked list interface. After much discussions, we decided that the generic interface would have only Push and Pop, and that each container would fill those functions with the most efficient implementation available for it. For lists, the most efficient implementation is PopFront and PushFront. For arrays, the most efficient is PushBack and PopBack. For double linked lists is either.
ReplaceAt
              
static int ReplaceAt(List *l,size_t position,void *data)
{
    list_element *rvp;

    if (position == l->count-1)
        rvp = l->Last;
    else  {
        rvp = l->First;
        while (position) {
            rvp = rvp->Next;
            position--;
        }
    }
    memcpy(&rvp->Data , data,l->ElementSize);
    l->timestamp++;
    return 1;
}

After error checking (not shown), position the cursor at the right item, then copy from the given data pointer the element size bytes needed.
An open issue is whether the "timestamp" field should be changed. Nothing in the list structure has been changed, only the data stored in the container. Any iterators will go on working as advertised even if this function is called to replace many items in the list. In the other hand, if user programs were making assumptions about the data (for instance a search function doesn't always look again at past items to see if they have been changed) this could bad consequences. As a rule, any change will provoke the incrementing of the "timestamp" counter.
Reverse
              
static int Reverse(List *l)
{
    list_element *New,*current,*old;

    if (l->count < 2)
        return 1;
    old = l->First;
    l->Last = l->First;
    New = NULL;
    while (old) {
        current = old;
        old = old->Next;
        current->Next = New;
        New = current;
    }
    l->First = New;
    l->Last->Next = NULL;
    l->timestamp++;
    return 1;
}

After the error checking, the list is reversed in place if the count of its element is bigger than 1.69
RotateLeft
              
static int RotateLeft(List *l, size_t n)
{
    ListElement *rvp,*oldStart,*last=NULL;
    if (l == NULL) return NullPtrError("RotateLeft");
    if (l->Flags & CONTAINER_READONLY)
        return ErrorReadOnly(l,"RotateLeft");
    if (l->count < 2 || n == 0)
        return 0;
    n %= l->count;
    if (n == 0) return 0;
    rvp = l->First;
    oldStart = rvp;
    while (n > 0) {
        last = rvp;
        rvp = rvp->Next;
        n--;
    }
    l->First = rvp;
    last->Next = NULL;
    l->Last->Next = oldStart;
    l->Last = last;
    return 1;
}

The RotateLeft and the RotateRightfunctions can be implemented without any movement of the stored objects themselves. It suffices to make the list start at another place: n places after the start for left rotates, or n places before the end for right rotates.
RotateRight
              
static int RotateRight(List *l, size_t n)
{
    ListElement *rvp,*oldStart,*last=NULL;
    if (l == NULL) return NullPtrError("RotateRight");
    if (l->Flags & CONTAINER_READONLY)
        return ErrorReadOnly(l,"RotateRight");
    if (l->count < 2 || n == 0)
        return 0;
    n %= l->count;
    if (n == 0) return 0;
    rvp = l->First;
    oldStart = rvp;
    n = l->count - n;
    while (n > 0) {
        last = rvp;
        rvp = rvp->Next;
        n--;
    }
    l->First = rvp;
    if (last == NULL) {
        iError.RaiseError("RotateRight",CONTAINER_INTERNAL_ERROR);
        return CONTAINER_INTERNAL_ERROR;
    }
    last->Next = NULL;
    l->Last->Next = oldStart;
    l->Last = last;
    return 1;
}

The RotateRight/RotateLeft functions check their arguments to the contrary of their ValArray counterparts that do not. This implementation shows also a checking of values that should be non-null but could be  NULL if there is a memory overwrite or another similar problem.
Save
              
static int Save(List *L,FILE *stream, SaveFunction saveFn,void *arg)
{
    size_t i;
    list_element *rvp;

    if (saveFn == NULL) {
        saveFn = DefaultSaveFunction;
        arg = &L->ElementSize;
    }

    if (fwrite(&ListGuid,sizeof(guid),1,stream) <= 0)
        return EOF;

    if (fwrite(L,1,sizeof(List),stream) <= 0)
        return EOF;
    rvp = L->First;
    for (i=0; i< L->count; i++) {
        char *p = rvp->Data;

        if (saveFn(p,arg,stream) <= 0)
            return EOF;
        rvp = rvp->Next;
    }
    return 1;
}

The format of the saved list container is:
  1. The GUID of the list container: 128 bytes
  2. The Header object
  3. The data for all the elements of the list. This is the siz of the container times the element size.

Seek
              
static void *Seek(Iterator *it,size_t idx)
{
    struct ListIterator *li = (struct ListIterator *)it;
    list_element *rvp;


    if (li->L->count == 0)
        return NULL;

    rvp = li->L->First;
    if (idx >= li->L->count-1) {
        li->index = li->L->count-1;
        li->Current = li->L->Last;
    }
    else if (idx == 0) {
        li->index = 0;
        li->Current = li->L->First;
    }
    else {
        li->index = idx;
        while (idx > 0) {
            rvp = rvp->Next;
            idx--;
        }
        li->Current = rvp;
    }
    return li->Current;
}

This function positions the given iterator at the desired position. Several alternatives are possible, for instance position the iterator at a given item. This can be obtained now only by calling first IndexOf, then Seek, what forces to go through the list twice.
SetCompareFunction
              
static CompareFunction SetCompareFunction(List *l,CompareFunction fn)
{
   CompareFunction oldfn = l->Compare;

   if (l == NULL) {
      iError.RaiseError("iList.SetCompareFunction",
                        CONTAINER_ERROR_BADARG);
      return NULL;
   }
   if (fn != NULL) {
      if (l->Flags&CONTAINER_LIST_READONLY) {
          l->RaiseError("iList.SetCompareFunction",
                        CONTAINER_LIST_READONLY);
      }
      else l->Compare = fn;
   }
   return oldfn;
}

This function returns the old value of the comparison function and sets it to the new one, if the new one is not  NULL . This allows to query the comparison function without changing it, avoiding yet another trivial function like GetComparisonFunction. This is just what in other languages like Objective C or others is called a property of the iList object. Objective C makes all this automatic with its synthetise directive.
In C there isn't any such hand holding and you have to write that code yourself. There are several other functions in the same style like SetErrorFunction, Size (that returns the count field) and SetFlags. They aren't listed here but you can look at the code by browsing through the list.c file distributed with this software.
Sizeof
              
static size_t Sizeof(List *l)
{
    if (l == NULL) {
        return sizeof(List);
    }

    return sizeof(List) + 
           l->ElementSize * l->count + 
           l->count *sizeof(list_element);
}

Returns the number of bytes used by the given list, including the data, and all overhead. For lists, tghis is the size of the header object, and for each element the overhead of a pointer to the next element and the size of each stored object. With a  NULL list pointer returns the size of the list header object, what allows you to allocate buffers containing a header object and use the Init function.
Sort
              
static int Sort(List *l)
{
    list_element **tab;
    size_t i;
    list_element *rvp;
    CompareInfo ci;

    if (l == NULL) {
        iError.RaiseError("iList.Sort",CONTAINER_ERROR_BADARG);
        return CONTAINER_ERROR_BADARG;
    }
    if (l->count < 2)
        return 1;
    if (l->Flags&CONTAINER_LIST_READONLY) {
        l->RaiseError("iList.Sort",CONTAINER_ERROR_READONLY);
        return CONTAINER_ERROR_READONLY;
    }
    tab = l->Allocator->malloc(l->count * sizeof(list_element *));
    if (tab == NULL) {
        l->RaiseError("iList.Sort",CONTAINER_ERROR_NOMEMORY);
        return CONTAINER_ERROR_NOMEMORY;
    }
    rvp = l->First;
    for (i=0; i<l->count;i++) {
        tab[i] = rvp;
        rvp = rvp->Next;
    }
    ci.Container = l;
    ci.ExtraArgs = NULL;
    qsortEx(tab,l->count,sizeof(list_element *),lcompar,&ci);
    for (i=0; i<l->count-1;i++) {
        tab[i]->Next = tab[i+1];
    }
    tab[l->count-1]->Next = NULL;
    l->Last = tab[l->count-1];
    l->First = tab[0];
    l->Allocator->free(tab);
    return 1;

}

This function basically builds an array and calls quicksort, nothing really fancy. Note that it calls a modified version of the library function quicksort, since it needs to pass a context to it for the comparison function. The default comparison function is listed below:
              
static bool lcompar (const void *elem1, const void *elem2,
                                       CompareInfo *ExtraArgs)
{
    list_element *Elem1 = *(list_element **)elem1;
    list_element *Elem2 = *(list_element **)elem2;
    List *l = (List *)ExtraArgs->Container;
    CompareFunction fn = l->Compare;
    return fn(Elem1->Data,Elem2->Data,ExtraArgs);
}

The default comparison function pulls the list compare function and calls it with the extra arguments needed to pass a context to it.
SplitAfter
              
 1 static List *SplitAfter(List *l, ListElement *pt)
 2 {
 3     ListElement *pNext;
 4     List *result;
 5     size_t count=0;
 6 
 7     if (pt == NULL || l == NULL) {
 8         iError.NullPtrError("iList.SplitAfter");
 9         return NULL;
10     }
11     if (l->Flags&CONTAINER_READONLY) {
12         ErrorReadOnly(l,"SplitAfter");
13         return NULL;
14     }
15     pNext = pt->Next;
16     if (pNext == NULL) return NULL;
17     result = CreateWithAllocator(l->ElementSize, l->Allocator);
18     if (result) {
19         result->First = pNext;
20         while (pNext) {
21             count++;
22             if (pNext->Next == NULL) result->Last = pNext;
23             pNext = pNext->Next;
24         }
25         result->count = count;
26     }
27     else return NULL;
28     pt->Next = NULL;
29     l->Last = pt;
30     l->count -= count;
31     l->timestamp++;
32     return result;
33 }

After the normal error checking of arguments, this function gets the next element after the given one. If there is none, it is impossible to split the list after after the given element since it is the last. We return  NULL (lines 15-16).
If there is an element, it will be the head of the new list. We create a new list (line 17) using the source list allocator and we set the given element (argument pt) as the first one of the new list. We then count the elements (lines 20 to 24) in the new list since we need to fill the "count" field in the new list. This makes this operation much more expensive than it would be if we didn't maintain a "count" field70. In lines 27-31 we set the correct fields in the new list, decrease the "count" field in the source list by the number of elements in the new list, and we note the fact that the input list has been modified in line 31.
RotateLeft
              
  1 static int RotateLeft(List * l, size_t n)
  2 {
  3     ListElement    *rvp, *oldStart, *last = NULL;
  4     if (l == NULL)
  5         return NullPtrError("RotateLeft");
  6     if (l->Flags & CONTAINER_READONLY)
  7         return ErrorReadOnly(l, "RotateLeft");
  8     if (l->count < 2 || n == 0)
  9         return 0;
 10     n %= l->count;
 11     if (n == 0)
 12         return 0;
 13     rvp = l->First;
 14     oldStart = rvp;
 15     while (n > 0) {
 16         last = rvp;
 17         rvp = rvp->Next;
 18         n--;
 19     }
 20     l->First = rvp;
 21     last->Next = NULL;
 22     l->Last->Next = oldStart;
 23     l->Last = last;
 24     return 1;
 25 }

Rotating a list is very simple: Cut the list at the desired place, and append the list elements from the start up to the cut point at the end of the list.
After the error checking is done (lines 3-12) we start a first loop where we find the place to cut: lines 15-19. Then, we cut (set the previous element's Next pointer to NULL in line 21) and append the elements we cutted to the end of the list, updating the list's header.

UseHeap
              
static int UseHeap(List *L, ContainerAllocator *m)
{
    if (L == NULL) {
        iError.RaiseError("iList.UseHeap",CONTAINER_ERROR_BADARG);
        return CONTAINER_ERROR_BADARG;
    }
    if (L->Heap || L->count) {
        L->RaiseError("UseHeap",CONTAINER_ERROR_NOT_EMPTY);
        return CONTAINER_ERROR_NOT_EMPTY;
    }
    if (m == NULL)
        m = CurrentAllocator;
    L->Heap = iHeap.Create(L->ElementSize+sizeof(list_element), m);
    return 1;
}

This function installs a heap to be used by the list. This is very important for huge lists, since performance goes quickly down if you call malloc for each element you add to the list. Basically, the heap is just a way to allocate memory in blocks so that malloc calls are reduced.

4.2  Queues

Queues are, to use the C++ terminology, adaptor containers, i.e. containers based on other containers, in this case a list. We describe here an implementation with the objective to show how those adaptors can be implemented, and how you can restrain the interface of the underlying container with a small cost.
The data structure used is very simple:
typedef struct _Queue {
    QueueInterface *VTable;
    List *Items;
} _Queue;

Just two fields: the interface and the underlying list. We do not document here some functions of the queue interface that trivially call the corresponding List functions.

Back
              
static int Back(Queue *Q,void *result)
{
    size_t idx;
    if (Q == NULL) {
        iError.RaiseError("iQueue.Front",CONTAINER_ERROR_BADARG);
        return CONTAINER_ERROR_BADARG;
    }
    idx = iList.Size(Q->Items);
    if (idx == 0)
        return 0;
    return iList.CopyElement(Q->Items,idx-1,result);
}

Returns the last element of the queue. We do not want to have any errors issued by the underlying list, so we test for  NULL . We use the size as an index, except of course when the queue is empty.
CreateWithAllocator
              
static Queue *CreateWithAllocator(size_t ElementSize,
                                  ContainerAllocator *allocator)
{
    Queue *result = allocator->malloc(sizeof(Queue));

    if (result == NULL)
        return NULL;
    result->Items = iList.CreateWithAllocator(ElementSize,allocator);
    if (result->Items == NULL) {
        allocator->free(result);
        return NULL;
    }
    result->VTable = &iQueue;
    return result;
}

Using the given allocator, we get memory for the Queue object, then for the list using the given allocator.
Finalize
              
static int Finalize(Queue *Q)
{
    ContainerAllocator *allocator = iList.GetAllocator(Q->Items);
    iList.Finalize(Q->Items);
    allocator->free(Q);
    return 1;
}

We should free the queue header object with the same allocator we used for the list. We obtain it first, before we free the list.
Front
              
static int Front(Queue *Q,void *result)
{
    size_t idx;
    if (Q == NULL) {
        iError.RaiseError("iQueue.Front",CONTAINER_ERROR_BADARG);
        return CONTAINER_ERROR_BADARG;
    }
    idx = iList.Size(Q->Items);
    if (idx == 0)
        return 0;
    return iList.CopyElement(Q->Items,0,result);
}

Same as Back. We make the error checking to avoid errors when accessing the list.
Sizeof
              
static size_t Sizeof(Queue *q)
{
    if (q == NULL) return sizeof(Queue);
    return sizeof(*q) + iList.Sizeof(q->Items);
}

If passed a  NULL queue, we return the size of the Queue header object. Note that we do not return the size of the underlying list even if it has been allocated and uses up space. An alternative design would have required to take into account the list header as it would have been part of the overhead of the Queue object. But in that case we could never know the size of the Queue itself...

4.3  The dictionary

Dictionary is an instance of a hash table where the key is supposed to contain character strings (names) that are associated with some data. Hash tables are normal tables that are indexed by a hash function, i.e. a function that maps character strings into some integer that is used to index the table. At each slot of the table we find a linked list of elements that were classified by the hash function into the same slot. If we have a good hash function, i.e. one that spreads evenly the elements across the table, we can have a speed up for searching an element of the order of the table size, in the best case.

4.3.1  Hashing

One of the important aspects of a dictionary implementation is to use a good hash function, i.e. one that distributes evenly the keys. I have picked up for this work one of the most used functions of this type. Here is the documentation I found for this function in the Apache runtime:
This is the popular `times 33' hash algorithm which is used by perl and that also appears in Berkeley DB. This is one of the best known hash functions for strings because it is both computed very fast and distributes very well.
The originator may be Dan Bernstein but the code in Berkeley DB cites Chris Torek as the source. The best citation I have found is "Chris Torek, Hash function for text in C, Usenet message < 27038@mimsy.umd.edu > in comp.lang.c , October, 1990." in Rich Salz's USENIX 1992 paper about INN which can be found at http://citeseer.nj.nec.com/salz92internetnews.html.
The magic of number 33, i.e. why it works better than many other constants, prime or not, has never been adequately explained by anyone. So I try an explanation: if one experimentally tests all multipliers between 1 and 256 (as I did while writing a low-level data structure library some time ago) one detects that even numbers are not useable at all. The remaining 128 odd numbers (except for the number 1) work more or less all equally well. They all distribute in an acceptable way and this way fill a hash table with an average percent of approx. 86%.
If one compares the chi2 values of the variants (see Bob Jenkins "Hashing FAQ" at http://burtleburtle.net/bob/hash/hashfaq.html for a description of chi2), the number 33 not even has the best value.
But the number 33 and a few other equally good numbers like 17, 31, 63, 127 and 129 have nevertheless a great advantage to the remaining numbers in the large set of possible multipliers: their multiply operation can be replaced by a faster operation based on just one shift plus either a single addition or subtraction operation. And because a hash function has to both distribute good and has to be very fast to compute, those few numbers should be preferred.
- Ralf S. Engelschall < rse@engelschall.com >
Julienne Walker has another twist to this story. She says:71
Bernstein hash
Dan Bernstein created this algorithm and posted it in a newsgroup. It is known by many as the Chris Torek hash because Chris went a long way toward popularizing it. Since then it has been used successfully by many, but despite that the algorithm itself is not very sound when it comes to avalanche and permutation of the internal state. It has proven very good for small character keys, where it can outperform algorithms that result in a more random distribution.
Bernstein's hash should be used with caution. It performs very well in practice, for no apparently known reasons (much like how the constant 33 does better than more logical constants for no apparent reason), but in theory it is not up to snuff. Always test this function with sample data for every application to ensure that it does not encounter a degenerate case and cause excessive collisions.

hash
              
static unsigned int hash(const unsigned char *key)
{
   unsigned int Hash = 0;
   const unsigned char *p;
		
    for (p = key; *p; p++) {
        Hash = Hash * 33 + scatter[*p];
    }
    return Hash;
}

Note that I have slightly modified the algorithm by using a scatter table of 256 positions filled with random numbers. The objective is to avoid that letters that appear frequently in the text would tend to cluster the keys in the same position.
This default function may not be the best for the data in the user's application. The library has reserved a field in the dictionary header object for a pointer to a hash function that can be changed by the user.

4.3.2  Creation

Another important aspect of the dictionary implementation is the decision of how many slots the table should have. I have followed the recommendations of Dave Hanson in his Book "C interfaces and Implementations"72, and I use a small table of primes to decide what size the table should have:
Init
              
static Dictionary *Init(Dictionary *Dict,
                              size_t elementsize,size_t hint)
{
    size_t i,allocSiz;
    static unsigned primes[] = { 509, 509, 1021, 2053, 4093, 8191, 
                16381, 32771, 65521, 131071, 0 };
    for (i = 1; primes[i] < hint && primes[i] > 0; i++)
        ;
    allocSiz = sizeof (Dictionary);
    memset(Dict,0,allocSiz);
    allocSiz = primes[i-1]*sizeof (Dict->buckets[0]);
    Dict->buckets = CurrentAllocator->malloc(allocSiz);
    if (Dict->buckets == NULL) {
        return NULL;
    }
    memset(Dict->buckets,0,allocSiz);
    Dict->size = primes[i-1];
    Dict->hash = hash;
    Dict->VTable = &iDictionary;
    Dict->ElementSize = elementsize;
    Dict->Allocator = CurrentAllocator;
    Dict->RaiseError = iError.RaiseError;
    return Dict;
}

The primes in the table are the nearest primes to the regular powers of two. Table sizes can range from 509 to more than 130000, what gives a really wide range of table sizes. Obviously, bigger tables could be necessary, and other specialized implementations could use the hint parameter to extend this algorithm or to use a completely different algorithm altogether.

4.3.3  Adding elements

This operation consists of:
Add
              
static int Add(Dictionary *Dict,const unsigned char *Key,void *Value)
{
    size_t i;
    struct DataList *p;
    unsigned char *tmp;

    if (Dict == NULL) 
        return NullPtrError("Add");
    if (Dict->Flags & CONTAINER_READONLY) 
        return ReadOnlyError(Dict,"Add");
    if (Key == NULL || Value == NULL) 
        return BadArgError(Dict,"Add");
    i = (*Dict->hash)(Key) % Dict->size;
    for (p = Dict->buckets[i]; p; p = p->Next) {
        if (strcmp(Key, p->Key) == 0)
            break;
    }
    Dict->timestamp++;
    if (p == NULL) {
        p = Dict->Allocator->malloc(sizeof(*p)+Dict->ElementSize);
        tmp = Dict->Allocator->malloc(1+strlen((char *)Key));
        if (p == NULL || tmp == NULL) {
            if (p) Dict->Allocator->free(p);
            if (tmp) Dict->Allocator->free(tmp);
            return NoMemoryError(Dict,"Add");
        }
        p->Value = (void *)(p+1);
        strcpy(tmp,Key);
        p->Key = tmp;
        p->Next = Dict->buckets[i];
        Dict->buckets[i] = p;
        Dict->count++;
    }
    memcpy((void *)p->Value,Value,Dict->ElementSize);
    return 0;
}

Following the logical steps outlined above, we:
  1. Call the hash function and use its result modulo the size of the slot table to fetch the list at the indicated slot.
  2. See if the key was absent. If that is the case, we need to add a new key. We copy the key and allocate memory for a new list element that is initialized afterwards with the copied value of the key and inserted into the list.
  3. Copy in the value. If it was a new key, its value is initialized, if the key was already present we overwrite the old contents.
This function uses strcmp for comparing keys. This has the advantage of simplicity and speed, but in many other contexts a key comparison function would be necessary, to allow for keys in Unicode for instance, or for binary keys, for instance a GUID or similar binary data.
An important design decision was to replace the data associated with a key if the key is already there. This is a decision that has consequences for all associative containers, since it must be coherent in all of them. Since the Ïnsert" function allows for non-destructive insertions, Add was allowed to replace contents since this is a very common operation for instance in some symbol tables, where Ïnsert if absent or replace if present" is used to ensure that a symbol is associated with a certain value. 73. At the same time we need a Replace function since we want to get an error if the element we want to replace was not found. A small table makes this clearer
Add Insert or replace an item for a key
Insert Insert, error if the key was present
Replace Replace, error if key was absent

4.3.4  Implementing iterators

Iterators in sequential containers are conceptually easy: just start at the first and stop at the last. In associative containers however things are more complicated since there is no obvious way to order them. The solution retained in the sample implementation involves going through all elements starting at the first element of the slots table, and for each slot go through the linked list of items if any. This guarantees to visit all elements in a fixed order. As an example of this here is the Apply function that should go through all elements calling the given function for each one of them.
Apply
              
static int Apply(Dictionary *Dict,
                 int (*apply)(const char *Key,
                             const void *Value, 
                             void *ExtraArgs),
                void *ExtraArgs)
{
    size_t i;
    unsigned stamp;
    struct DataList *p;

    if (Dict == NULL) {
        return NullPtrError("Apply");
    }
    if (apply == NULL)
        return BadArgError(Dict,"Apply");
    stamp = Dict->timestamp;
    for (i = 0; i < Dict->size; i++) {
        for (p = Dict->buckets[i]; p; p = p->Next) {
            apply(p->Key,p->Value, ExtraArgs);
            if (Dict->timestamp != stamp)
                return 0;
        }
    }
    return 1;
}

As we outlined above, we start at slot zero, going upwards. If we find a non-empty slot, we go through the linked list of items.
Iterators are implemented using the same algorithm, and need conceptually two indexes to remember their position: a first index for the slots table, and another for the position in the list of items at that slot.
The implementation of the dictionary iterator is as follows:
struct DictionaryIterator {
   Iterator it;
   Dictionary *Dict;
   size_t index;
   struct DataList *dl;
   size_t timestamp;
   unsigned long Flags;
};

The index field remembers the position in the slot table, and the dl field is just a small structure that contains a link to the next item in the linked list and a pointer to the key. Storing the list element itself spare us the work of going through all the list to position ourselves at each advance of the cursor in the list.

4.4  The bloom filter

This container is a completely different beast as all other ones we have in the library. It is a probabilistic data structure. It was conceived by Mr Burton Howard Bloom in 1970 according to D. E Knuth in his Art of Computer Programming.
Bloom filters are designed to cheaply test if a given element is in a large set. It is possible that the filter says that an element is there when in fact, it is not. But if the filter says it is not there you can be ceratin that the element is not in the set.
You can add elements to the set but not remove them. The more elements you add to the filter, the larger the posibility of getting false positives, i.e. getting an answer of ÿes, the element is there" when in fact it is not.

4.5  Debugging malloc

The library provides a sample of how a malloc used for debugging allocation problems could look like. It is designed to be enhanced and even if it has several important features like detection of double free and buffer overflows, it is not a competitor for the professional versions you can find in the market like valgrind or similar.
Malloc
              
static void *Malloc(size_t size)
{
    register char *r;
    register size_t *ip = NULL;

    size = ALIGN_DEFAULT(size);
    size += 3 * sizeof(size_t);
    r = malloc(size);
    if (r == NULL)
        return NULL;
    AllocatedMemory += size;
    ip = (size_t *) r;
    *ip++ = SIGNATURE;
    *ip++ = size;
    memset(ip, 0, size - 3*sizeof(size_t));
    ip = (size_t *) (&r[size - sizeof(size_t)]);
    *ip = MAGIC;
    return (r + 2 * sizeof(size_t));
}

The algorithm is as follows: The other functions that complete this memory manager (free, realloc calloc) are not shown here (they are available in the source code of the library). They just undo what Malloc has built, calling the error functions if they detect a problem.
This simple system has several drawbacks. For completeness here is the code of the free function for the debugging malloc sample:
Free
              
 1 static void Free(void *pp)
 2 {
 3         size_t *ip = NULL;
 4         size_t s;
 5         register char *p = pp;
 6         if (p == NULL)
 7                 return;
 8         p -= 2 * sizeof(size_t);
 9         ip = (size_t *) p;
10         if (*ip == SIGNATURE) {
11             *ip++ = 0;
12             s = *ip;
13             ip = (size_t *) (&p[s - sizeof(size_t)]);
14             if (*ip != MAGIC) {
15                 /* overwritten block size */
16                 iError.RaiseError("Free",
                          CONTAINER_ERROR_BUFFEROVERFLOW);
17                 return;
18             }
19             *ip = 0;
20             AllocatedMemory -= s;
21             memset(p,66,s);
22             free(p);
23         }
24         else {
25             /* Wrong block passed to Free */
26             iError.RaiseError("Free",CONTAINER_ERROR_BADPOINTER);
27         }
28 }

4.6  The observer interface

This interface allows arbitrary functions to be called when some interesting event happens. It supposes several actors that play together: The observer interface has three entry points:
  1. Subscribe. This operation is started by an object that wants to be notified of events happening in a specific container. It calls the associative interface to be notified when those events occur.
  2. Notify. The container sends events descriptions to the interface. The interface searches the observer list and if an interested object exists, its associated function is called.
  3. Unsubscribe Either the container is going out of scope or the object that receives the notifications is going out of scope and wants to stop the process. The associative interface is called to break the event stream. It can be that an either object is no longer interested in receiving notifications for a specific container without any change in scope: One of the objects desires to break the relationship.
The observer object then, is very simple:
typedef struct _tagObserver {
    void *ObservedObject; 
    ObserverFunction Callback; 
    unsigned Flags; 
} Observer;

The association is between an observed object (the container) and another unspecified object represented by its callback here. The flags contain in each bit an event code74. If an observer wants to subscribe to several events it sets different bits in this field.
Note that we do not characterize further the observed object: it is just a void *. This is not a great idea since the InitObserver function assumes it is a generic container.
We need a table of this objects because several containers could have several observers defined.
static Observer *ObserverVector;
static size_t vsize;

Now we can start describing the functions themselves
Subscribe
              
static int Subscribe(void *ObservedObject, 
                     ObserverFunction callback, unsigned flags)
{
    Observer result;
    int r = InitObserver(&result,ObservedObject,callback,flags);
    if (r > 0)
        r = AddObject(&result);
    return r;
}

We initialize an observer object, and if that succeeds we add it to the association tables. We use temporary storage for the initialization because the ÄddObserver" function copies the contents into the table75.
InitObserver
              
static int InitObserver(Observer *result,void *ObservedObject, 
                        ObserverFunction callback, unsigned flags)
{
    GenericContainer *gen = ObservedObject;
    unsigned Subjectflags = gen->Flags;
    Subjectflags |= CONTAINER_HAS_OBSERVER;
    gen->Flags=Subjectflags;
    memset(result,0,sizeof(Observer));
    result->ObservedObject = ObservedObject;
    result->Callback = callback;
    result->Flags = flags;
    if (ObserverVector == NULL && initVector() == 0) {
        return CONTAINER_ERROR_NOMEMORY;
    }
    return 1;
}

This function assumes that it receives a container that follows the requirements of generic containers, i.e. it has a Get/Set flags field. It sets a bit in the flags field that is tested at each function that modifies the number of elements within the container logic. This means in most machines a bit test, a very fast operation that should not really affect the speed of the library code in a significant way.
A far more important consideration is that the interface is called with a notification for many functions that the user hasn't subscribed at all. This could be speeded up simply by storing the flags somewhere in the container, but the sample implementation doesn't go that far. The reason is that it is assumed that observers are seldom used, and the objects that have an observer defined are surely heavyweight objects where the slow down caused by the observer interface is not that significant.
Of course this assumptions could be very wrong: other, better implementations could decide otherwise.
AddObject
              
static int  AddObject(Observer *ob)
{
    size_t i;
    Observer *tmp;

1:  for (i=0; i<vsize;i++) {
        if (ObserverVector[i].ObservedObject==NULL) {
            memcpy(ObserverVector+i,ob,sizeof(Observer));
            return 1;
        }
    }
2:  tmp = realloc(ObserverVector,(vsize+CHUNK_SIZE)*sizeof(Observer));
    if (tmp == NULL) {
        iError.RaiseError("iObserver.Subscribe",
                             CONTAINER_ERROR_NOMEMORY);
        return CONTAINER_ERROR_NOMEMORY;
    }
    ObserverVector = tmp;
    memset(ObserverVector+vsize+1,0,(CHUNK_SIZE-1)*sizeof(Observer));
    memcpy(ObserverVector+vsize,ob,sizeof(Observer));
    vsize+= CHUNK_SIZE;
    return 1;
}

The AddObject function is responsible for inserting a new association in the existing table. First (in 1: above) it searches for a free slot. If a free slot is available it copies the new association into it and returns.
If there isn't any free slot it attempts to enlarge the table (2:). If an error occurs, the original table is still valid but no more elements can't be added. It reports the error and returns with the error code.
Otherwise all went well, and a new element is inserted.
Notify
              
static int Notify(void *ObservedObject,unsigned operation,
                  void *ExtraInfo1,void *ExtraInfo2)
{
   int count=0;
   size_t idx = 0;
   void *ExtraInfo[2];

   ExtraInfo[0] = ExtraInfo1;
   ExtraInfo[1] = ExtraInfo2;
   for (idx=0; idx < vsize;idx++) {
      if (ObserverVector[idx].ObservedObject == ObservedObject) {
         if (ObserverVector[idx].Flags & operation) {
            ObserverVector[idx].Callback(ObservedObject,
                                        operation,ExtraInfo);
            count++;
         }
      }
   }
   return count;
}

This is a simple linear search function. We search for an association that has the same observed object and in the flags field has a bit set that indicates that is interested in this operation. If both conditions are true we call the registered function.
Unsubscribe
              
static size_t Unsubscribe(void *ObservedObject,
                          ObserverFunction callback)
{
    size_t idx,count=0;

1:  if (ObservedObject == NULL) {
        if (callback == NULL) 
            return 0;
        for (idx=0; idx<vsize;idx++) {
            if (ObserverVector[idx].Callback == callback) {
                memset(ObserverVector+idx,0,sizeof(Observer));
                count++;
            }
        }
        return count;
    }
2:  if (callback == NULL) {
        for (idx=0;idx<vsize;idx++) {
            if (ObserverVector[idx].ObservedObject == ObservedObject) {
                memset(ObserverVector+idx,0,sizeof(Observer));
                count++;
            }
        }
        return count;
    }
3:  for (idx=0; idx<vsize;idx++) {
        if (ObserverVector[idx].ObservedObject == ObservedObject &&
            ObserverVector[idx].Callback == callback) {
            memset(ObserverVector+idx,0,sizeof(Observer));
            count++;
        }
    }
    return count;
}

Unsubscribe should handle three different situations: To erase an item we just set it to zero, supposing that the next time an object subscribes the empty slot will be found and used. Obviously this method could waste some space in case we ever do only a single relationship in the whole program. The number of slots that is reserved in the sample implementation is small, to avoid wasting memory in case there are few observers. More sophisticated implementations can add features here.

4.7  ValArrays

All ValArrays are implemented using a template file that receives its parameters from a small c file. The same is done for the header files, that are also controlled by a header file. Since header files do not contain any definitions, only declarations, the different headers are grouped into a single header file that includes the templated file several times. Here is an excertp of valarray.h:
  1 /**********************************************  *       
  2  *          ValArraySize_t                       *
  3  *************************************************/ 
  4 #undef ElementType
  5 #undef ValArrayInterface
  6 #undef ElementType
  7 #undef ValArray
  9 #undef _ValArray
 10 #define ValArray ValArraySize_t
 11 #define ElementType size_t
 12 #define ValArrayInterface ValArraySize_tInterface
 13 #define __IS_UNSIGNED__
 14 #define __IS_INTEGER__
 15 #include "valarraygen.h"
 16 #undef __IS_UNSIGNED__
 17 #undef __IS_INTEGER__
 18 extern ValArraySize_tInterface iValArraySize_t;
 

Lines 4-9 remove a possible previous definition of the parameters we are going to use for valarraygen.h. Then we start defining the parameters: The valarraygen.h (gen for generic) defines the interfaces for all the parameter types. Here is a small part of it so that you get the idea:
typedef struct _ValArray ValArray;
typedef struct tagValArray {
    size_t (*Size)(const ValArray *AL);
    int (*Contains)(ValArray *AL,ElementType data);
    int (*Erase)(ValArray *AL,ElementType elem);
    // ... snip
} ValArrayInterface;

In a very similar way, the generic ValArray containers for all basic types are organized in a small parameter file "valarrayint.c", "valarraydouble.c" and others that make the necessary defines so that the underlying valarraygen.c defines a function for each required basic type. Programming in valarraygen.c is fairly simple. Here is a function that is parametrized by the ElementType macro:
static ElementType GetElement(const ValArray *AL,size_t idx)
{
        size_t start=0,incr=1,top=AL->count;

        if (AL->Slice) {
                start = AL->Slice->start;
                incr = AL->Slice->increment;
                top = AL->Slice->length;
        }
        if (idx >=top ) {
                IndexError("GetElement");
                return MinElementType;
        }
        idx = start+idx*incr;
        return AL->contents[idx];
}

We see here: The valarraygen.c file can be used to provide for an array of actually any data structure that is small enough to be returned by value. It suffices to change the ElementType to the concerned structure whose definition must be visible to the compiler. In the next chapter we see how this could be done.

Chapter 9
Building generic components

1  Pre processing a generic file

If you take the source code of a container like ärraylist", for instance, you will notice that all those "void *äre actually a single type, i.e. the type of the objects being stored in the container. All generic containers use "void *" as the type under which the objects are stored so that the same code works with many different types.
Obviously another way is possible. You could actually replace the object type within that code and build a family of functions and types that can be specialized by its type parameter. For instance:
struct tag$(TYPE)ArrayInterface;
typedef struct _$(TYPE)Array {
   struct tag$(TYPE)ArrayInterface *VTable; 
   size_t count;
   unsigned int Flags;
   $(TYPE) *contents;
   size_t capacity;
   size_t ElementSize;
   unsigned timestamp;
   CompareFunction CompareFn; 
    ErrorFunction RaiseError;
} $(TYPE)_Array ;

Now, if we just substitute $(TYPE) with "double" in the code above, we obtain:
struct tagdoubleArrayInterface;
typedef struct _doubleArray {
   struct tagdoubleArrayInterface *VTable; 
   size_t count;
   unsigned int Flags;
   double *contents;
   size_t capacity;
   size_t ElementSize;
   unsigned timestamp;
   CompareFunction CompareFn; 
    ErrorFunction RaiseError;
} double_Array ;

We use the name of the parameter to build a family of names, and we use the name of the type parameter to declare an array of elements of that specific type as the contents of the array. This double usage allows us to build different name spaces for each different array type, so that we can declare arrays of different types without problems.
Using the same pattern, we can build a family of functions for this container that is specialized to a concrete type of element. For instance we can write:
static int EraseAt($(TYPE)_Array *AL,size_t idx)
{
        $(TYPE) *p;
        if (idx >= AL->count)
                return CONTAINER_ERROR_INDEX;
        if (AL->Flags & AL_READONLY)
                return CONTAINER_ERROR_READONLY;
        if (AL->count == 0)
                return -2;
        p = AL->contents+idx;
        if (idx < (AL->count-1)) {
                memmove(p,p+1,(AL->count-idx)*sizeof($(TYPE)));
        }
        AL->count--;
        AL->timestamp++;
        return AL->count;
}

when transformed, the function above becomes:
static int EraseAt(double_Array *AL,size_t idx)
{
    double *p;
    if (idx >= AL->count)
        return CONTAINER_ERROR_INDEX;
    if (AL->Flags & AL_READONLY)
        return CONTAINER_ERROR_READONLY;
    if (AL->count == 0)
        return -2;
    p = AL->contents+idx;
    if (idx < (AL->count-1)) {
        memmove(p,p+1,(AL->count-idx)*sizeof(double));
    }
    AL->count--;
    AL->timestamp++;
    return AL->count;
}

Now we can build a simple program in C that will do the substitution work for us. To make things easier, that program should build two files: We separate the commands to change the name of the file from the rest of the text by introducing in the first positions of a line a sequence of three or more @ signs. Normally we will have two of those "commands": one for the header file, another for the c file.
Besides that, our program is just a plain text substitution. No parsing, nor anything else is required. If we write "$(TYPE)" within a comment or a character string, it will be changed too.
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#define MAXLINE_LEN     2048
#define MAX_FNAME       1024
#define EXPANSION_LENGTH 256

int main(int argc,char *argv[])
{
   FILE *input,*output=NULL;
   char buf[MAXLINE_LEN],
        tmpLine[MAXLINE_LEN+EXPANSION_LENGTH];
   char tmpBuf[MAX_FNAME];
   char outputFile[MAX_FNAME];
   char *TypeDefinition;
   unsigned lineno = 1;

   if (argc < 3) {
      fprintf(stderr,
        "Usage: %s <template file to expand> <type name>\n",
              argv[0]);
         return EXIT_FAILURE;
   }
   input = fopen(argv[1],"r");
   if (input == NULL) {
       fprintf(stderr,"Unable to open file '%s'\n",argv[1]);
       return EXIT_FAILURE;
   }
   TypeDefinition = argv[2];
   while (fgets(buf,sizeof(buf)-1,input)) {
       if (buf[0]=='@' && buf[1] == '@' && buf[2] == '@') {
          int i=0,j=0;
          while (buf[i]  == '@')
                 i++;
          while (buf[i] != 0 && 
                 buf[i] != '\n' && 
                 i < MAX_FNAME-1) {
                   tmpBuf[j++] = buf[i];
                 i++;
           }
           tmpBuf[j] = 0;
           if (strrepl(tmpBuf,"$(TYPE)",TypeDefinition,NULL)) {
                 fprintf(stderr,"File name '%s' too long\n",
                                tmpBuf);
                 return EXIT_FAILURE;
           }
           strrepl(tmpBuf,"$(TYPE)",TypeDefinition,outputFile);
           if (output != NULL)
               fclose(output);
               output = fopen(outputFile,"w");
               if (output == NULL) {
                  fprintf(stderr,
                         "Impossible to open '%s'\n",outputFile);
                  return(EXIT_FAILURE);
               }
            }
            else if (lineno == 1) {
              fprintf(stderr,
              "Error: First line should contain the file name\n");
              exit(EXIT_FAILURE);
            }
            else {
             /* Normal lines here */
                if (strrepl(buf,"$(TYPE)",TypeDefinition,NULL) 
                     >= sizeof(tmpLine)) {
                 fprintf(stderr,
                      "Line buffer overflow line %d\n",lineno);
                       break;
                }
                strrepl(buf,"$(TYPE)",TypeDefinition,tmpLine);
                fwrite(tmpLine,1,strlen(tmpLine),output);
           }
           lineno++;
        }
        fclose(input);
        fclose(output);
        return EXIT_SUCCESS;
}

The heart of this program is the ßtrrepl" function that replaces a given character string in a piece of text. If you call it with a  NULL output parameter, it will return the number of characters that the replacement would need if any. For completeness, here is the code for strrepl:
int strrepl(const char *InputString, const char *StringToFind,
            const char *StringToReplace, char *output)
{
    char *offset = NULL, *CurrentPointer = NULL;
    int insertlen;
    int findlen = strlen(StringToFind);
    int result = 0;

    if (StringToReplace)
       insertlen = strlen(StringToReplace);
    else
       insertlen = 0;
    if (output) {
        if (output != InputString)
            memmove(output,InputString,strlen(InputString)+1);
            InputString = output;
    }
    else
       result = strlen(InputString)+1;

    while (*InputString)    {
    offset = strstr (!offset ? InputString : CurrentPointer,
                     StringToFind);
       if (offset == NULL)
           break;
       CurrentPointer = (offset + (output ? insertlen : findlen));
       if (output) {
           strcpy (offset, (offset + findlen));
           memmove (offset + insertlen,
                       offset, strlen (offset) + 1);
           if (insertlen)
               memcpy (offset, StringToReplace, insertlen);
           result++;
       }
       else {
           result -= findlen;
           result += insertlen;
       }
    }
    return result;
}

And now we are done. The usage of this program is very simple:
   expand <template file> <type name>

For instance to substitute by "double" in the template file ärraylist.tpl" we would use:
   expand arraylist.tpl double

We would obtain doublearray.h and doublearray.c
BUG: Obviously, this supposes that the type name does NOT contain any spaces or other characters like '*' or "[ ]". If you want to use types with those characters you should substitute them with a "_" for instance, and make a typedef:
typedef long double long_double;
And use that type ("long_double") as the substitution type.

2  Using the pre-processor

Instead of using a separate program we can try to use the pre-processor to make the grunt of the editing work. This is the path taken by the sample implementation. We will describe here the listgen.c and listgen.h files that implement a type-generic list container.
Each type needs two files:
  1. A header file where the data structures are declared. The name of this file is composed from the name of the type and the name of the container. For instance for a double linked list of integers we would have: intdlist.h.
  2. An implementation file where the code for the data type resides. The name is the same as the header file but with a .c extension 76.
In the sample implementation the different files (intlist.h, doublelist.h, etc) are very small files that mainly define a single macro, including afterwards a generic file with the bulk of the code. For instance here is the intlist.h file:
#include "containers.h"
#undef DATA_TYPE
#define DATA_TYPE int // The name of the type
#include "listgen.h"  // The generic header file
#undef DATA_TYPE
#undef LIST_TYPE
#undef LIST_TYPE_
#undef INTERFACE
#undef ITERATOR
#undef ITERFACE_NAME
#undef LIST_ELEMENT
#undef LIST_ELEMENT_

The #undef statements allow to include several files of the same type in a source file.

2.0.1  The generic header file

The generic list file above (listgen.h) has the following tasks:
  1. Define the container data type: [type][container] in this case intList. Since we are defining a list, we define a list element component:
    typedef struct LIST_ELEMENT {
        struct LIST_ELEMENT *Next;
        DATA_TYPE Data;
    } LIST_ELEMENT;
    
    
    This will be expanded by the pre processor to:
    typedef struct intListElement {
        struct intListElement *Next;
        int Data;
    } intListElement;
    
    
  2. Define the interface structure (the function table) using generic notation:
    struct INTERFACE_STRUCT_INTERNAL_NAME(DATA_TYPE) {
        size_t (*Size)(const LIST_TYPE *L);
        unsigned (*GetFlags)(const LIST_TYPE *L);
        unsigned (*SetFlags)(LIST_TYPE *L,unsigned flags);
        int (*Contains)(const LIST_TYPE *L,const DATA_TYPE element);
        int (*Erase)(LIST_TYPE *L,const DATA_TYPE);
        int (*EraseAll)(LIST_TYPE *l,const DATA_TYPE);
        // ...
    };
    
    
    This will be translated into:
    struct __intListInterface {
        size_t (*Size)(const intList *L);
        unsigned (*GetFlags)(const intList *L);
        unsigned (*SetFlags)(intList *L,unsigned flags);
        int (*Clear)(intList *L);
        int (*Contains)(const intList *L,const int element);
        int (*Erase)(intList *L,const int);
        int (*EraseAll)(intList *l,const int);
        // ...
    };
    
    

2.0.2  The implementation file

There are two posibilities:
This first edition of the sample implementation has chosen the second strategy since the code is new, and probably a lot of changes will be necessary before it stabilizes. In general we have three types of functions:
The implementation file receives several other optional parameters.
#define COMPARE_EXPRESSION(a, b) \
      ((*b)->Data > (*a)->Data ? -1 : (*b)->Data != (*a)->Data)

This expression is used to return -1 for a < b, zero for equality and 1 for a > b77.


Chapter 10
API Overview


Here is a table of all functions of the library. It indicates which functions are implemented in each container. Some containers are fused together since they implement exactly the same functions: all the ValArray containers are displayed in a single column, the containers StringList and wStringList share the same column also.
Function List Dlist Vector Val Bit- String Queue Deque Dict. Hash Tree Priority Stream
Namearray string collection table map queue buffer
Abs
X
Accumulate X
Add X X X X X X X X X X
AddRange X X X X X X X
And X X
AndAssign X
AndScalar X
Append X X X X X X
Apply X X X X X X X X X X
Back X X X X X X X
BitBlockCount X
BitLeftShift X X
BitRightShift X X
CastToArray X X
Clear X X X X X X X X X X X X X
Compare X
CompareEqual X X X
CompareEqualScalar X X X
CompareScalar X
Contains X X X X X X X X X X
Copy X X X X X X X X X X X
CopyBits X
CopyElement X X X X X
CopyTo X X X
Create X X X X X X X X X X X X X
CreateFromFile X X
CreateSequence X
CreateWithAllocator X X X X X X X X X X X
Dequeue X
DivideBy X
DivideByScalar X
DivideScalarBy X
Enqueue X
Equal X X X X X X X X X X
Erase X X X X X X X X X X
EraseAll X X X X X
EraseAt X X X X X X
EraseRange X
FCompare X
FillSequential X
Finalize X X X X X X X X X X X X X
FindFirst X
FindNext X
FindText X
FindTextIndex X
FindTextPositions X
FirstElement X X
ForEach X
Fprintf X
Front X X X X X X X X
GetAllocator X X X X X X X X
GetCapacity X X X X
GetData X X X X X X
GetElement X X X X X X X X X
GetElementSize X X X X X X X X X
GetFlags X X X X X X X X X X
GetHeap X
GetKeys X
GetLoadFactor X
GetPosition X
GetRange X X X X X X
GetSlice X
IndexIn X X X
IndexOf X X X X X X
Init X X X X X X X X X
InitIterator X X X X X X X X X X
InitWithAllocator X X X X
InitializeWith X X X X X X X X
Insert X X X X X X
InsertAt X X X X X X
InsertIn X X X X X X
Inverse X
LastElement X X
LessEqual X
Load X X X X X X X X X X
Max X
Memset X X
Min X
Mismatch X X X
Mod X
ModScalar X
MultiplyWith X
MultiplyWithScalar X
NewIterator X X X X X X X X X X
Not X X
NotAssign X
Or X X
OrAssign X
OrScalar X
Pop X
PopBack X X X X X X
PopFront X X X X
PopulationCount X
Print X
Product X
Push X
PushBack X X X X X X
PushFront X X X X
Read X
ReadFromFile X
RemoveRange X X X X X
Replace X X
ReplaceAt X X X X X X
Reserve X
ResetSlice X
Resize X X X X
Reverse X X X X X X X
RotateLeft X X X X
RotateRight X X X X
Save X X X X X X X X X X
Search X
SearchWithKey X
Select X X X X X
SelectCopy X X X X X
SetCapacity X X X X
SetCompareFunction X X X X X X
SetDestructor X X X X X X X X X
SetElement X
SetElementData X X
SetErrorFunction X X X X X X X X X X
SetFlags X X X X X X X X X X
SetHashFunction X X
SetPosition X
SetSlice X
Size X X X X X X X X X X X X X
Sizeof X X X X X X X X X X X X
SizeofIterator X X X X X X X X X
Sort X X X X X
Splice X
SplitAfter X X
StringToBitString X
SubtractFrom X
SubtractFromScalar X
SubtractScalarFrom X
SumScalarTo X
SumTo X
Union X
UseHeap X X
Write X
WriteToFile X X
Xor X X
XorAssign X
XorScalar X
Totals 56576094566511283526281315
Total APIs 544

Index (showing section)


Abs
     Observer, 13.3
Accumulate
     Observer, 13.3
Add
     Observer, 1.1, 3.2, 4.2, 6.2, 8.2, 11.3, 12.2, 13.3, 14.2
     code for Observer, 4.1, 4.3
AddObject
     code for Observer, 4.6
AddRange
     Observer, 1.1, 3.2, 5.2, 13.3
     code for Observer, 4.1
Advance
     Observer, 1.1
Alloc
     Observer, 3.0
Allocator, 16.0
And
     Observer, 1.2, 4.2, 13.3
AndAssign
     Observer, 4.2
API Overview, 0.0
Append
     Observer, 1.1, 3.2
     code for Observer, 4.1
Apply
     Observer, 1.1, 3.2, 6.2, 8.2, 10.1, 13.3
     code for Observer, 4.1, 4.3
     general, 12.2

Back
     Observer, 1.1, 3.2, 9.2, 10.1
     code for Observer, 4.2
BitBlockCount
     Observer, 4.2
BitLeftShift
     Observer, 4.2, 13.3
BitRightShift
     Observer, 13.3
BitString, 4.0
     structure definition, 3.9
Bloomfilter, 12.0
Buffers, 14.0

CalculateSpace
     Observer, 12.2
Calloc
     Observer, 3.0
CastToArray
     Observer, 5.2, 6.2
Clear, 2.0
     Observer, 1.1, 1.2, 2.2, 3.0, 3.2, 6.2, 8.2, 10.1, 11.3, 12.2, 13.3, 14.1, 14.2
     code for Observer, 4.1
Compare
     Observer, 13.3
COMPARE_EXPRESSION, 2.0
CompareEqual
     Observer, 3.2, 13.3
CompareEqualScalar
     Observer, 3.2, 13.3
CompareFunction, 7.2
CompareScalar
     Observer, 13.3
Contains
     Observer, 1.1, 3.2, 6.2, 10.1, 13.3
     code for Observer, 4.1
Copy
     Observer, 1.1, 1.2, 3.2, 6.2, 8.2, 10.1, 11.3, 13.3
     code for Observer, 4.1
CopyBits
     Observer, 4.2
CopyElement
     Observer, 1.1, 3.2, 6.2
     code for Observer, 4.1
CopyTo
     Observer, 3.2, 13.3
Create, 1.0
     Observer, 1.1, 1.2, 2.2, 3.0, 3.2, 6.2, 8.2, 10.1, 11.3, 12.2, 13.3, 14.1, 14.2
     code for Observer, 4.1
CreateFromFile
     Observer, 5.2, 14.1
CreateFromMask
     Observer, 1.2
CreateSequence
     Observer, 13.3
CreateWithAllocator, 6.2
     Observer, 1.1, 3.2, 14.1, 14.2
     code for Observer, 4.1, 4.2

DefaultListCompareFunction
     code for Observer, 4.1
DefaultListLoadFunction
     code for Observer, 4.1
DefaultSaveFunction
     code for Observer, 4.1
deleteIterator
     Observer, 1.1, 3.2, 6.2, 8.2
     code for Observer, 4.1
Deque, 10.0
DestructorFunction, 7.6
Dictionary, 6.0
     structure definition, 3.6
DivideBy
     Observer, 13.3
DivideByScalar
     Observer, 13.3
DivideScalarBy
     Observer, 13.3
Dlist, 2.0
     structure definition, 3.4

EmptyErrorFunction
     Observer, 4.0
Equal
     Observer, 1.1, 3.2, 6.2, 10.1, 11.3, 13.3
     code for Observer, 4.1
Erase
     Observer, 1.1, 3.2, 6.2, 8.2, 10.1, 13.3
     code for Observer, 4.1
EraseAll
     Observer, 1.1, 3.2, 13.3
EraseAt
     Observer, 1.1, 3.2, 13.3
EraseRange
     Observer, 1.1
     code for Observer, 4.1
error-codes, 4.1
ErrorFunction, 7.5

FCompare
     Observer, 13.3
FillSequential
     Observer, 13.3
Finalize, 2.0
     Observer, 1.1, 1.2, 2.2, 3.0, 3.2, 6.2, 10.1, 11.3, 12.2, 13.3, 14.1, 14.2
     code for Observer, 4.1, 4.2
Find
     Observer, 12.2
FindFirst
     Observer, 5.2
FindNext
     Observer, 5.2
FindTextPositions
     Observer, 5.2
FirstElement
     Observer, 1.1
ForEach
     Observer, 13.3
Fprintf
     Observer, 13.3
Free
     code for Observer, 4.5
FreeObject
     Observer, 2.2
Front
     Observer, 1.1, 3.2, 5.2, 9.2, 10.1, 11.3
     code for Observer, 4.2

Generic Container
     structure, 3.1
GetAllocator
     Observer, 1.1, 6.2
GetCapacity
     Observer, 3.2, 13.3
GetCurrent
     Observer, 5.1
     code for Observer, 4.1
GetData
     Observer, 3.2, 4.2, 9.2, 13.3, 14.1
GetElement
     Observer, 1.1, 3.2, 6.2, 8.2, 13.3
GetElementSize
     Observer, 1.1, 3.2, 6.2, 13.3
GetFirst
     Observer, 5.1
     code for Observer, 4.1
GetFlags
     Observer, 6.2, 8.2, 10.1
     code for Observer, 4.1
GetFlags / SetFlags
     Observer, 1.1, 3.2
GetLast
     Observer, 5.1
GetLoadFactor
     Observer, 6.2
GetNext
     Observer, 5.1
     code for Observer, 4.1
GetPosition
     Observer, 14.1
GetPrevious
     Observer, 5.1
     code for Observer, 4.1
GetRange, 5.0
     Observer, 1.1, 3.2, 4.2, 13.3
     code for Observer, 4.1
GetSlice
     Observer, 13.3
guid, 4.1

hash
     code for Observer, 4.3
HashTable, 8.0

iAssociativeContainer, 15.3
iBitString, 4.1
iDeque, 10.1
iDictionary, 6.1
iDlist, 2.0
iError, 4.0
iGenericContainer, 15.1
iHashTable, 8.1
iHeap, 2.2
iList, 1.0
IndexIn
     Observer, 3.2, 13.3
IndexOf
     Observer, 1.1, 3.2, 13.3
     code for Observer, 4.1
Infinite arrays, 3.0
Init
     Observer, 1.1, 5.2, 6.2
     code for Observer, 4.1, 4.3
InitHeap
     Observer, 2.2
InitializeWith
     Observer, 1.1, 4.2, 6.2, 13.3
InitObserver
     code for Observer, 4.6
InitWithAllocator
     Observer, 1.1, 5.2, 6.2
     code for Observer, 4.1
Insert
     Observer, 6.2
InsertAt
     Observer, 1.1, 3.2, 13.3
     code for Observer, 4.1
InsertIn
     Observer, 1.1, 3.2, 5.2, 6.2
     code for Observer, 4.1
Inverse
     Observer, 13.3
iPool, 3.0
iSequentiaContainer, 15.2
istrCollection, 5.0
Iterator
     structure, 3.10
iterator
     Dictionary, 4.3
Iterators
     General, 12.3
iTreeMap, 7.0
iValArray, 13.0
iVector, 3.0, 3.1
iWstrCollection, 5.0

LastElement
     Observer, 1.1
List, 0.3
ListIterator, 3.10
lists
     code, 4.1
     double linked, 2.0
     single linked, 1.0
     structure, 3.3
Load
     Observer, 1.1, 3.2, 6.2, 8.2, 10.1
     code for Observer, 4.1

Malloc
     code for Observer, 4.5
mapcar, 3.0
mapcon, 3.0
Mask
     structure definition, 3.8
Max
     Observer, 13.3
Memset
     Observer, 13.3
Merge
     Observer, 8.2
Min
     Observer, 13.3
Mismatch
     Observer, 3.2, 5.2, 13.3
MoveBack
     Observer, 2.0
MultiplyWith
     Observer, 13.3
MultiplyWithScalar
     Observer, 13.3

NewIterator
     Observer, 1.1, 3.2, 6.2, 8.2
     code for Observer, 4.1
newObject
     Observer, 2.2
NextElement
     Observer, 1.1
Not
     Observer, 1.2, 4.2, 13.3
NotAssign
     Observer, 4.2
Notify
     Observer, 6.2
     code for Observer, 4.6
NullPtrError
     Observer, 4.0

observer, 6.0
ObserverFunction
     Observer, 6.2
Or
     Observer, 1.2, 4.2, 13.3
OrAssign
     Observer, 4.2
OrScalar
     Observer, 13.3
Overlay
     Observer, 8.2

PeekFront
     Observer, 14.2
Pop
     Observer, 11.3
PopBack
     Observer, 2.0, 3.2, 5.2, 10.1, 13.3
PopFront
     Observer, 1.1, 10.1, 14.2
     code for Observer, 4.1
PopulationCount
     Observer, 1.2, 4.2
PreviousElement
     Observer, 2.0
Print
     Observer, 4.2
Priority queues, 11.0
Product
     Observer, 13.3
Push
     Observer, 11.3
PushBack
     Observer, 2.0, 10.1
PushFront
     Observer, 1.1, 10.1
     code for Observer, 4.1

Queue, 9.0

RaiseError
     Observer, 4.0
Read
     Observer, 14.1
ReadFromFile
     Observer, 14.1
ReadFunction, 7.4
RemoveAt
     Observer, 4.2
     code for Observer, 4.1
RemoveRange
     Observer, 1.1, 3.2, 5.2
Replace
     Observer, 5.1, 8.2
ReplaceAt
     Observer, 1.1, 3.2
     code for Observer, 4.1
Reserve
     Observer, 3.2
ResetSlice
     Observer, 13.3
Resize
     Observer, 3.2, 8.2, 14.1
Reverse
     Observer, 1.1, 3.2, 4.2, 13.3
     code for Observer, 4.1
RotateLeft
     Observer, 1.1, 3.2, 13.3
     code for Observer, 4.1
RotateRight
     Observer, 1.1, 3.2, 13.3
     code for Observer, 4.1

Save
     Observer, 1.1, 3.2, 6.2, 8.2, 10.1, 13.3
     code for Observer, 4.1
SaveFunction, 7.3
SearchWithKey
     Observer, 3.2
Seek
     Observer, 5.1
     code for Observer, 4.1
Select
     Observer, 1.1, 3.2, 13.3
SelectCopy
     Observer, 1.1, 3.2, 13.3
Set
     Observer, 1.2, 4.2
SetCapacity
     Observer, 3.2
SetCompareFunction
     Observer, 1.1, 3.2, 5.2, 13.3
     code for Observer, 4.1
SetDestructor
     Observer, 1.1, 3.2, 6.2
SetElementData
     Observer, 1.1
SetErrorFunction
     Observer, 1.1, 3.2, 4.0, 6.2, 8.2
SetHashFunction
     Observer, 6.2
SetPosition
     Observer, 14.1
SetSlice
     Observer, 13.3
Size, 14.0
     Observer, 1.1, 1.2, 3.2, 6.2, 8.2, 11.3, 13.3, 14.1, 14.2
Sizeof
     Observer, 1.1, 1.2, 2.2, 3.2, 6.2, 8.2, 11.3, 13.3, 14.2
     code for Observer, 4.1, 4.2
SizeofIterator, 15.2
     Observer, 1.1
Sort
     Observer, 1.1, 3.2, 13.3
     code for Observer, 4.1
Splice
     Observer, 2.0
SplitAfter
     Observer, 1.1
     code for Observer, 4.1
strCollection
     structure, 3.7
StrError
     Observer, 4.0
StringCompareFn, 5.2
StringToBitString
     Observer, 4.2
Subscribe
     Observer, 6.2
     code for Observer, 4.6
SubtractFrom
     Observer, 13.3
SubtractFromScalar
     Observer, 13.3
SubtractScalarFrom
     Observer, 13.3
SumTo
     Observer, 13.3
SumToScalar
     Observer, 13.3

TreeMap, 7.0

Unsubscribe
     Observer, 6.2
     code for Observer, 4.6
UseHeap
     Observer, 1.1
     code for Observer, 4.1

ValArray, 13.0
ValArrays,code, 4.7
Vector, 3.0
     structure, 3.5

Write
     Observer, 14.1
WriteToFile
     Observer, 5.2, 14.1
WstrCollection, 5.0

Xor
     Observer, 4.2, 13.3
XorAssign
     Observer, 4.2
XorScalar
     Observer, 13.3

Footnotes:

1"Precise Reasoning for programs using containers" Isil Dillig, Thomas Dillig, and Alex Aitken, available on line at http://www.stanford.edu/!~!isil/popl2011.pdfor at POPL 2011 Proceedings of the 38th annual ACM SIGPLAN-SIGACT symposium on Principles of programming languages ACM New York, NY, USA ©2011
2We were discussing the specifications of the mismatach function of the C++ STL and why any error analysis is absent. The C++ STL prescribes a bounded region for the first container, but just a starting point for the second one. If the second is shorter than the specified range of the first undefined behavior ensues and anything can happen. In many cases this änything" is different each time the same error occurs. In our specific case mismatch would read from memory that doesn't belong to the container it started with. Depending on the contents of that memory a crash could happen, or worst, a wrong result returned to the calling software, etc.
3Donald Knuth, the author of the TeX typesetting program can be without doubt be qualified as a good programmer (and an excellent computer scientist). But he, like anybody else, is not without flaws. See: www.tug.org/texmf-dist/doc/generic/knuth/errata/errorlog.pdf. There are hundreds of entries in that log.
4Matthew Wilson uses a more restrictive definition of a container in his book Ëxtended STL (Vol 1, page 16)" :
A container is a collection that owns its objects and provides operations by which those objects may be accessed and modified and, optionally, added removed and rearranged.
By this definition, containers that have just pointers to their elements wouldn't be containers at all.
5There is no automatic cleanup of objects left by active functions in the stack. This can be a problem or not, depending if your use a garbage collection or not. If you use a garbage collector, this problem doesn't even appear: the unused objects will be automatically collected. If you don't, you should test for the return code of each function.
6In general most return values could be a +size_t+,but it would be very difficult to differentiate a huge unsigned number from a negative error code. Under some versions of UNIX there exists the +rsize_t+for a signed version of +size_t+but it is not in the C standard.
7This corresponds to ENOMEM in the POSIX1 standard.
8The most similar error code using the POSIX standard would be EPERM here.
9In the C99 standard this error is reserved for a wrong sequence of wide character bytes. Here it would be used for a wrong sequence of operations what somehow changes the meaning of the error code. It is used since the standard has only three error codes.
10The POSIX error number would be here EIO: error in input/output
11The corresponding POSIX error number would be EFBIG: file too big
12In POSIX this would be EFAULT.
13The corresponding POSIX error would EBADF: bad file
14POSIX: EDOM, domain error
15This would be the ENOENT error under POSIX
16 Hashtables are present in all major computer languages: They were absent from the C++ STL for unknown reasons. They have been now incoporated into the latest C++ standard.
17This incredibly useful feature has been made now optional by the C99 committee, even if it was mandatory when the C99 standard was published.
18 Other frameworks use a similar method. For instance Apple Foundation classes has several classes that take an ällocator" argument, for instance the CFBundleCreate and other functions that create objects.
19In previous versions these functions returned some useful information in case of success, for instance the number of elements left. The problem is that an int can't span all the possible values of a size_t data type but it is needed to return negative error codes. If the result type would be a size_t the negative error codes would be transformed in other values, etc. After some iterations the present solution was used: a positive number is equal to success, without specifying what the positive number is, or what information (if any) should be coded in it. The sample implementation always returns 1.
20Usually this value is 1 of course
21In general it is a bad idea to save elements containing pointers without a custom save/load function. The pointers restored are with almost certainty wrong when restored in another environment
22Note that the container is not declared as const and could possibly be modified either directly or indirectly by the function being applied to it. Some modifications like modifying the number of elements could lead to undefined behavior since it is not required that the Apply loop tests at each iteration if the container has been modified
23This is completely different to the C++ language. In C++ you may have an invalid iterator or not if you change the underlying container, depending on the operation and the specific container involved. This interface was discarded for the following reasons:
  1. There are many rules to remember without underlying principles. You have to know the specifics of each container to know if the iterators are invalidated or not. This breaks the independence of the algorithm code from the underlying container.
  2. Any error leads directly to catastrophic consequences instead of being caught and signaled in an orderly fashion. Worst, errors do not produce always the same consequences, depending on what were the contents of the invalid memory you are using, on the memory allocation pattern, etc. In short, any error leads to very difficult maintenance problems.
  3. Any modifications of the container type lead to a review of all code that uses that container since the rules change from container to container. Iterators that worked could be invalid now. This another source of errors.
24Contrary to C++ const directive this is done at run time and an explicit check of this flag is needed. This has disadvantages (one instruction and a conditional jump are needed) but it has also advantages: you can set it when you pass some container to another module, and unset it when you need to update the container. This solution is more flexible than the static solution at the cost of a very small runtime cost.
25In C++ the allocator receives the number of items to allocate.
26An open issue is whether the interface of the memory allocator should be extended with functions like GetSize for instance, that would return the size of a given memory block, or other query functions like isMallocBlock that would allow to verify if a memory block belongs to the pool. Some proposals were discussed in the discussion group comp.std.c but nothing official has emerged from the committee meetings
27An alternative design would have been to specify not one type of observer function but to define a different function type for each possible message the containers could send. We would have then a SubscribeAdd SubscribeErase SubscribeReplace functions, combined with NotifyAdd, NotifyErase, NotifyReplace functions. That design would have been easier to control at compile time. It was rejected because of the increased complexity of the interface and the necessity for the user to define a lot of functions just to know when something as simple as "Was this container modified?" happened.
Obviously implementations can add that type of interface if they wish. In future revisions of this specifications this question will be posed again, with more actual use data to make more informed decisions.
28See the full list and the description of all notifications at the end of this section
29 The single linked list container corresponds to the C++ STL forward_list.
30Since the allocator's free() function will be used by the finalize function to free the memory used by the list, it is imperative that both lists share the same allocator.
Some implementations could make a copy of the second list using the first list allocator, then append, then release all the storage but that would change significantly the execution time. It is better to return an error in this case.
31Java, C++ and C# have a similar "Clear" functionality.
</