Hey all. As usual, my question isn't a simple "malloc kilz prg w/segfault".
I needed a generic list type in C, so I hacked up a simple library that does that.
I parameterized it like is done in C++ templates:
1 2
|
#include "list.h"
list_t (int) numbers;
| |
However, I quickly realized that I'd have to use the GCC
typeof() operator to avoid having to name the element type when using things like
1 2
|
/* Pretty GCC stuff */
int x = *list_first( numbers );
|
/* But I know the type anyway... */
int x = *list_first( int, numbers ); | |
(This code has to be extremely portable -- so GCC extensions are out, especially for just a couple small things like that pretty stuff.)
Next it became apparent that the actual type of the elements in the list are not needed. I could just as easily have coded the above as:
1 2 3 4
|
#include "list.h"
list_t numbers;
...
int x = *list_first( int, numbers );
| |
However, I
liked having the element type in the typename of the list. I like the explicit typing, and it makes it easier for the programmer to remember to avoid abuses like adding differently typed data to the same list.
1 2 3
|
list_t numbers;
list_append( numbers, 12 );
list_append( numbers, "Hello" );
| |
Of course, without the typeof() operator, or true generics (which I'll mention below) it is impossible to guarantee against such behavior...
Also, I still need to
typedef stuff, since you can't use anonymous
structs in argument lists and the like (at least not without complaints).
1 2 3 4 5 6
|
void print_list( list_t (point_t) ls );
...
list_t (point_t) points = {0};
...
print_list( points );
|
test-list.c:39: warning: anonymous struct declared inside parameter list
test-list.c:39: warning: its scope is only this definition or declaration, which
is probably not what you want
test-list.c: In function 'main':
test-list.c:63: error: incompatible type for argument 1 of 'print_list'
... | |
In this case, it probably
is what I want, but I don't want my library code generating error messages, so I have to eschew using the generic type directly in lieu of the aforementioned typedef:
1 2 3 4 5 6 7
|
typedef list_t (point_t) point_list_t;
...
void print_list( point_list_t ls );
...
point_list_t points = {0};
...
print_list( points );
| |
That's a bit disappointing. So ultimately, there doesn't seem to be much point in parameterizing over a type at all. Except, of course, for the same pretty factor that was dismissed earlier.
True generics
can be achieved in C. For example:
generic_list.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
#ifndef DATATYPE
#error You must #define DATATYPE before #including this file!
#endif
/* The structure definition */
typedef struct DATATYPE ## _node_tag
{
struct DATATYPE ## _node_tag* next;
struct DATATYPE ## _node_tag* prev;
DATATYPE data;
}
DATATYPE ## _node_t;
typedef struct
{
DATATYPE ## _node_t** first;
DATATYPE ## _node_t** last;
unsigned long length;
}
DATATYPE ## _list_t;
/* Prototypes */
DATATYPE* DATATYPE ## _list_first( DATATYPE ## _list_t list );
unsigned long DATATYPE ## _list_append( DATATYPE ## _list_t list, DATATYPE data );
etc
#undef DATATYPE
| |
my_prog.c
1 2 3 4 5 6 7 8 9 10 11 12 13
|
#include "generic_list.h"
#define DATATYPE int
#include "generic_list.h"
typedef struct { int x, y; } point_t;
#define DATATYPE point_t
#include "generic_list.h"
...
int_list_t primes;
point_t_list_t points;
...
| |
This, of course, includes all the overhead of regular C++ templates, plus it requires some careful compilation... (And this is all simplified considerably, of course. If I go this route I'll make the #including and the like much less gory for the user
which is likely just myself anyway...)
So here's the question.
What do you gurus think? Should I stick with my simple untyped-generic above? Should I get rid of the "pretty template jazz" which has no apparent point?
Or should I just give in and use true generics?
Remember, I'm using ANSI C89 here. Thanks for reading. :-@