The most generic way to program in C is still: void*
(albeit _Generic
, which undoubtly has its uses, but do not see it as a hammer and everything else as a nail).
e.g.
struct array_t {
void *data; //generic data pointer
size_t type_size; //sizeof(type)
size_t size; //number of elements in data
size_t capacity; //allocated size (in number of elements)
};
Then, with the newly introduced member type_size
, you have every bit of information you need to reallocate and copy the various data arrays. You could also extend the generic array with numerous function pointers to specify the appropriate functions to allocate, free, copy or move the data (default would be malloc
, realloc
, free
, memcpy
, memmove
).
Simply put, give up type safety (use macros) or use a bunch of functions which do all the same thing but differ in name and signature or implement only a generic version, by using void*
as type parameter and decide via the field type_size
on how many bytes to allocate or move around.
e.g.
array_t* array_append(array_t *dst, const array_t *src)
{
if (dst->type_size != src->type_size) { //incompatible types
return NULL;
}
if (dst->capacity < dst->size + src->size) {
dst->data = realloc(dst->data, (dst->capacity + src->size) * type_size);
dst->capacity += src->size;
}
memcpy(
dst->data + (dst->size * dst->type_size),
src->data,
src->size * src->type_size
);
dst->size += src->size;
return dst;
}
or
//param 'num' interpreted as number of elements in 'src',
//therefore the length in bytes of src would be: num * sizeof(type)
//num bytes could also be used here,
//but then, num would have to be a multiple of dst->type_size,
//therefore num elements recommended
array_t* array_append(array_t *dst, const void *src, size_t num);
The only useful macros here would be for an array initializer or member access (subscript, dereference, ...), other than that no other macro or inline function or _Generic
voodoo needed (remember, it's being said that macros are evil).
More sophisticated version:
//each type has its own class
struct array_class_t {
size_t type_size;
//following sizes, either interpreted as num bytes or num elements,
//but be consistent, do not mix!
void* (*allocate)(size_t);
void* (*reallocate)(void*, size_t);
void (*free)(void*);
void (*copy)(void*, const void*, size_t);
void (*move)(void*, const void*, size_t);
};
struct array_t {
struct array_class_t *class_;
void *data;
size_t size;
size_t capacity;
};
Note: above code (pointer arithmetic) does not conform to the C Standard, it uses an GCC extension, see: Arithmetic on void- and Function-Pointers. To conform to the C Standard, the pointers would have to be cast to unsigned char*
(or to be used in the first place, e.g. instead of void*
).