1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | typedef struct { void* ptr; int cap, size; } array; void append(array* a, void* elem, size_t size) { if (a->cap == a->size) { a->cap *= 2; a->ptr = realloc(a->ptr, a->cap * size); } memcpy((char*)a->ptr + size*a->size++, elem, size); } #define append(arr, elem) append(&arr, &elem, sizeof(elem)) void* get(array* a, size_t index, size_t size) { return index < a->size ? (char*)a->ptr + index * size : NULL; } #define get(arr, type, index) (type)get(&arr, index, sizeof(type)) |
1 | _Generic( 'a', char: 1, int: 2, long: 3, default: 0) |
1 | #define increment(var) _Generic(var, int: incrementi, float: incrementf, default: increment_)(var) |
mmozeiko
how about void* and macros together?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 typedef struct { void* ptr; int cap, size; } array; void append(array* a, void* elem, size_t size) { if (a->cap == a->size) { a->cap *= 2; a->ptr = realloc(a->ptr, a->cap * size); } memcpy((char*)a->ptr + size*a->size++, elem, size); } #define append(arr, elem) append(&arr, &elem, sizeof(elem)) void* get(array* a, size_t index, size_t size) { return index < a->size ? (char*)a->ptr + index * size : NULL; } #define get(arr, type, index) (type)get(&arr, index, sizeof(type))
knox
C11 has _Generic selections, which might work.
Blog post with more explanation.
Basically something like:
1 _Generic( 'a', char: 1, int: 2, long: 3, default: 0)
will return 2, because 'a' is an int.
So you can define a single macro to call the correct function:
1 #define increment(var) _Generic(var, int: incrementi, float: incrementf, default: increment_)(var)
It becomes more complex with multiple variable functions, but otherwise code bloat looks minimal from what little testing I've done on godbolt.
1 2 3 4 5 | #define overload __attribute__((overloadable)) void overload inc(float* x) { *x += 1.f } void overload inc(double* x) { *x += 1. } void overload inc(int* x) { *x += 1 } |
mmozeiko
And if you are OK with going clang compiler only, you can do pretty much same overloading as C++, but in C...
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 28 29 30 | struct array arr; arr_init(int, arr, 10); int i; for (i = 0; i < MAX; i++) arr_append(int, arr, i); arr_print(int, arr, print_int); arr_reverse(int, arr); arr_print(int, arr, print_int); int *c; c = arr_get(int, arr, 0); int d = 100; arr_insert(int, arr, 1, d); int e = 100; arr_remove(int, arr, e, cmp_int); arr_pop(int, arr, 5, &c); arr_popl(int, arr, &c); arr_extend(int, arr, arr); struct array copy; arr_copy(int, arr, copy); arr_sort(int, arr, cmp_int); arr_print(int, arr, print_int); for (size_t i = 0; i < arr.count; i++) printf("arr_ptr: %d\n", *arr_ptr(int, arr, i)); |
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 28 29 | // Insert an element in the array at the given index. bool arr_insert(size_t size, struct array *arr, size_t index, void *elem) { if (index <= arr->count) { if (arr->count + 1 > arr->cap) if (!arr_resize_(size, arr, 1)) return false; // for (size_t i = arr->count; i > index; i--) // arr->ptr[i] = arr->ptr[i - 1]; // arr->ptr[index] = elem; // arr->count++; // Move every element after the insert index to the right // one position. void *src = arr_ptr_(size, arr, index); void *dst = arr_ptr_(size, arr, index + 1); memmove(dst, src, size * (arr->count - index)); memmove((char *)arr->ptr + size * index, elem, size); arr->count++; return true; } return false; } |
Shastic
I see Casey is saying on Twitter he found 3 bugs in Clang in 2 weeks, so he's gone back to the MS compiler.
The stdlib should Just Work TM on all platforms, otherwise it shouldn't be allowed to use the name.
1 2 3 4 5 | static inline T* JOIN(A, end)(A* self) { return JOIN(A, back)(self) + 1; } |
1 2 3 4 5 6 7 | #define arr_end_ JOIN(A, end) static inline T* arr_end_(A* self) { return arr_back_(self) + 1; } |
graeme
I think martins approach has the best power-to-weight ratio but... since we're past that...
1 2 3 4 5 6 7 8 9 | void *int_var(int *val) { return val; } bool arr_add(array *arr, void* val) { ... } arr_add(arr, int_var(val)); |
1 | arr_add(arr, int_var(val)); |
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | #define A JOIN(arr, T) struct A { T data; size_t capacity, count; size_t size_type; }; // Return an unchecked pointer to the element in // the array at the given index. #define arr_ptr JOIN(A, ptr) void * arr_ptr(struct A *arr, size_t index) { return (char *)arr->data + index * arr->size_type; } // Return a bounds checked pointer to the element in // the array at the given index. #define arr_at JOIN(A, at) T arr_at(struct A *arr, size_t index) { if (arr->count > 0 && index < arr->count) return arr_ptr(arr, index); else return 0; } // Allocate memory for a new array #define arr_alloc JOIN(A, alloc) bool arr_alloc(struct A *arr, size_t size_type, size_t capacity) { arr->capacity = 0; arr->count = 0; arr->size_type = 0; if (!(arr->data = memmgr_malloc_array(capacity, size_type))) return false; arr->capacity = capacity; arr->size_type = size_type; return true; } // Insert 'data' into the array at the given index. #define arr_insert JOIN(A, insert) bool arr_insert(struct A *arr, const T data, size_t index) { if (!(index <= arr->count)) return false; if (arr->count + 1 > arr->capacity) if (!arr_resize(arr, 1)) return false; memmove(arr_ptr(arr, index + 1), arr_ptr(arr, index), (arr->count - index) * arr->size_type); memmove(arr_ptr(arr, index), data, arr->size_type); arr->count++; return true; } ... etc |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | typedef void* voidp; #define T voidp #include "array_template.h" typedef char *charp; #define T charp #include "array_template.h" typedef struct user_data *udtp; #define T udtp #include "array_template.h" typedef int *intp; #define T intp #include "array_template.h" |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // Using the array of type string struct arr_charp arr; char *value; arr_charp_alloc(&arr, sizeof(value), 3); arr_charp_push_back(&arr, "VALUE_0"); arr_charp_push_back(&arr, "VALUE_1"); value = arr_charp_at(&arr, 0); // Convert the string array to use the void* array to reduce code bloat struct arr_voidp arr; char *value; arr_voidp_alloc(&arr, sizeof(value), 3); arr_voidp_push_back(&arr, "VALUE_0"); arr_voidp_push_back(&arr, "VALUE_1"); value = arr_voidp_at(&arr, 0); |
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | struct array { void *data; size_t capacity, count; size_t size_type; }; // Return an unchecked pointer to the element in // the array at the given index. void * arr_ptr_(struct array *arr, size_t index) { return (char *)arr->data + index * arr->size_type; } #define arr_ptr(type, arr, index) (type*)arr_ptr_(arr, index) // Return a bounds checked pointer to the element in // the array at the given index. void * arr_at_(struct array *arr, size_t index) { if (arr->count > 0 && index < arr->count) return arr_ptr_(arr, index); else return 0; } #define arr_at(type, arr, index) (type*)arr_at_(arr, index) // Allocate memory for a new array bool arr_alloc_(size_t size_type, struct array *arr, size_t capacity) { arr->capacity = 0; arr->count = 0; if (!(arr->data = memmgr_malloc_array(capacity, size_type))) return false; arr->capacity = capacity; arr->size_type = size_type; return true; } #define arr_alloc(type, arr, capacity) arr_alloc_(sizeof(type), arr, capacity) // Insert 'data' in the array at the given index. bool arr_insert_(struct array *arr, void *data, size_t index) { if (!(index <= arr->count)) return false; if (arr->count + 1 > arr->capacity) if (!arr_resize(size_type, arr, 1)) return false; // Move every element from the insert index to the right // one position. memmove(arr_ptr_(arr, index + 1), arr_ptr_(arr, index), (arr->count - index) * arr->size_type); memmove(arr_ptr_(arr, index), &data, arr->size_type); arr->count++; return true; } #define arr_insert(type, arr, data, index) \ arr_insert_(arr, JOIN(type, var(data)), index) |
I've since removed all my generics macros. Why?
You can't use other macros with them.
They are okay as long as you don't try to combine them with other files made using the same method. I found it very confusing in one case.
They are like C++ templates, in that when there is a compiler error, they spew out a huge amount of data, and its hard to find the problem.
Hash including them in lots of places, makes the code base messier and harder to read.
I also found the hash includes for these files need more types when you are using Casey's CamelCase style, instead of snake_case for everything.
Now I think the best way to do generics in C, is good old void *, and a function to check the type.
This is a pain to do by hand, so no one would do it. So I think a tool that inserts these checker functions into the code base, and removes them as well, is the best way to go.
tl;dr Templates bad. Type checking good.