3 ms·
This gets messy in a couple of ways; for example, what if you want to pass two of them to a function? Then the names of the parameters generated by the ARRAY_AR
by tines 10y ago
This gets messy in a couple of ways; for example, what if you want to pass two of them to a function? Then the names of the parameters generated by the ARRAY_ARG macro will clash and you'll have to add a counter to it, etc. (Also I'm not sure that you can concatenate `* p` with `_length` in `S##_length` where `S` is `* p`, and the same thing for the other, but I understand what you meant.) You'll also have potentially very confusing errors for the users of your library when they happen to create a variable whose name collides with one that the macro generates. And those are just the cursory observations.
- gravypod 10y agoThis is what the macros expand to. From: $ cat test.c #define ARRAY(T,S) T S;size_t S##_length;size_t S##_capacity #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) #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) 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); } #define ARRAY_PARAMS(T,S) T *S,size_t S##_length,size_t S##_capacity #define ARRAY_ARG(S) S,S##_length,S##_capacity void test(ARRAY_ARG(p), ARRAY_ARG(a)) { } int main() { ARRAY(int, myarray); ARRAY(int, ourarray); test(ARRAY_ARG(myarray), ARRAY_ARG(ourarray)); } To: $ gcc -E test.c # 1 "test.c" # 1 "<built-in>" # 1 "<command-line>" # 31 "<command-line>" # 1 /usr/include/stdc-predef.h" 1 3 4 # 32 "<command-line>" 2 # 1 "test.c" # 22 "test.c" 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); } void test(p,p_length,p_capacity, a,a_length,a_capacity) { } int main() { int myarray;size_t myarray_length;size_t myarray_capacity; int ourarray;size_t ourarray_length;size_t ourarray_capacity; test(myarray,myarray_length,myarray_capacity, ourarray,ourarray_length,ourarray_capacity); } Looks like fairly reasonable code. Edit: I would like to see it take a type in the Params and a __typeof__ while passing the args to make sure you know what is going where.
- to3m 10y agoThanks for trying it out - my post was assembled by copying bits out of the (rather gnarlier) code that I actually used, so I'm glad it mostly survived the process ;) Interesting point about the type checking; my thinking was that the compiler could check they matched, and that this would suffice - and sure enough it worked absolutely fine in practice. But now that I'm made to think about it again, I think it's probably still not quite good enough to be perfect, because you could do this: void f(ARRAY_PARAMS(const char *,*xs)) { ARRAY_ADD(*xs,"fred"); } ... ARRAY(char *,xs); ARRAY_INIT(xs); f(&xs); And now you've got a char * that points to a const string. Erm... that's not good! EDIT: maybe I see what you're getting at with ARRAY_ARG now. The intention is that you use ARRAY_PARAMS to generate the text for the function declaration or definition (that's why it has the type in it), and ARRAY_ARG to generate the text for the code where you pass one to a function so declared (that's why it's just 3 names - they're intended to be expressions, not names for function parameters). That means test should (?) be like this: void test(ARRAY_PARAMS(int, p), ARRAY_PARAMS(int, a)) { } Hopefully that makes sense. Maybe they'd have been better off with the common C terminology of formal and actual parameters. Then you'd have ARRAY_FORMAL_PARAMS for ARRAY_PARAMS, and ARRAY_ACTUAL_PARAMS for ARRAY_ARG.
- to3m 10y agoARRAY_PARAM (as I assume you mean?) has no problem with two arrays. You just give them different names. Suppose you do this: void CopyIntArray(ARRAY_PARAMS(int,*dest),ARRAY_PARAMS(int,src)) Now you end up with this: void CopyIntArray(int **dest,size_t *dest_length,size_t *dest_capacity, int *src,size_t src_length,size_t src_capacity); And you can call it like this: ARRAY(int,xs); ARRAY(int,ys); CopyIntArray(ARRAY_ARG(&xs),ARRAY_ARG(ys)) Your point about token pasting with * p is a very good one, and I don't think that had occurred to me... but neither clang, gcc or VC++ seems to mind (and I used a number of different versions of each). I need to go and look up what the C standard has to say about this now. I do note that I didn't use ARRAY_PARAM or ARRAY_ARG all that much in my code, though - but I don't remember whether this is because I found some problem with them in practice, or whether it just ended up that way. (I'm on OS X right now and I just tried my code with clang. Probably-relevant compile flags were "-std=c1x -Wall -Wuninitialized -Winit-self -pedantic -Werror=implicit-function-declaration -Wsign-conversion -Wunused-result -Werror=incompatible-pointer-types -Werror=int-conversion -Werror=return-type -Wno-overlength-strings -Wunused-parameter".)
- tines 10y agoAh good catch, I misread it.