| 
  
    
  | 
    
       
    
       
   | 
  
    
       
   | 
  
    
       
    
       
   | 
 
 
| C is powerful enough to implement efficient and reusable parametrized lists. | C es suficientemente poderoso para implementar listas parametrizables eficientes y reutilizables. | 
Real C++ programmers use templates. But for some, they are wasteful because they create a new copy of every algorithm for each class. Others will not use them because all the source code must be provided in header files.
 
 
template <class T> class Stack {
    int top;      // private members
    T vec[100];   // constructed by T()
public:
    Stack() : top(0) {}
    ~Stack() {}
    void push(const T& it) { vec[top++] = it; }
    T pop() { return vec[--top]; }
};
Stack<int>   Sint;
Stack<float> Sfloat;
 | 
     
Consider class stack, shown in
Figure 1. To instantiate a template, one 
just uses it to declare variables, as it is the case for both 
Sint and Sfloat.
class Stack_int {
   int top;        // private members
   int vec[100];   // constructed by int()
public:
    Stack_int() : top(0) {}
    ~Stack_int() {}
    void push(const int& it) { vec[top++] = it; }
    int pop() { return vec[--top]; }
};
Stack_int Sint;
 | 
class Stack_float {
    int   top;        // private members
    float vec[100];   // constructed by float()
public:
    Stack_float() : top(0) {}
    ~Stack_float() {}
    void push(const float& it) { vec[top++] = it; }
    float pop() { return vec[--top]; }
};
Stack_float Sfloat;
 | 
     
Regretfully, the result of using the template in
Figure 1 to declare variables 
Sint and Sfloat is equivalent to 
defining two classes, one for each type of stack, as shown in
Figure 2. When more classes are used, as 
it is the case for the C++ Standard Template Library
[STL], the worse this duplication becomes. In many 
applications the resulting executable programs are just a few 
kilobytes bigger, which are negligible for current computer memory 
sizes, but in others the size increase of the executable might be 
unbearable. But there is another problem with templates.
     
