I’ve attempted to make something similar to C++ and D templated lists in plain C. In this gist there’s the full code of the header file and test driver I’m using. I’ll post some excerpts from the header and all of the test driver.
My main questions are:
-
Am I overusing the preprocessor? Is it OK to put all of the code generation into a macro (given the task at hand)?
-
Does the code conform to the good practices? I haven’t programmed in C for ages. I’m particularly interested in procedure naming – it feels somewhat unfortunate to me.
Here’s the test driver (in full):
#include <assert.h> #include <stdio.h> #include "dlist.h" /* We only use int lists in the following tests. Others are here just to * demonstrate that different types of lists can coexist for as long as their * aliases are different. */ REQUIRE_DLIST(int, int); REQUIRE_DLIST(int, foobar); REQUIRE_DLIST(double, double); IMPLEMENT_DLIST(int, int); IMPLEMENT_DLIST(int, foobar); IMPLEMENT_DLIST(double, double); //IMPLEMENT_DLIST(int, int); /* This will cause an error if uncommented. */ int main(int argc, char** argv) { struct dlist_int *list = create_dlist_int(); if (list == NULL) { puts("Failed to create a list."); exit(EXIT_FAILURE); } else { puts("Successfully created a list."); } /* Test that we can push to the list. */ push_dlist_int(list, 1); push_dlist_int(list, 2); push_dlist_int(list, 3); struct dlist_elem_int *cur = list->first; assert(cur->value == 3); cur = cur->next; assert(cur->value == 2); cur = cur->next; assert(cur->value == 1); puts("Successfully prepended three values to the list."); /* Test that we can push to the end of the list. */ push_back_dlist_int(list, 10); push_back_dlist_int(list, 20); push_back_dlist_int(list, 30); cur = list->last; assert(cur->value == 30); cur = cur->prev; assert(cur->value == 20); cur = cur->prev; assert(cur->value == 10); puts("Successfully appended three values to the list."); /* Test that we can pop from both ends of the dlist. */ assert(pop_dlist_int(list) == 3); assert(pop_back_dlist_int(list) == 30); assert(dlist_int_length(list) == 4); destroy_dlist_int(list, NULL, NULL); puts("Done."); }
This is the REQUIRE_DLIST
macro:
#define REQUIRE_DLIST(type, alias) \ /* Base structs. */ \ struct dlist_##alias { \ struct dlist_elem_##alias *first, *last; \ }; \ struct dlist_elem_##alias { \ struct dlist_elem_##alias *prev, *next; \ type value; \ }; \ /* Base operations. */ \ extern struct dlist_##alias *create_dlist_##alias(void); \ extern void destroy_dlist_##alias( \ struct dlist_##alias *list, \ void (*destroyer)(type, void*), \ void *arg); \ extern void simple_destroy_dlist_##alias( \ struct dlist_##alias *list, \ void (*destroyer)(type)); \ extern size_t preallocate_dlist_##alias(struct dlist_##alias *list, \ size_t len); \ /* Copying. */ \ extern struct dlist_##alias *copy_dlist_##alias(struct dlist_##alias *list, \ type (*copier)(type, void *), void *arg); \ extern struct dlist_##alias *simple_copy_dlist_##alias(struct dlist_##al ias *list, \ type (*copier)(type)); \ /* Insertion. */ \ extern int push_dlist_##alias(struct dlist_##alias *list, type value); \ extern int push_back_dlist_##alias(struct dlist_##alias *list, type valu e); \ extern size_t append_dlist_##alias( \ struct dlist_##alias *append_to, \ struct dlist_##alias *tail); \ extern size_t prepend_dlist_##alias(struct dlist_##alias *prepend_to, \ dlist_##alias *head); \ extern struct dlist_##alias \ *cat_dlists_##alias(struct dlist_##alias *left, \ struct dlist_##alias *right); \ /* Deletion and popping. */ \ extern type pop_dlist_##alias(struct dlist_##alias *list); \ extern type pop_back_dlist_##alias(struct dlist_##alias *list); \ extern size_t pop_n_dlist_##alias(struct dlist_##alias *list, size_t n); \ extern size_t pop_back_n_dlist_##alias(struct dlist_##alias *list, size_ t n); \ extern void clear_dlist_##alias( \ struct dlist_##alias *list, \ void (*destroyer)(type, void*), \ void*); \ extern void simple_clear_dlist_##alias( \ struct dlist_##alias *list, \ void (*destroyer)(type)); \ /* Information retrieval. */ \ extern size_t dlist_##alias##_length(struct dlist_##alias *list); \ extern type dlist_##alias##_first(struct dlist_##alias *list); \ extern type dlist_##alias##_last(struct dlist_##alias *list); \ extern int dlist_##alias##_empty(struct dlist_##alias *list); \ /* End of REQUIRE_DLIST */
And these are some parts of IMPLEMENT_DLIST
macro, hopefully enough to illustrate the point and the method:
#define IMPLEMENT_DLIST(type, alias) \ /* Base operations - creation. */ \ struct dlist_##alias \ *create_dlist_##alias(void) { \ struct dlist_##alias *res = malloc(sizeof(struct dlist_##alias)) ; \ if (res == NULL) return NULL; \ res->first = NULL; \ res->last = NULL; \ return res; \ } \ struct dlist_elem_##alias \ *create_dlist_elem_##alias(type value) { \ struct dlist_elem_##alias *res = malloc(sizeof(struct dlist_elem _##alias)); \ if (res == NULL) return NULL; \ res->prev = res->next = NULL; \ res->value = value; \ return res; \ } \ struct dlist_elem_##alias \ *create_uninit_dlist_elem_##alias(void) { \ struct dlist_elem_##alias *res = malloc(sizeof(struct dlist_elem _##alias)); \ if (res == NULL) return NULL; \ res->prev = res->next = NULL; \ return res; \ } \ /* Base operations - clean up. */ \ void \ destroy_dlist_##alias(struct dlist_##alias *list, void (*destroyer)(type , void*), \ void *arg) { \ struct dlist_elem_##alias *cur = list->first; \ while (cur != NULL) { \ struct dlist_elem_##alias *next = cur->next; \ if (destroyer != NULL) \ destroyer(cur->value, arg); \ free(cur); \ cur = next; \ } \ free(list); \ } \ void \ simple_destroy_dlist_##alias(struct dlist_##alias *list, void (*destroye r)(type)) { \ struct dlist_elem_##alias *cur = list->first; \ while (cur != NULL) { \ struct dlist_elem_##alias *next = cur->next; \ if (destroyer != NULL) \ destroyer(cur->value); \ free(cur); \ cur = next; \ } \ free(list); \ } \ /* Insertion. */ \ int \ push_dlist_##alias(struct dlist_##alias *list, type value) \ { \ struct dlist_elem_##alias *new_first = create_dlist_elem_##alias(value); \ if (new_first == NULL) return 0; \ if (list->first == NULL) { \ list->first = list->last = new_first; \ } else { \ struct dlist_elem_##alias *old_first = list->first; \ old_first->prev = new_first; \ list->first = new_first; \ new_first->next = old_first; \ } \ return 1; \ } \ int \ push_back_dlist_##alias(struct dlist_##alias *list, type value) \ { \ struct dlist_elem_##alias *new_last = create_dlist_elem_##alias(value); \ if (new_last == NULL) return 0; \ if (list->last == NULL) { \ list->first = list->last = new_last; \ } else { \ struct dlist_elem_##alias *old_last = list->last; \ old_last->next = new_last; \ list->last = new_last; \ new_last->prev = old_last; \ } \ return 1; \ } \ /* Deletion. */ \ type \ pop_dlist_##alias(struct dlist_##alias *list) \ { \ struct dlist_elem_##alias *first = list->first; \ type res = first->value; \ if (first == list->last) { \ free(first); \ list->first = list->last = NULL; \ } else { \ struct dlist_elem_##alias *second = first->next; \ second->prev = NULL; \ list->first = second; \ free(first); \ } \ return res; \ } \ type \ pop_back_dlist_##alias(struct dlist_##alias *list) \ { \ struct dlist_elem_##alias *last = list->last; \ type res = last->value; \ if (list->first == last) { \ list->first = list->last = NULL; \ free(last); \ } else { \ struct dlist_elem_##alias *second = last->prev; \ second->next = NULL; \ list->last = second; \ free(last); \ } \ return res; \ } \