[HN Gopher] Generic Containers in C: Vec
___________________________________________________________________
Generic Containers in C: Vec
Author : uecker
Score : 51 points
Date : 2025-07-23 07:04 UTC (3 days ago)
(HTM) web link (uecker.codeberg.page)
(TXT) w3m dump (uecker.codeberg.page)
| uecker wrote:
| Towards safe containers in C.
| rwmj wrote:
| Here's a generic vector used in real world code:
| https://gitlab.com/nbdkit/nbdkit/-/blob/master/common/utils/...
| senderista wrote:
| I doubt I'd want to use a dynamic array in C without a custom
| allocator.
| teo_zero wrote:
| You don't need an explicit allocator: you can create an empty
| object and vec_push() does the magic of (re)allocating the
| memory when needed.
|
| Instead, what is missing is an automatic deallocator, one
| that's automatically called when the variable goes out of
| scope. For example: { vec(int) v={}; /*
| no extra room allocated */ vec_push(v,1); /* space
| allocated */ ... use v ... } /* here the space is
| dellocated, then v is released */
|
| This example doesn't use the same definition of vec as TFA, but
| something more similar to 'span' by the same author.
| uecker wrote:
| And you could easily add a version with a custom allocator if
| you need it.
| cv5005 wrote:
| And if I want a vec(int *)? These token pasting 'generic' macros
| never work for non-trivial types.
| teo_zero wrote:
| Correct, complex types must be typedef'd. At least, until c2y
| integrates _Record as per N3332:
| https://thephd.dev/_vendor/future_cxx/papers/C%20-%20_Record...
| hyperbolablabla wrote:
| 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!
| uecker wrote:
| Why? I do not find the ergonomics bad.
|
| It is also not clear how you get tap completion with code
| generation. But you could also get tab completion here,
| somebody just has to add this to the tab completion logic.
| teo_zero wrote:
| Why do you need to pass the type to vec_push? Can't T be replaced
| by typeof(v->data[0]) ?
| uecker wrote:
| It could. I like things being spelled out explicitly. Otherwise
| I would probably not use C.
| camel-cdr wrote:
| Here is my version of this concept, I tried to keep it as simple
| as possible: https://github.com/camel-
| cdr/cauldron/blob/main/cauldron/str...
| petters wrote:
| > 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).
| tialaramex wrote:
| > realloc often does this already internally
|
| Is Martin claiming that realloc is "often" maintaining a O(1)
| growable array for us?
|
| That's what the analogous types in C++ or Rust, or indeed Java,
| Go, C# etc. provide.
| uecker wrote:
| No, I claim that the performance of realloc is good enough
| for most use cases because it also does not move the memory
| in case there is already enough space left.
|
| I then mention that for other use cases, you can maintain a
| capacity field only in the part of the code where you need
| this.
|
| Whether this is the right design for everybody, I do not
| know, but so far it is what I prefer for myself.
| tialaramex wrote:
| I think the claim that it's good enough for "most use
| cases" to have an O(n) growable array container needs some
| serious backing data.
| uecker wrote:
| If I have some time, I will do some benchmarking.
|
| In all my current code where I tried it makes no
| noticeable difference, and I am not a fan of premature
| optimization. But then, I could always switch to the
| alternative API.
| swinglock wrote:
| Popular allocators will indeed grow your allocation in-
| place without moving when possible. This is essentially
| the same as if you'd tracked it yourself in your vector
| and grown it once in a while, though it will work with
| bytes instead of number of items. See for instance the
| size classes in jemalloc at
| https://jemalloc.net/jemalloc.3.html. If you ask for 1
| byte, you actually have 8 bytes, so realloc within the
| same size class will be cheap compared to actually
| moving.
| uecker wrote:
| Exactly! Why would I want to add my own memory management
| logic on top of the memory management logic that already
| exist.
|
| One valid reason might be that I can't rely on realloc
| not be poor, but then I would rather use my own special
| allocation function. Other valid reasons would be to have
| very precise control or certain guarantees, but then I
| would prefer a different interface. In any case, I do not
| think that this logic belongs into my vector. But it is
| also possible that I change my mind on this...
| im3w1l wrote:
| I think it's a very interesting design choice. I haven't
| read the code, so maybe you already thought of this, but
| one idea that comes to mind is that instead of reallocing
| new_size, you realloc f(new_size) where f is some function
| that rounds up to discrete steps. This should ensure good
| asymptotics as realloc can then realize that the requested
| allocation size is identical to the current one and nothing
| needs to be done.
|
| However one possible issue is if someone pushes and pops
| repeated just at the boundary where f increases in value.
| To address that you would have to use more advanced
| techniques, and I think "cheat" by inspecting internal
| structures of the allocator.
|
| Edit: malloc_usable_size could be used for this purpose I
| think.
| uecker wrote:
| Yes!
|
| I try do this here (this code is not tested and may not
| be up-to-date):
| https://github.com/uecker/noplate/blob/main/src/vec.h#L30
|
| The issue with the boundary is what I meant with
| hysteresis in the article.
| ethan_smith wrote:
| Without a capacity field, each push operation potentially
| triggers a realloc, causing O(n) copying and possible memory
| fragmentation - especially problematic for large vectors or
| performance-critical code.
| eps wrote:
| 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();
|
| but it doesn't really roll of the tongue.
| uecker wrote:
| If all you do is call abort anyway, you do not need an
| interface that makes you test for errors.
| johnisgood wrote:
| If this is in a library code, then I tend to disagree. As an
| user of a library, I would rather be able to handle errors the
| way I want, I do not want the library to decide this for me, so
| just return an error value, like "VEC_ERR_NOMEM", or whatever.
| gsliepen wrote:
| 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.
| serbuvlad wrote:
| My problem with C++, and maybe this is just me, is RAII.
|
| Now, Resource Aquisition Is Initialization is correct, but the
| corollary is not generally true, which is to say, my variable
| going out of scope does not generally mean I want to de-aquire
| that resource.
|
| So, sooner or later, everything gets wrapped in a reference
| counting smart pointer. And reference counting always seemed to
| me to be a primitive or last-resort memory managment strategy.
| secondcoming wrote:
| If you want to take back manual control, use the release()
| function
| gpderetta wrote:
| Your problem is not with RAII, but with reference counting,
| which you correctly identified should be the last resort, not
| the default; at least for the applications typically written
| in C++.
| Levitating wrote:
| Why should reference counting be a last resort?
| spacechild1 wrote:
| > my variable going out of scope does not generally mean I
| want to de-aquire that resource.
|
| But it does! When an object goes out of scope, nobody
| can/shall use it anymore, so of course it should release its
| (remaining) resources. If you want to hold on the object, you
| need to revisit its lifetime and ownership, but that's
| independent from RAII.
| Lvl999Noob wrote:
| Instead of reference counting, consider having two types. An
| "owner" type which actually contains the resource and the
| destructor to dequire the resource. And "lender" types which
| contain a reference (a pointer or just logically (e.g., an fd
| can just be copied into the lender but only closed by the
| owner) to the resource which don't dequire on destruction.
|
| Same thing as what Rust does with `String` and `str`.
| uecker wrote:
| Except that I find C++ far from being perfect. In fact, I
| switched from C++ to C (a while ago) to avoid its issues and I
| am being much happier I also find my vec(int) much nicer.
|
| In fact, we are at the moment ripping out some template code in
| a C code base which has some C++ for cuda in it, and this one
| file with C++ templates almost doubles the compilation time of
| the complete project (with ~700 source files). IMHO it is
| grotesque how bad it is.
| cyber1 wrote:
| 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.
| sirwhinesalot wrote:
| The most insulting thing about _Generic is the name. Really?
| _Generic? For a type-based switch with horrific syntax? What
| were they thinking...
|
| That said, generic programming in C isn't that bad, just very
| annoying.
|
| To me the best approach is to write the code for a concrete
| type (like Vec_int), make sure everything is working, and then
| do the following:
|
| A macro Vec(T) sets up the struct. It can then be wrapped in a
| typedef like typedef Vec(int) Vec_i;
|
| For each function, like vec_append(...), copy the body into a
| macro VEC_APPEND(...).
|
| Then for each relevant type T: copy paste all the function
| declarations, then do a manual find/replace to give them some
| suffix and fill in the body with a call to the macro (to avoid
| any issues with expressions being executed multiple times in a
| macro body).
|
| Is it annoying? Definitely. Is it unmanageable? Not really.
| Some people don't even bother with this last bit and just use
| the macros to inline the code everywhere.
|
| Some macros can delegate to void*-based helpers to minimize the
| bloating.
|
| EDIT: I almost dread to suggest this but CMake's configure_file
| command works great to implement generic files...
| cyber1 wrote:
| Hey, I understand you and know this stuff well, having worked
| with it for many years as a C dev. To be honest, this isn't
| how things should generally be done. Macros were invented for
| very simple problems. Yes, we can abuse them as much as
| possible (for example, in C++, we discovered SFINAE, which is
| an ugly, unreadable technique that wasn't part of the
| programming language designer's intent but rather like a joke
| that people started abusing), but is it worth it?
| ioasuncvinvaer wrote:
| username checks out
| uecker wrote:
| There are less annoying ways to implement this in C. There
| are at least two different common approaches which avoid
| having macro code for the generic functions:
|
| The first is to put this into an include file
| #define type_argument int #include <vector.h>
|
| Then inside vector.h the code looks like regular C code,
| except where you insert the argument. foo_ ##
| type_argument ( ... )
|
| The other is to write generic code using void pointers or
| container_of as regular functions, and only have one-line
| macros as type safe wrappers around it. The optimizer will be
| able to specialize it, and it avoids compile-time explosion
| of code during monomorphization,
|
| I do not think that templates are less annoying in practice.
| My experience with templates is rather poor.
| sirwhinesalot wrote:
| Those techniques being less annoying is highly debatable
| ;). Working with void* is annoying, header includes look
| quite ugly with the ## concatenation everywhere or even a
| wrapper macro. It also gets much worse when you need to
| customize the suffix (because type_argument is char* or
| whatever).
|
| Sometimes the best option is an external script to
| instantiate a template file.
| uecker wrote:
| It may be debatable, but I would say C++'s template
| syntax is not nicer. I do not think working with void
| pointers is annoying, but I also prefer the container_of
| approach. The ## certainly has the limitation that you
| need to name things first, but I do not think this much
| of a downside.
|
| BTW, here is some generic code in C using a variadic
| type. I think this quite nice.
| https://godbolt.org/z/jxz6Y6f9x
|
| Running a program for meta programming are always a
| possibility, and I would agree that sometimes the best
| solution.
| 1718627440 wrote:
| I don't think T ## _foo (T foo, ...)
|
| is that much different from <T>::foo (T
| foo, ...)
|
| Same for: foo (Object * a)
|
| vs: foo (void * a)
| sparkie wrote:
| An idea I had was to implement a FUSE filesystem for
| includes, so instead of the separate `#define
| type_argument` (and `#undef type_argument` that would need
| to follow the #include), we could stick the type argument
| in the included filename. #include
| <vector.h(int32_t)> #include <vector.h(int64_t)>
|
| The written `vector.h(type_argument)` file could just be a
| regular C header or an m4 file which has `type_argument` in
| its template. When requesting `vector.h(int32_t)` the FUSE
| filesystem would effectively give the output of calling
| `gcc -E` or `m4` on the template file as the content of the
| file being requested.
|
| Eg, if `vector.h(type_argument)` was an m4 file containing:
| `#ifndef VECTOR_'type_argument`_INCLUDED' `#define
| VECTOR_'type_argument`_INCLUDED' typedef
| struct `vector_'type_argument { size_t length;
| type_argument values[]; } `vector_'type_argument;
| ... #endif
|
| Then `m4 -D type_argument=int32_t vector.h(type_argument)`
| gives the output: #ifndef
| VECTOR_int32_t_INCLUDED #define
| VECTOR_int32_t_INCLUDED typedef struct
| vector_int32_t { size_t length;
| int32_t values[]; } vector_int32_t;
| ... #endif
|
| But the idea is to make it transparent so that existing
| tools just see the pre-processed file and don't need to
| call `m4` manually. We would need to mount each include
| directory that uses this approach using said filesystem.
| This shouldn't require changing a project's structure as we
| could use the existing `include/` or `src/` directory as
| input when mounting, and just pick some new directory name
| such as `cfuse/include` or `cfuse/src`, and mount a new
| directory `cfuse` in the project's root directory. The
| change we'd need to make is in any Makefiles or other parts
| of the build, where instead of `gcc -Iinclude` we'd have
| `gcc -Icfuse/include`. Any non-templated headers in
| `include/` would just appear as live copies in
| cfuse/include/, so in theory this could work without
| causing anything to break.
| sirwhinesalot wrote:
| That's the craziest idea on this topic I've seen so far!
| I'm not sure that's a good or a bad thing, but it sure is
| a thing!
| uecker wrote:
| I don't struggle, I switch from C++ to C and find this much
| nicer.
| cyber1 wrote:
| I'm currently at a crossroads: C++ or Zig. One is very
| popular with a large community, amazing projects, but has
| lots of ugly design decisions and myriad rules you must know
| (this is a big pain, it seems like even Stroustrup can't
| handle all of them). The other is very close to what I want
| from C, but it's not stable and not popular.
| uecker wrote:
| Why not C?
|
| Its only real issue is that people will constantly tell you
| how bad it is and how their language of choice is so much
| better. But if you look at how things work out in practice,
| you can usually do things very nicely in C.
| mananaysiempre wrote:
| My choice in this situation is indeed C, but every once
| in a while I hit a problem that makes me yearn for better
| metaprogramming.
|
| Perfect hashing that you'd ideally use two different
| approaches for depending on whether the platform has a
| cheap popcount (hi AArch32), but to avoid complicating
| the build you give up and emulate popcount instead.
| Hundreds of thousands of lines of asynchronous I/O code
| written in a manual continuation-passing style, with
| random, occasionally problematic blocking synchronization
| sprinkled all over because the programmer simply could
| not be bothered anymore to untangle this nested loop, and
| with a dynamic allocation for each async frame because
| that's the path of least resistance. The intense
| awkwardness of the state-machine / regular-expression
| code generators, well-developed as they are. Hoping the
| compiler will merge the `int` and `long` code paths when
| their machine representations are identical, but not
| seeing it happen because functions must have unique
| addresses. Resorting to .init_array--and slowing down
| startup--because the linker is too rigid to compute this
| one known-constant value. And yes, polymorphic
| datastructures.
|
| I don't really see anybody do noticeably better than C; I
| think only Zig and Odin (perhaps also Hare and Virgil?)
| are even competing in the same category. But I can't help
| feeling that things could be much better. Then I look at
| the graveyard of attempted extensions both special-
| purpose (CPC[1]) and general (Xoc[2]) and despair.
|
| [1] https://github.com/kerneis/cpc
|
| [2] https://pdos.csail.mit.edu/archive/xoc/
| uecker wrote:
| It would be interesting to understand better where
| language feature are actually needed or helpful, and
| where the code should be organized differently. I also
| observe that often cure if worse than the disease.
|
| Many example I see where people argue for metaprogramming
| features are not all convincing to me. For example, there
| was recently a discussion about Zig comp-time.
| https://news.ycombinator.com/item?id=44208060 This is the
| Zig example: https://godbolt.org/z/1dacacfzc Here is the
| C code: https://godbolt.org/z/Wxo4vaohb
|
| Or there was a recent example where someone wanted to
| give an example for C++ coroutines and showed pre-order
| tree traversal (which I can't find at the moment), but
| the C code using vec(node) IMHO was better:
| https://godbolt.org/z/sjbT453dM compared to the C++
| coroutine version: https://godbolt.org/z/fnGzszf3j (from
| https://news.ycombinator.com/item?id=43831628 here).
| Edited to add source.
| sparkie wrote:
| The biggest issue is the ABI for C - it's the lingua-franca of
| language interoperability and can't really be changed - so
| whatever approach is taken it needs to be fully compatible with
| the existing ABI. `_Generic` is certainly flawed but doesn't
| cause any breaking ABI changes.
|
| That's also a major reason why you'd use C rather than C++. The
| C++ ABI is terrible for language interoperability. It's common
| for C++ libraries to wrap their API in C so that it can be used
| from other language's FFIs.
|
| Aside from that another reason we prefer C to C++ is because we
| don't want vtables. I think there's room for a `C+` language,
| by which I mean C+templates and not C+classes - perhaps with an
| ABI which is a subset of the C++ ABI but superset of the C ABI.
| cyber1 wrote:
| This is true. I agree with this statement. It's the holy cow
| of C. However, the problem with generic programming and
| metaprogramming isn't going away, and many people continue to
| struggle with it. Introducing something like compile-time
| reflection might be a solution...
| signa11 wrote:
| > I think there's room for a `C+` language, by which I mean
| C+templates and not C+classes - perhaps with an ABI which is
| a subset of the C++ ABI but superset of the C ABI.
|
| indeed, i have spoken to a lot of my colleagues about just
| that. if overloading is not allowed, perhaps there is still
| some hope for a backwards compatible abi ?
| sparkie wrote:
| I don't think we can get away with just using the C ABI -
| or even if we did, we would need a standardized name-
| mangling scheme, and then any language which consumes the
| ABI would need to be aware of this name-mangling scheme, so
| it would effectively be a new ABI.
|
| We might be able to make this ABI compatible with C if no
| templates are used, which wouldn't cause breaking changes -
| but for other compilers to be able to use templates they
| would need to opt-in to the new scheme. For that we'd
| probably want to augment libffi to include completely new
| functions for dealing with templates. Eg, we'd have an
| ffi_template_type, and an ffi_prep_template for which we
| supply its type arguments - then an ffi_prep_templated_cif
| for calls which use templates, and so forth. It would
| basically be a new API - but probably still more practical
| than trying to support the C++ ABI.
|
| Another issue is that if we compile some library with
| templates and expose them in the ABI, we need some way to
| instantiate the template with new types which were not
| present when the library was compiled. There's no trivial
| solution to this. We'd really need to JIT-compile the
| templates.
| signa11 wrote:
| > ... we would need a standardized name-mangling scheme,
| ...
|
| may you please elaborate on _why_ you think this is
| needed ?
| sparkie wrote:
| If the templates are monomorphized, each instantiation of
| a templated function will have a different address. To
| acquire the address of any given instantiation we need a
| symbol in the object file.
| uecker wrote:
| What isn't clear to me why one would ever want
| monomorphization in the first place.
| cherryteastain wrote:
| How can you have templates without name mangling and
| overloads?
| IAmLiterallyAB wrote:
| > we don't want vtables
|
| Then don't use virtual functions. Then there will be no
| vtables.
|
| You might have known that already, but in general I'm
| surprised how many engineers think that all C++ classes have
| vtables. No, most in fact do not. C++ classes generally have
| the same memory layout as a C struct as long as you don't use
| virtual functions.
| up2isomorphism wrote:
| They showed something they think it's neat. You start a topic
| with the assumption that they struggle, not sure how you get
| that information from the original post or you just want to
| state that claim anyway?
| jll29 wrote:
| The authoritative treatise of this topic is "C - Interfaces and
| Implementations" (known as CII) by David R. Hanson.
|
| His code is here: https://github.com/drh/cii
| mashpoe wrote:
| 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
|
| A neat project that was posted here a while back uses it:
| https://x.com/kotsoft/status/1792295331582869891
| itay2805 wrote:
| By far the best implementation for type-safe containers in C I
| found is stb_ds, it provides both a vector and a hashmap
| (including a string one).
|
| https://nothings.org/stb_ds
| lor_louis wrote:
| I do something similar, but I don't implement the logic in a
| macro, instead I have a Vec struct which looks like
| struct Vec { void *data; size_t len;
| size_t cap; size_t sizeof_ty; }
|
| I then use a macro to define a new type IntVec
| { struct Vec inner; int ty[0]; }
|
| Using the zero sized filed I can do typeof(*ty) to get some type
| safety back.
|
| 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.
| codr7 wrote:
| 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.
|
| https://github.com/codr7/hacktical-c/tree/main/vector
| uecker wrote:
| Why would you think this? My implementation is type and bounds
| safe and nice to use.
| mwkaufma wrote:
| 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.
| minroot wrote:
| Why just copy the code by yourself?
| tom_ wrote:
| What is an array? It's 3 variables: base, length and capacity. So
| why not decide that an array is just that. 3 variables of the
| right size and type. #define ARRAY(T,S) T
| S;size_t S##_length;size_t S##_capacity
|
| Then you can make one like this: ARRAY(int,xs);
|
| You'll also need to initialise and these destroy array "objects".
| #define ARRAY_INIT(S) \ do { \
| S=NULL; \ (S##_length)=0; \
| (S##_capacity)=0; \ } while(0) #define
| ARRAY_DESTROY(S) \ do { \
| Array_Free(S); \ ARRAY_INIT(S); \
| } while(0)
|
| Add you'll probably want to add an item to an array too.
| #define ARRAY_ADD(S,X) \ do {
| \ if((S##_length)>=(S##_capacity)) { \
| S=Array_Grow(S, \
| sizeof *S, \
| &(S##_length), \
| &(S##_capacity)); \ }
| \ S[S##_length++]=(X); \
| } while(0)
|
| So you might use them like this: ARRAY(int,xs);
| ARRAY_INIT(xs); for(int i=0;i<100;++i)
| ARRAY_ADD(xs,i); ARRAY_DESTROY(xs);
|
| 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. void
| Array_Free(void *p) { free(p); }
| void *Array_Grow(void *base,size_t stride,size_t *length,size_t
| *capacity) { *capacity+=*capacity/2;
| *capacity=MAX(*capacity,MAX(MIN_CAPACITY,*length));
| return realloc(base,*capacity*stride); }
|
| Array accesses and iteration and the like are just done in the
| traditional way. Even performs nicely with -O0.
| for(size_t i=0;i<xs_length;++i) {
| printf("%d\n",xs[i]); }
|
| 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": void
| FunctionThatTakesAnArray(ARRAY_PARAMS(T,*p));
|
| And you call it like this: ARRAY(T,myarray);
| FunctionThatTakesAnArray(ARRAY_ARG(&myarray));
|
| (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've got no taste, hopefully I've at least got a
| rough feel for what'll work out OK and what'll end up a
| disaster.)
___________________________________________________________________
(page generated 2025-07-26 23:01 UTC)