I am currently making my own dynamic array and I have function that will append to it but I want to append my structure or pointer so I need to modify function arguments and not the function name. This is my function that will append items into ph_arr.
#define ph_array_(type, name) \
struct name { \
type* data; size_t used_size, alloc_size; \
} __attribute__((__packed__))
void ph_array_append_(struct ph_array_void_* ph_arr, size_t data_size, void* items, size_t count);
I want to use the macro (ph_array_append
) like this
ph_array_append(ph_arr1, ph_arr2);
// or
const int data[] = { 1, 2, 3, 4 };
ph_array_append(ph_arr1, data, 4);
I can't call the function because I need to make different arguments for the same function, so I tried to add the function with tweaked arguments but it didn't work, also I tried to add only the tweaked arguments but it didn't work either.
Is it possible to do this or do I have to change it all? If yes can you recommend me some solution?
CodePudding user response:
Here's one idea for how you could use a _Generic
to call different functions depending on the second argument, which is either a pointer to a struct ph_array_##name
or a pointer to the type the struct holds.
Note: I'm using the gcc extension __VA_OPT__
.
#include <stdio.h>
#define ph_array_(type, name) \
struct ph_array_##name { \
type* data; size_t used_size, alloc_size; \
} __attribute__((__packed__));
// define two structs
ph_array_(void,void)
ph_array_(int,int)
// 2 functions for each struct:
void ph_array_append_void_void(struct ph_array_void *a1, void* items, size_t count, size_t data_size) {
puts("ph_array_append_void_void");
}
void ph_array_append_void_ph_array_void(struct ph_array_void *a1, struct ph_array_void *a2) {
puts("ph_array_append_void_ph_array_void");
}
void ph_array_append_int_int(struct ph_array_int *a1, int* items, size_t count) {
puts("ph_array_append_int_int");
}
void ph_array_append_int_ph_array_int(struct ph_array_int *a1, struct ph_array_int *a2) {
puts("ph_array_append_int_ph_array_int");
}
// and the _Generic that selects which function to call:
#define ph_array_append(A, S, ...) \
_Generic((S), \
void* : ph_array_append_void_void, \
struct ph_array_void* : ph_array_append_void_ph_array_void, \
int* : ph_array_append_int_int, \
struct ph_array_int* : ph_array_append_int_ph_array_int) \
((A),(S) __VA_OPT__(,) __VA_ARGS__)
int main(void) {
struct ph_array_int foo;
struct ph_array_void bar;
ph_array_append(&foo, &foo);
ph_array_append(&foo, (int*)NULL, 0); // no data_size
ph_array_append(&bar, &bar);
ph_array_append(&bar, (void*)NULL, 0, 10); //data_size needed
}
Output:
ph_array_append_int_ph_array_int
ph_array_append_int_int
ph_array_append_void_ph_array_void
ph_array_append_void_void
CodePudding user response:
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*
).