It's a though question, however, I was also interested in the matter when I asked a similar question to yours some time ago.
My conclusions is that if you are going to use vectors (or, using more accurate naming, dynamic arrays) of many different types then it's wasteful to have all those functions vector_##TYPE##_reserve()
, vector_##type##_resize()
, etc... multiple times.
Instead, it is more efficient and clean to have those functions defined only once in a separate .c
file, using your type's size as an extra argument. Those functions prototyped in a separate .h
file. Then the same .h
file would provide macros that generate functions wrappers for your own types, so that you don't see it using the size as an extra argument.
For example, your vector.h
header would contain the following :
/* Declare functions operating on a generic vector type */
void vector_generic_resize(void *vector, size_t size, size_t data_size);
void vector_generic_push_back(void *vector, void *value, size_t data_size);
void *vector_generic_pop_back(void *vector, size_t data_size);
void vector_generic_init(void *vector, size_t size, size_t data_size);
void vector_generic_destroy(void *vector) ; // I don't think data_size is needed here
/* Taken from the example in the question */
#define VECTOR_DEFINITION(type)\
typedef struct {\
size_t size;\
size_t capacity;\
type *data;\
} vector_ ## type ## _t;\
/* Declare wrapper macros to make the above functions usable */
/* First the easy ones */
#define vector_resize(vector, size) vector_generic_resize(vector, size, sizeof(vector.data[0]))
#define vector_init(vector, size) vector_generic_init(vector, size, sizeof(vector.data[0]))
/* Type has to be given as an argument for the cast operator */
#define vector_pop_back(vector, type) (*(type*)(vector_generic_pop_back(vector, sizeof(vector.data[0]))))
/* This one is tricky, if 'value' is a constant, it's address cannot be taken.
I don't know if any better workarround is possible. */
#define vector_push_const(vector, type, value) \
{ \
type temp = value; \
vector_generic_push_back(vector, &temp, sizeof(vector.data[0]));\
}
/* Equivalent macro, but for pushing variables instead of constants */
#define vector_push_var(vector, value) vector_generic_push_back(vector, &value, sizeof(vector.data[0]))
/* Super-macro rediriging to constant or variable version of push_back depending on the context */
#define GET_MACRO(_1,_2,_3,NAME,...) NAME
#define vector_push_back(...) GET_MACRO(__VA_ARGS__, vector_push_const, vector_push_var)(__VA_ARGS__)
/* This macro isn't really needed, but just for homogenity */
#define vector_descroy(vector) vector_generic_destroy(vector)
The functions can then be used as you said in the example you linked, with the significant exception of vector_generic_push_back where unfortunately the type has to be specified each time as an extra macro argument.
So with this solution
- You only have to do
VECTOR_DEFINITION()
within the .c
file, avoiding the risk of declaring it with the same type twice
- The vector library is only existing once in the binary
- The macros can be used elegantly without using the type in their names, except for the pop back macro and the push literal macro.
- If this is a problem you could make the push literal use long long always, it will work but potentially loose efficiency.
- Similarly you could make the pop_back() macro and the
vector_generic_pop_back()
functions not return anything like they does in the C++ language, so that if you do both of those tricks you never need to use the type name explicitly in the macros.
As a reference, the main function you posted in the example that is linked in your question has to be adapted like that :
#include <stdio.h>
#include <stdlib.h>
#include "vector.h"
typedef unsigned int uint;
typedef char* str;
VECTOR_DEFINITION(uint)
VECTOR_DEFINITION(str)
int main()
{
vector_uint_t vector;
vector_init(&vector, 10);
for (unsigned int i = 0; i < vector.size; ++i)
vector.data[i] = i;
for (unsigned int i = 0; i < 10; ++i)
vector_push_back(&vector, i);
/* When pushing back a constant, we *have* to specity the type */
/* It is OK to use C keywords as they are supressed by the preprocessor */
vector_push_back(&vector, unsigned int, 12);
for (unsigned int i = 0; i < vector.size; ++i)
printf("%d ", vector.data[i]);
printf("\n");
vector_destroy(&vector);
vector_str_t sentence;
vector_init(&sentence, 0);
vector_push_back(&sentence, "Hello");
vector_push_back(&sentence, str, "World!"); /* Also possible, less efficient */
vector_push_back(&sentence, "How");
vector_push_back(&sentence, "are");
vector_push_back(&sentence, "you?");
for (unsigned int i = 0; i < sentence.size; ++i)
printf("%s ", sentence.data[i]);
printf("\n");
vector_destroy(&sentence);
return 0;
}