When a template class is used to declare a variable, the compiler 
specializes the template code for the declaration: this process is 
called "Instantiation"; should the compiler not 
have the source code available, it would not be able to roll out 
the instantiated source code. For example, when variable 
Sint is declared in
Figure 1, the compiler generates the 
equivalent to class Stack_int in
Figure 2.  To replicate the class for 
the chosen type, enclosed in the square parenthesis 
"<int>" in the template 
invocation, the compiler needs the source code for the class, 
which forces programmers who implement templates to make all their 
algorithms public. At first sight this seems good to the people 
(quite
a few claim that
"software 
should be free"), but a closer look reveals some caveats.
When the programmer needs access to the source code, by mistake an error could be introduced. Hence, it is better for many different classes to share the same object code, which can be put into a library, a dynamic linked library (DLL), a shared library, or simply distributed in object form, ensuring that the programmer will not affect it. More software houses will be eager to develop generic algorithms when their intellectual property rights can be protected.
 
 
Why did the C++ Standard Committee choose defining templates in such a manner to force the programmer to give away source code? Are there any ways to avoid this? The simpler answer is short: yes, just use pointers.
  
 | 
Many C++ class libraries implement reference containers, as the list shown in Figure 3. These contain not the values, but pointers to the values. The problem with this approach is that more space is required to store the container, because of the extra pointer used for each stored value. They also impose a time penalty to access stored values indirectly through their pointer. This is why this type of polimorphism was not built into C++; the alternative is to use templates, even though their use requires giving away the source code.
Reference containers are completely polymorphic, as they can contain any type of object, and even mixes of objects. They are quite popular as most compilers come with versions of these. The same object code is shared amongst all lists, regardles of the stored value type, and the client programmer just needs to know the specification to use them. But many C++ programmers do not like them. Recall that one of the design goals for C++ was to allow for the most efficient implementation for an algorithm, and using extra pointers compromises this requirement. That is why the STL was developed using templates: to achieve the efficiency of a hand coded algorithm.
 
 
When facing a problem, one usually tries to find a way to lessen the requirements to reach an acceptable solution. Complete optimality is seldom required. I decided to implement a list class with the following requirements:
The first requirement is unavoidable, as otherwise people would no use the list class, prefering most of the times to either use STL's or writing their own, to gain efficiency. C and C++ programmers crave for efficiency. The second requirement comes from my dislike for all the code replication that using the STL conveys. It bothers me that, when using a container, a full new copy of the implementation is deployed for each different data type.
While programming my list class, and after trying to meet the first two requirements, I found that the third one came for free. The fourth requirement is one of convenience, as many C programmers still will not use C++. Many object that "the C++ compiler available is difficult to use, which hinders software development", or "C++ bloats code", or "I have a native C compiler, but only a C++ cross-compiler", and even "I don't know C++". I don't share any of these reasons, but they are valid to their defenders. But what really moved me is that, if it can be done in C, it will be done with plenty of elegancia in C++.
I believe that, when faced with scarcity, we have to squeeze every ounce of ingenuity to find a solution, which usually leads to a better overall product. That is why I like doing things the hard way, to get a better insight in how to achieve results, and oftentimes to find a more efficient solution. Recall that Alexander Stepanov, the STL's main architect, had the opportunity to change C++ to accommodate for his special needs [Ste-95]. Maybe a less favorable environment would have lead to a slimmer C++. Besides, the C language is ubiquitous.
     
What did I have to give in? The implementation I found works well 
for linked containers, as is the case for a linked list or a tree, 
but my solution does not go as far as reaching the efficiency and 
malleability of the
sort()
algorithm defined in header file
<algorithm>, that comes in the STL library. 
Also, C++ templates can be a bit faster than my implementation, as 
templates can be used to get rid of function invocations.
 
 
L ──>─┐ │ v ┌────────┬───┐ ┌────────┬───┐ ┌────────┬───┐ │ elem_1 │ *─┼──>│ elem_2 │ *─┼──>│ elem_3 │ *─┼─> NIL └────────┴───┘ └────────┴───┘ └────────┴───┘  | 
     
Look into any data structures book, and you will find that lists 
are drawn as shown in
Figure 4. If you examine closely the 
implementation in the
STL library for 
the list class, which can be found in header file
<list>,
you will see that it follows this structure.
 
 | 
     
There is no problem with Figure 4, but 
compare it with the diagram in
Figure 5. In this later one, the arrows 
do not point to the boxes, usually called 
"nodes" in textbooks, but instead they 
link together the "next" fields, which I call 
"link fields". Is that a big difference? Not 
really, at least from the point of view of the code needed to 
implement the list algorithms, because in either list 
implementation the code juggles with the "next" 
field. If pointer "p" points to a node, this link 
field is accessed using the following syntax:
     
p->next
     
Why is this important? Because there are many algorithms that 
already exists for classes conceived as in
Figure 4, which can be readily 
translated into equivalent algorithms for a class drawn as in
Figure 5. As a matter of fact, what I 
did was to use a list implementation that I had shelved since my 
school days, to come up with the header file clist.h, 
shown in
Listing 1, and its implementation clist.c, shown in
Listing 2.
L->last ───────────────────────────────────────┐ │ ┌────────────┐ ┌────────────┐ ┌───────────┐ │ │ v │ v │ v v │ ┌────────┬───┐ │ ┌────────┬───┐ │ ┌────────┬───┐ │ │ elem_1 │ ├─┼─┘ │ elem_2 │ ├─┼─┘ │ elem_3 │ ├─┼─┐ │ └────────┴───┘ └────────┴───┘ └────────┴───┘ │ │ ^ next ^ │ │ │ │ │ │ first last v └───────<───────────────<────────────────<─────────┘  | 
clist     
Instead of using the regular "point to the first" implementation, 
I decided to use a circular list (the "c" in 
"clist" stands for "circular"),
as in
Figure 6, because it is very efficient 
to add elements both at the beginning or the end of a circular 
list. Programmers seldom use them because they are a little more 
difficult to program; my hope is that my implementation will be 
reusable, so that never again a C programmer should have to invest 
time reprogramming this.
When implementing a list only dealing with link fields, it does not matter at all what it is the element type contained in the list, because every algorithm only deals with the linked fields. However, some pointer shifting is required to transform a pointer to the link field into a pointer to the value stored in the list.
 
 
     
In the header file
clist.h two important types get declared. Structure link_rep is the
link field used to implement the list, 
and clist_rep is the list itself, which contains the 
pointer to the last element in the list. These names end in 
"rep" because they are the internal 
Representation of the data types.
To fake data hiding a little, I used the interface structures 
called cl_link and clist. In the header 
files, all the operations for these abstract data types follow 
their declaration.
     
C does not support neither encapsulation nor name overloading, 
which forces the programmer to use naming conventions to fake 
those C++ features. What I chose to do is to prepend every 
operation name with the letters "cl_", which stand 
for circular list. For the link field type,
cl_link, only two operations are 
required: initialization with cl_link_init_() and 
destruction with cl_link_done_(). These names end 
with an underscore "_" because I also provide macros to inline 
their implementation, named without the underscore (they appear 
towards the end of header file
clist.h). As the C language does not support references, these routines receive pointers to link fields.
     
The operations for the circular list are the usual: 
initialization, destruction, copying, counting, etc. As it happens 
for
link fields, those names that end in 
underscore can be inlined using the corresponding macro. What is 
significantly different is the way to store values in one of these 
lists: instead of "inserting" the value, what one should do it to 
"link" it to the list. Hence, there are no 
"cl_insert()" or "cl_delete()" 
operations, but "cl_link_after()" and 
"cl_unlink_after()". These take as arguments pointers 
to the link fields that will get stored within the list.
 
 
typedef struct {
    int     n;
    cl_link ik;    /* link field */
} int_lk;
 | 
int_lk     
These lists force the client programmer to include the
link field in any structures that will 
be stored in a list. For example, should a list of integers be 
needed, then the programmer must come up with a structure that 
includes the integer value and the link field for the list, as it 
is done in
Figure 7 (the complete declaration and 
implementation for this type are shown in
Listing 3 and
Listing 4). How is it to program using these lists? The quicker answer is to examine the implementation of function primes(), in the file
c-list.c shown in
Listing 5 (notice the dash "-" in the name:
c-list.c is the main test program). Before storing a value in the list, a new linkable structure must be obtained from dynamic memory, and later linked into the list. In function primes(), the 
pointer "pInt" is used to create a new value invoking the macro binder_create(), which is defined in header file "binder.h", shown in
Listing 6. The result of unrolling
     
int_lk * pInt;
     
binder_create(Bi, pInt)
is to obtain, inlined, the following code:
int_lk * pInt;
do {
    (void*)(pInt) = malloc((Bi)->size);
    (Bi)->init( (void*)(pInt) );
} while(0)
     
Beware that, as binder_create() is a macro, it does 
change the value of pointer "pInt". Were 
binder_create() a regular C function, to achieve this 
same effect would require passing a pointer to pInt, 
which of course would be one of those "pointer pointers", which 
are messy
[DY-91].
     
"Bi" points to a structure that contains function 
pointers and values, one being "size", which is used 
to obtain a chunk of dynamic memory big enough to hold the 
complete node that will get linked -stored- within the list. The 
function Bi->init() is used to initialize the node 
just created. The block of code is surrounded in a 
do-while block to force the programmer to put a 
semicolon ";" after each macro invocation. The typecasts are 
required to deceive the compiler type checking, and all the 
parenthesis are used to avoid ambiguity when the macro gets 
unrolled.
     
Where did variable "Bi" get defined? In the main 
program file
c-list.c. It is a constant pointer, that can not modify what it points to. Look for the following line:
     
const binder * const Bi = &name_binder(int_lk, ik);
     
The macro name_binder(int_lk, ik) is also 
defined in header file
binder.h. It is used to build the name of the structure that contains both the "Bi->size" field and the functions pointer "Bi->init()", which are declared in file
intrat.h and defined in
intrat.c (just in case you forgot the difference between "defining" and "declaring", remember that you put declarations in header files, and implementations -definitions- elsewhere; it is usual for C definition 
files to have names that end in ".c"). Look closely again to the macro invocation for name_binder(int_lk, ik): it contains the name of the type, "int_lk", and the name of the field used to link it to a list 
"ik". Hence, this macro comes up with the name "Binder_int_lk_ik", which gets declared and initialized in
intrat.c. "Bi" is just a shorthand that references this structure. The macro define_binder() is invoked in the implementation file
intrat.c, and it gets passed the addresses of each function used to handle structure int_lk; in particular, it gets a pointer to int_init(), the function that carries out the duty of 
initializing the list node. In other words, "Bi->init()" executes function int_init().
     
After creating the new node, its value "pInt->n" 
is updated, and later the whole node is linked into the list:
     
cl_link_after(L, pHere, &pInt->ik);
 
┌────────┬───┐ │ 1999 │ ├─┼─> next └────────┴───┘ ^ ^ │ │ pInt pHere  | 
     
The link operation for the list receives a pointer to the list, 
called "L" in the primes() routine, the 
place where to leave the node denoted by pointer 
"pHere", and a pointer to the link field to chain 
into the list, "pInt->ik". For this last argument, 
it is necessary to take its address using the "&" 
operator because list operations use pointers to
link fields. In this invocation, 
variable "pHere" points to a link field, not to the 
complete value. (see
Figure 8). Pointer "pInt" 
is a different type of animal, as it points to a structure that 
contains a link field, that "pHere" can point to, to 
remember where the last addition to the list was made, to add the 
new value just after there. This is consistent with
Figure 6. Adding to the front of the 
list requires using a NULL pointer, which is the 
value used to initialize position "pHere".
     
int_lk  * pInt;         /* pointer to the node */
     
cl_link * pHere = NULL; /* position: pointer to link field */
     
pHere   = &pInt->ik;    /* update current position */
 
 
     
As operation cl_link_after() deals only with
link fields, it would be possible to mix 
in the same list different types of objects. For example, if 
pFloat points to a structure that contains a link 
field "fk", the compiler will not issue a warning 
should field "pFloat->fk" be linked into the list. 
This means that these lists are polimorphic, but it also means 
that the compiler typechecking is missing. To include 
typechecking, you must use wrapper macros, or wrapper C++ 
templates.
     
As the compiler has no recollection of what type of elements are 
stored in a container, it is up to the programmer to keep track of 
this important fact. The function has_digit() 
traverses a list that contains numbers; to use them, the 
programmer must resort to macro int_lk_cast(p) to 
transformer a position in the list, which always is a pointer to a 
link field (type cl_link*), into a pointer to the 
whole node (type int_lk*). This is the purpose of 
defining int_lk_cast(p) in header file
intrat.h; the result of unrolling its invocation is to obtain the following code:
     
binder_cast(p, int_lk, ik)
This is, in turn, the invocation of another macro, 
binder_cast(), defined in header file
binder.h, which gets unrolled to yield this:
     
( (int_lk*) (void *)( ((char*)(p)) - (offsetof(int_lk, ik)) ) )
     
This code begets some explanation. Lists are implemented having
link fields pointing to one another. The 
client programmer does not care for those pointer, but for the 
values stored in the list. Hence, there must be a way to change a 
pointer to the link field into a pointer to the complete value. 
The procedure to achieve this goal is to substract from the link 
field pointer the offset at which the link field appear in the 
node to get, for example, pInt from pHere in
Figure 8. This offset can be obtained 
with the C standard macro
offsetof(), defined in header file
<stddef.h>.
     
Why is pointer "p" in macro
SUB_OFFSET() typecasted into a (char *)? Because the C standard clearly states that a "char" always has a size of one, which ensures that the pointer arithmetic used to adjust the 
link field pointer yields a pointer to the first bit in the stored value. Again, the extra parenthesis are there to avoid ambiguity; they are annoying only when looking at the source code after the C preprocessor mangles it, which is seldom needed. 
Looking back into
Figure 8 it is by now clear that all 
these pointer juggling just "adjusts back" the pointer to the link 
field into a pointer into the full stored value.
What is the difference between polimorphism and parametrization? According to the experts [MDC-91], the first is a more general concept, whereas the later is a special type of polymorphism usually called textual polymorphism. For most people, parametrization is a synomim for generic programming. C++ supports parametrization in the form of textual polymorphism; C++ is not a fully polymorphic language.
 
 
     
You can argue that containers implemented linking fields contained 
in their elements are not polymorphic, at least no in the 
traditional ways. However, what matters is whether is it possible 
to write functions that process all the values in a container, 
regardless of the stored value types. An example of such a 
function is the operation cl_print_binder(L,F,B), 
defined in
clist.c. This function receives three parameters:
const clist * LFILE * Fconst binder * B     
It is in the binder where all the information to deal with a value 
stored in a list is collected. The B->size is 
needed to malloc() a new node of the right size. 
B->offset is needed to adjust a link pointer into 
a node pointer. The other fields defined in
binder.h are pointers to functions that provide basic operations like copying, initialization, etc. One of these is the pointer to the print() operation, called int_print() for the 
int_lk type.
     
The implementation of the polymorphic print() 
operation for the list is straight forward. The list traversal 
starts at the first node, obtained invoking operation 
cl_first(). Each time that a stored value needs to be 
printed, the print function is invoked through the 
print() function pointer stored in the binder: 
B->print(). Some heavy pointer typecasting is 
required, but a neat trick is to recall this pointer into a local 
variable, "PRINT", that can be used to clearly make 
the function invocation:
const void (* PRINT) (void *, FILE *) = B->print; /* ... */ PRINT( SUB_OFFSET(p, ofs), F );
     
The main difference between this code and the one used in function 
primes() is that in here we really do not care for 
the type of the values stored in the list, because we just need to 
print them, and that task is carried by operation 
B->print(), whereas in the other case we had to 
typecast back to a node pointer each list position to use it in 
the algorithm. Hence, there are two different programming styles: 
generic programming, when an algorithm works on a list regardless 
of the stored value types, and client programming, when one uses 
the container to store, retrieve and use values.
     
The efficiency of operation cl_print_binder() matches 
that of a hand coded function, because no indirect pointer jumps 
are used. As a matter of fact, variable PRINT is used 
to avoid looking up this pointer in the binder B. As 
both cl_first() and cl_last() are 
inlined macros, the object code generated for this algorithm is as 
fast as the one generated should the
STL library 
were being used in C++. The difference here is that all the 
implementations are sharing the same algorithm, and the price paid 
is just some pointer adjustment used to call the stored value 
print() operation. Nonetheless, let us see why the 
STL would generate faster code than this.
void qsort(
     void *base, size_t nelem, size_t width,
     int (*fcmp) (const void *, const void *) );
 | 
qsort()     
If you delve into the standard C header
<stdlib.h> you will find the
qsort() ordering function, shown in
Figure 9, which receives a function 
pointer, fcmp(), that it uses to compare the elements 
in array base until it is left ordered. The same 
object code will work to sort an array of any type; most compiler 
vendors only provide the object code to qsort(), and 
the programmer just needs to know its specification to use it.
     
However, each time that qsort() compares two 
elements, it needs to incur in a function call when invoking 
fcmp(), which slows down execution. In contrast, the 
generated code for the STL sort algorithm will avoid such call by 
inlining the implementation of fcmp(). From this it 
follows that, when the print() operation is not 
implemented as an inline function, the speed of the STL 
print() method for list would be the same as that of 
operation cl_print_binder().
 
 
     
There is another test that a generic container must pass. It 
should be possible to store a whole container inside another. To 
try this for circular lists, I devised function 
PI_listlist() that receives a string and creates a 
list containing as many list as defined by the numbers in the 
string. For example, for string "3.14159" it should 
print the following list of list:
     
((3 3 3) () (1) (4 4 4 4) (1) (5 5 5 5 5) (9 9 9 9 9 9 9 9 9))
     
The first thing to do, as it was the case for type 
int_lk, is to create a new "list of lists" type, 
called Lint and defined in the test program itself,
c-list.c. What can be stored in a Lint variable is a list, but every Lint can itself be put into a list. Hence, the list of lists "LL" is not of type Lint, 
but just a regular list that just happens to contain Lint values.
     
What is delicate is creating a node to store in LL. 
For example, for digit '3', a list with three integer 
values must be created, and linked into LL, to obtain 
its first value: (3 3 3). This value is 
created using pointer "L" and invoking macro 
binder_create(). However, contrary to what was done 
in function primes(), when creating another 
Lint value to store in LL, the real name 
for the Lint binder is used: 
"&name_binder(Lint,lk)". That is why this binder 
needs to be declared, at the very beginning of test program
c-list.c:
     
declare_binder(Lint, lk);  /* Binder_Lint_ik */
     
The new node that pointer "L" denotes is already 
initialized, because macro binder_create() invokes 
the initialization function for the Lint type: 
Lint_init(). After this all its values are generated 
in a for loop. The code to create and store a value in this inner 
list is similar to that used in function primes():
     
int_lk * pInt;
     
binder_create(Bi, pInt)
What changes is the way to append a value:
     
cl_append(&L->L, &pInt->ik);
Why is there a difference? Recall that "L" points to 
a node that can be linked into the list of lists 
"LL", so that L->L is the list inside 
"L". The ampersand "&" is used 
because operation cl_append() expects a pointer to a 
list. How does the big list LL get its value printed? 
Its enough to invoke:
     
cl_print_binder(&LL, stdout, &Binder_Lint_ik); 
 
 
Values that will be stored in generic lists require to be defined as structures that contain a link field. This field, nonetheless, does not belong to the value, but to the list. Hence, the operations for the stored value should never use nor change a link field. Right? Wrong! Let us see why.
     
Look into the implementation of the initialization operations for 
types int_lk and Lint. Both of these 
operations, int_init() and Lint_init() 
initialize the link field, called "ik" and 
"lk" in each case, invoking the list link constructor 
"cl_link_init()". The destructors for these same 
types also destroy these link fields. Why is that?
The answer is simple: upon creation, a list node is just raw memory. After initialization, all its fields must contain consistent values. Upon creation of a list node, it makes sense to initialize every field within, including those that belong to the container. But never again should it be touched only when destruction should take place. Doing things another way will force to include complicated machinery within both the container and the binder implementation, which is messier.
     
Nonetheless, no other operations but the constructor and 
destructor should change or use the
link field. For example, the copy operations for both int_lk 
and Lint never access the link field: both  Lint_copy() and 
int_copy() skip over it.
     
That was why link fields should be initialized and destroyed by 
the node's constructor and destructor. However, a close 
examination of the implementation of both 
cl_link_init() and cl_link_done() 
reveals that their inline code is empty, or somehow weird when 
macro NDEBUG is not defined. The idea behind this is 
the following: for debugged code, and in the case of the list 
container, no initialization should be done for the 
".next" link field. However, when debugging, it is 
nice to have the compiler issue warnings if things are not done 
the right way.
     
For other containers, for example a tree or a graph, node 
initialization is not as trivial as for the list. For example, 
when using the list container to implement a graph, the graph link 
field would be a list, and the gr_link_init() 
constructor would have to invoke the list initializer 
cl_init(). A similar thing would be required for its 
destructor.
     
The standard C NDEBUG macro is also used in the 
implementation of the list operations cl_link_after() 
and cl_unlink_after(). In the first case, the 
conditional compilation includes code to check whether an already 
linked node is being used, because it contains a non 
NULL value, or whether an unlinked node is being 
decoupled from the container, when the link field is already 
NULL.
 
 
     
How different is to program using the clist generic 
list instead of a STL list, or a hand coded one? Most of the time, 
the client programmer will code algorithms as found in the 
implementation of functions primes() and 
digits() in
Listing 5. The main differences here, should you not want to call then "annoyances", is to define the data type int_lk to include a field, and to invoke macros binder_create() or 
binder_cast() to change "position in the list" pointers into "value" pointers. The rest of the code is similar to that used in regular lists.
     
What if one needs to code a generic algorithm? Examine again the 
implementation of the cl_print_binder() function. To 
come up with such code you need to understand a little bit more 
how binding is done between the container and its contained value: 
the main idea is that link field pointers need to be adjusted back 
to point to the beginning of the each node before using the 
operations stored it the binder table (see
Figure 8). However, most programmers 
will not need to code generic algorithms.
Let us examine this implementation from the optics of the requirements stated before:
char, 
     int, 
     long, etc.) can result in 
     faster code. For most containers both this implementation and 
     the STL would invoke functions to carry out the basic 
     operations of initialization, copying, etc. When dealing with 
     linked containers some speed savings will result because 
     their values need not exist in dynamic memory always.To my eyes, this is success: you will be the judge.
 
 
