I think the overwhelmingly better approach for C is codegen here. Better ergonomics, tab completion, less error prone, etc. As long as your codegen is solid!
> Many vector types include a capacity field, so that resizing on every push can be avoided. I do not include one, because simplicity is more important to me and realloc often does this already internally. In most scenarios, the performance is already good enough.
I think this is the wrong decision (for a generic array library).
The post is more of a quick-n-dirty (and rather trivial) proof of concept as the code includes only sporadical checks for allocation errors and then adds a hand-wavy disclaimer to improve it as needed.
E.g. in production code this
if (!vec_ptr) // memory out
abort();
for (int i = 0; i < 10; i++)
vec_push(int, &vec_ptr, i);
should really be
if (!vec_ptr) // memory out
abort();
for (int i = 0; i < 10; i++)
if (! vec_push(int, &vec_ptr, i))
abort();
It's amazing how many people try to write generic containers for C, when there is already a perfect solution for that, called C++. It's impossible to write generic type-safe code in C, and this version resorts to using GCC extensions to the language (note the ({…}) expressions).
For those afraid of C++: you don't have to use all of it at once, and compilers have been great for the last few decades. You can easily port C code to C++ (often you don't have to do anything at all). Just try it out and reassess the objections you have.
Many C programmers need proper generic programming mechanisms (perhaps something like Zig's comptime) in C, but macros are the worst possible approach, and they don't want to switch to a different language like C++. As a result, they struggle with these issues. This is what I think the standardization committee should focus on, but instead, they introduced _Generic.
I made a pretty convenient C vector library a while back that lets you use the [] operator directly on the vector pointers: https://github.com/Mashpoe/c-vector
From my experience, trying to make C type safe is counter productive.
It's perfectly possible to do generic vectors in C without twisting the language. This implementation isn't as safe as alternatives in other languages, but plays well on C's strengths.
An important property of C++ templates is that their instantiations are de-duplicated at link-time.
If you have two dependencies in C which both use the same generic container macros, and both instantiate vec(int) you'll fail-out with redefinition errors.
Array_Free is very simple, and Array_Grow is barely more complicated (however I wrote it off the cuff, so of course it could still be wrong). Both of these mainly exist just to keep stdlib.h out of the header.
You might want to hide the size naming policy behind a macro:
#define ARRAY_LENGTH(S) (S##_length)
But it'd often be shorter just to type the name out. (On the other hand, if attempting to fully productize this, maybe it'd be nice to let the consumer customize the naming convention - and now you'd have the option. ARRAY_LENGTH would hide the details, you'd fix up ARRAY_ADD (etc.) to use it, and now you could have the size variable called arraySize (or whatever) and everything would fall into line.)
For a full implementation you'll probably also need a way of generating a static array. (I mainly found myself needing this for test code, which uses globals for convenience; most arrays I create normally are locals, or parts of structs.)
You'll also need a parameters list for use in a function declaration or definition, and a macro that expands to all 3 variables.
#define ARRAY_PARAMS(T,S) T *S,size_t S##_length,size_t S##_capacity
#define ARRAY_ARG(S) S,S##_length,S##_capacity
Like then you might have a function that takes a pointer to an "array":
(I found this cropped up often enough that I needed the macro, but it was less common than I thought.)
There's more you can do, but the above is the long and the short of it.
This might all look terrible - or perhaps it sort of looks OK, but you're just not sure that it would actually work - but I've used this in a prototype project and thought it worked out well. (I mainly do C++ nowadays, but I had a good stint of C before that. So even if I...
20 comments
[ 3.2 ms ] story [ 42.2 ms ] threadI think this is the wrong decision (for a generic array library).
E.g. in production code this
should really be but it doesn't really roll of the tongue.For those afraid of C++: you don't have to use all of it at once, and compilers have been great for the last few decades. You can easily port C code to C++ (often you don't have to do anything at all). Just try it out and reassess the objections you have.
His code is here: https://github.com/drh/cii
A neat project that was posted here a while back uses it: https://x.com/kotsoft/status/1792295331582869891
https://nothings.org/stb_ds
All of the methods are implemented on the base Vec type and have a small wrapper which casts/assets the type of the things you are trying to push.
It's perfectly possible to do generic vectors in C without twisting the language. This implementation isn't as safe as alternatives in other languages, but plays well on C's strengths.
https://github.com/codr7/hacktical-c/tree/main/vector
If you have two dependencies in C which both use the same generic container macros, and both instantiate vec(int) you'll fail-out with redefinition errors.
For a full implementation you'll probably also need a way of generating a static array. (I mainly found myself needing this for test code, which uses globals for convenience; most arrays I create normally are locals, or parts of structs.)
You'll also need a parameters list for use in a function declaration or definition, and a macro that expands to all 3 variables.
Like then you might have a function that takes a pointer to an "array": And you call it like this: (I found this cropped up often enough that I needed the macro, but it was less common than I thought.)There's more you can do, but the above is the long and the short of it.
This might all look terrible - or perhaps it sort of looks OK, but you're just not sure that it would actually work - but I've used this in a prototype project and thought it worked out well. (I mainly do C++ nowadays, but I had a good stint of C before that. So even if I...