If you're trying to make any kind of generic software, then yes, void* is pretty much essential at some point.
Another approach is to have something like pre-processor abuse (note, this gets ugly in a hurry).
Code:
#ifndef LIST_H
#define LIST_H
#define XLISTNAME(x,y) x ## y
#define LISTNAME(x,y) XLISTNAME(x,y)
#endif
#ifdef LIST_TYPE
struct LISTNAME(ListOf_,LIST_TYPE) {
LIST_TYPE var;
struct LISTNAME(ListOf_,LIST_TYPE) *prev;
struct LISTNAME(ListOf_,LIST_TYPE) *next;
};
void LISTNAME(initListOf_,LIST_TYPE) ( struct LISTNAME(ListOf_,LIST_TYPE) *p, LIST_TYPE *data ) {
p->next = NULL;
p->prev = NULL;
p->var = *data;
}
#else
#error LIST_TYPE is undefined
#endif
#undef LIST_TYPE
Which used as follows
Code:
#define LIST_TYPE int
#include "list.h"
#define LIST_TYPE double
#include "list.h"
int main()
{
return 0;
}
causes the pre-processor to generate two structs and two functions...
Code:
# 1 "list.h" 1
# 10 "list.h"
struct ListOf_int {
int var;
struct ListOf_int *prev;
struct ListOf_int *next;
};
void initListOf_int ( struct ListOf_int *p, int *data ) {
p->next = NULL;
p->prev = NULL;
p->var = *data;
}
# 3 "foo.c" 2
# 1 "list.h" 1
# 10 "list.h"
struct ListOf_double {
double var;
struct ListOf_double *prev;
struct ListOf_double *next;
};
void initListOf_double ( struct ListOf_double *p, double *data ) {
p->next = NULL;
p->prev = NULL;
p->var = *data;
}
# 5 "foo.c" 2
int main()
{
return 0;
}
We now have instantiated rudimentary lists of ints and doubles.
Expanding the header file into a feature-complete list implementation is an exercise for the reader