It would be nice if there was something that could be done with "link" container but not with regular containers. There is one thing: suppose you need a value to be stored in two different container, say a tree and a list. When using linked containers the way to do it is to include two link fields in the node, one for the tree and the other for the list. Can the same thing be done with regular, STL like, containers?
The answer is no, unless references (pointers) to the values are used. This follows from the fact that STL containers keep their stuff in dynamic memory, where they copy or remove values when the insert or delete operations are used. Hence, what gets stored in a container is always a copy of the original value, and when trying to store the same thing in two places the result will be to have two copies, leaving the original untouched: that is not the best for all ocasions.
 
 
     
You can argue, quite convincingly, that using
binder.h is a mess because a lot of new data types should be defined just to link them into a list. I can answer back that it is a good programming practice to encapsulate data types in their own structure, but 
you can conter attack and say that efficiency is not only measured in terms of program speed and time usage, but also in terms of programming easy of use. I could conter argue that when a list is just another service of the operating systems, or the 
software platform, then programmers do things right faster, after they learn how to use generic containers. We could keep argueing back and forth, but at last I would say this: if you read up to here, then maybe the ideas I threw at you are not that bad. 
Give it a try, and
let me know what the experience of using this approach 
teaches you. Remember that most ideas are useless, and many are 
useful just to breach the gap to reach other discoveries.
     
