Here's an option that is very flexible, but requires a lot of work.
In the node list, save the data pointer as void * :
struct node { void *data; struct node *next; };
Then you will create a set of functions for each type that handles tasks such as comparing, assigning, duplicating, etc.:
// create a new instance of the data item and copy the value // of the parameter to it. void *copyInt(void *src) { int *p = malloc(sizeof *p); if (p) *p = *(int *)src; return p; } void assignInt(void *target, void *src) { // we create a new instance for the assignment *(int *)target = copyInt(src); } // returns -1 if lhs < rhs, 0 if lhs == rhs, 1 if lhs > rhs int testInt(void *lhs, void *rhs) { if (*(int *)lhs < *(int *)rhs) return -1; else if (*(int *)lhs == *(int *)rhs) return 0; else return 1; } char *intToString(void *data) { size_t digits = however_many_digits_in_an_int(); char *s = malloc(digits + 2); // sign + digits + terminator sprintf(s, "%d", *(int *)data); return s; }
Then you can create a list type with pointers to these functions, for example
struct list { struct node *head; void *(*cpy)(void *);
Then, when you pass the list to an insert or search operation, you call the functions from the list object:
void addToList(struct list *l, void *value) { struct node *new, *cur = l->head; while (cur->next != NULL && l->test(cur->data, value) <= 0) cur = cur->next; new = malloc(sizeof *new); if (!new) {
By delegating type-related operations to separate functions that are called using function pointers, you now have a list structure that can hold values of any type. To add support for new types, you only need to implement the new functions of copy, destination, toString, etc. For this new type.
There are flaws. Firstly, you cannot use constants as parameters of a function (for example, you cannot do something simple, for example addToList(&myIntList, 1); ) - first you must assign everything to a variable and pass the address of the variable (which is why you you need to create new instances of the data item when you add it to the list, if you just assigned an address to a variable, each item in the list will point to the same object, which may no longer exist depending on the context).
Secondly, you complete a lot of memory management; you are not just creating a new instance of the node list, but you must also create a new instance of the data item. Remember to free the item before freeing the node. Then you create a new instance of the row every time you want to display data, and you must remember to free this row when you are done with it.
Finally, this solution gives type safety directly from the window and into the oncoming traffic (after its ignition). Delegate functions expect you to keep types straight; there is nothing stopping you from passing the address of the double variable to one of the int processing functions.
Between memory management and the fact that you have to make a function call for almost every operation, performance will suffer. This is not a quick fix.
Of course, this assumes that each item in the list is the same type; if you want to store elements of different types in one list, then you will need to do something else, for example, associate functions with each node, and not with a general list.