> vector-like data type for different types
When using C, if you want something from a higher-level language, usually you'd just do what that language's runtime does internally. So for a vector of heterogeneous objects, you could do a linked-list of pointers to the data objects. Something like:
struct VecItm { struct VecItm next; void *data; size_t data_len; char objTypeId; };
Is it more work? Yes. But at this point I would stop and consider if I really need a heterogeneous collection. Do we really need to store a list of objects with arbitrary sizes? What if we have 3 types of objects, in 3 arrays, and we store index into those arrays?
struct Foo { unsigned byte x; };
struct Bar { unsigned word x; };
struct Baz { unsigned long x; };
struct Foo foos[256];
struct Bar bars[256];
struct Baz bazzes[256];
// objTypeId: 0 = foo, 1 = bar, 2 = baz
struct VecItm { struct VecItm next; unsigned int idx; char objTypeId; };
Or what if we can do something even better? What if Foo, Bar, and Baz all have a max size that isn't too wildly different? Can we store them all in an array directly?
// objTypeId: 0 = foo (x contains 8 bits), 1 = bar (x contains 16 bits), 2 = baz (x contains 32 bits)
struct VecItm { unsigned long x; char objTypeId; };
struct VecItm vecItms[256];
> Its “macro” system is just a disgusting hack
It's really not that bad. It can result in spaghetti-code where your macros are using other macros and they're spread all over a header file. But if you use them surgically, only when needed, they don't cause much trouble.
> I could just as well write a sed script
The benefit of C macros over sed is if you use some language-aware tooling like an IDE (such as CLion), it will syntax-check and type-check your macros.