A little macro tweaking with some pointer juggling yields linked 
containers good enough for most applications. It is always better 
to implement them in an Object Oriented Language
[Str-88], but with a little care a 
programmer can build a container library in C
[BSG-92] that is efficient and provides some 
of the better features found in more complicated libraries, like 
the C++
STL. You can 
download all the code in this article from:
     
http://www.di-mare.com/adolfo/p/src/c-list.zip
 
 
Both Ronald Argüello and Carlos Loría took the time to criticize and earlier version of this paper.
This research was made in project 326-98-391 "Polimorfismo uniforme más eficiente", funded by Vicerrectoría de Investigación in the Universidad de Costa Rica. The Escuela de Ciencias de la Computación e Informática has also provided funding for this work.
 
 
| 
     
       [BSG-92]
     
 | 
   Bingham, Bill & Schlintz, Tom & Goslen, Greg:
  OOP Without C++,
  C/C++ User's Journal,
  Vol.10, No.3,
  pp [31, 32, 34, 36, 39, 40],
  March 1992.
   | 
| [DY-91] |  Dos Reis, Anthony & Yun, Li:
   Pointer-Pointers in C,
  C/C++ Users Journal,
  Vol.9 No.3,
  pp [83-84],
  March 1991.
   | 
| [MDC-91] |  Morrison, P. & Dearle, A. & Connor, R. C. H. & Brown, A. L.:
  An Ad Hoc Approach to the Implementation of
  Polymorphism,
  ACM Transactions on Programming Languages and Systems,
  Vol.13 No.3,
  pp [342-371],
  July 1991.
   | 
| [Ste-95] |  Stevens, Al:
  Alexander Stepanov and STL,
  Dr. Dobbs's Journal, No.228,
  pp [118-123],
  March 1995.
  http://www.sgi.com/Technology/STL/drdobbs-interview.html
   | 
| [Str-88] | 
  
  Stroustrup, Bjarne:
  What is Object-Oriented Programming,
  IEEE Software,
  pp [10-20],
  May 1988.
  http://www.research.att.com/~bs/whatis.ps
   | 
 
 
| [-] | Abstract | [-] |  Listing 1: clist.h
   | 
| [-] | A stack class | [-] |  Listing 2: clist.c
   | 
| [-] | The problem with pointers | [-] |  Listing 3: intrat.h
   | 
| [-] | A compromise | [-] |  Listing 4: intrat.c
   | 
| [-] | Drawing a list | [-] |  Listing 5: c-list.c
   | 
| [-] | C implementation | [-] |  Listing 6: binder.h
   | 
| [-] | Using lists |   | 
|
| [-] | Polimorphism |   | 
|
| [-] | Polimorphic functions |   | 
|
| [-] | Lists of lists |   | 
|
| [-] | Link field initialization |   | 
|
| [-] | Evaluation |   | 
|
| [-] | Try doing this! |   | 
|
| [-] | Conclusion |   | 
|
| [-] | Acknowledgments |   | 
|
|   | 
|||
 
          | 
   Bibliography
   | 
||
 
          | 
   Index
   | 
||
 
          | 
   About the author
   | 
||
 
          | 
   About this document
   | 
||
    
       
   | 
  
    Top
    
       
    Index
    
       
    Bottom
   | 
||
 
 
| Adolfo Di Mare: Costarrican Researcher at the Escuela de Ciencias de la Computación e Informática [ECCI], Universidad de Costa Rica [UCR], where he is full professor. Works on Internet and programming technologies. He is Tutor at the Stvdivm Generale in the Universidad Autónoma de Centro América [UACA], where he is Cathedraticum and Academic Consiliarius. Obtained the Licenciatura at UCR, and the Master of Science in Computer Science from the University of California, Los Angeles [UCLA], and the Ph.D. at the Universidad Autónoma de Centro América. | 
| Adolfo Di Mare: Investigador costarricense en la Escuela de Ciencias de la Computación e Informática [ECCI] de la Universidad de Costa Rica [UCR], en donde ostenta el rango de Profesor Catedrático. Trabaja en las tecnologías de Programación e Internet. Es Maestro Tutor del Stvdivm Generale de la Universidad Autónoma de Centro América [UACA], en donde ostenta el rango de Catedrático y funge como Consiliario Académico. Obtuvo la Licenciatura en la Universidad de Costa Rica, la Maestría en Ciencias en la Universidad de California, Los Angeles [UCLA], y el Doctorado (Ph.D.) en la Universidad Autónoma de Centro América. | 
Adolfo Di Mare <adolfo@di-mare.com>
 
 
| Reference: |  Di Mare, Adolfo:
       C Parametrized Lists,
       Revista de Ingeniería,
       Universidad de Costa Rica, 2000.
       
        http://www.di-mare.com/adolfo/p/c-list.htm
   | 
| Internet: |  
       
        http://www.di-mare.com/adolfo/p/c-list.htm
       
  
       
        http://www.di-mare.com/adolfo/p/src/c-list.zip
       
   | 
| Author: | Adolfo Di Mare
  
  <adolfo@di-mare.com>
   | 
| Contact: |  Apdo 4249-1000, San José Costa Rica Tel: (506) 207-4020 Fax: (506) 438-0139  | 
| Revision: | ECCI-UCR, April 1999
      | 
| Visitors: | 
  
    ![]()  | 
  
    ![]()  | 
  
    ![]()  |