Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling an Associative* Function with C Macros

*Not to be confused with having anything to do with Associative Arrays.

I know how to vectorize a function in C with macros to give results similar to the Mathematica's Map (or Apply) functionality. Namely apply a function to a list of arguments.

#define Apply( type, function, ...)             \
{                                               \
   void *Stop = (int[]){0};                     \
   type **List = (type*[]){__VA_ARGS__, Stop};  \
   for( int i = 0; List[i] != Stop; ++i )       \
   function( List[i] );                         \
}                     

I can then do something like

#define FreeAllAtOnce(...) Apply( void, free, __VA_ARGS__ );

which has the effect that

free( Array1 );
free( Array2 );
free( Array3 );

is equivalent to

FreeAllAtOnce( Array1, Array2, Array3 );

I didn't make that up, I read about it in a book and have used it heavily since.

My question is: Can I do something similar to associatively combine an array via some binary function. For example take the GCD function. I want a function like:

GCD_all( a, b, c, d, e );

That has the same effect as

GCD( GCD( GCD( GCD( a, b ), c ), d ), e );

for any number of arguments.

I've tried to do this and have not been able to properly make it work. I'm also interested in the case where there may be additional parameters passed to the function. In the most generic sense I'm looking to do this with functions like:

Atype BinaryCombine( Atype a, Atype b, OtherType z, OtherType y )

so that I have a function

Atype BinaryCombineAll( Atype a, Atype b, Atype c, Atype d, OtherType z, OtherType y )

I hope that makes sense. Any ideas or help would be very appreciated!

Thanks.

like image 481
amcalde Avatar asked Jun 16 '16 14:06

amcalde


People also ask

What does ## mean in macro?

The double-number-sign or token-pasting operator (##), which is sometimes called the merging or combining operator, is used in both object-like and function-like macros. It permits separate tokens to be joined into a single token, and therefore, can't be the first or last token in the macro definition.

What is Argumented macro in C?

Function-like macros can take arguments, just like true functions. To define a macro that uses arguments, you insert parameters between the pair of parentheses in the macro definition that make the macro function-like. The parameters must be valid C identifiers, separated by commas and optionally whitespace.

What is a macro call in C?

"Macros" in C are a simple string substitution (including substitution of arguments) done by the C pre-processor (on Linux, that would be cpp foo. c ) before compilation is done. Run just the C pre-processor on your source to see exactly how the macro is expanded. It doesn't exactly act like a function.

How do you use #define C++?

Remarks. The #define directive causes the compiler to substitute token-string for each occurrence of identifier in the source file. The identifier is replaced only when it forms a token. That is, identifier is not replaced if it appears in a comment, in a string, or as part of a longer identifier.


1 Answers

This requires rather tricky machinery (see this answer for more details), since you cannot normally have recursive macros in C:

#define _NUM_ARGS2(X,X5,X4,X3,X2,X1,N,...) N
#define NUM_ARGS(...) _NUM_ARGS2(0,__VA_ARGS__,5,4,3,2,1,0)
// be sure to add X6,X7,... and 6,7,... to support more arguments

#define GCD_OF_1(a)         (a)         
#define GCD_OF_2(a,b)       GCD(a, b)
#define GCD_OF_3(a,b,...)   GCD_OF_2(GCD_OF_2(a,b),__VA_ARGS__)
#define GCD_OF_4(a,b,...)   GCD_OF_3(GCD_OF_2(a,b),__VA_ARGS__)
#define GCD_OF_5(a,b,...)   GCD_OF_4(GCD_OF_2(a,b),__VA_ARGS__)
// in general:
// #define GCD_OF_N(a,b,...)   GCD_OF_N-1(GCD_OF_2(a,b),__VA_ARGS__)

#define _GCD_OF_N3(N, ...)  GCD_OF_ ## N(__VA_ARGS__)
#define _GCD_OF_N2(N, ...) _GCD_OF_N3(N, __VA_ARGS__) // helper macro to expand N
#define GCD_all(...)       _GCD_OF_N2(NUM_ARGS(__VA_ARGS__), __VA_ARGS__)

int main(void)
{
    GCD_all(a, b, c, d, e);
}

The gcc -E produces output as:

int main(void)
{
  GCD(GCD(GCD(GCD(a, b), c), d), e);
}

The NUM_ARGS automagically finds the number of arguments. This way you obtain the "starting point" macro GCD_OF_N for futher expanding.

like image 50
Grzegorz Szpetkowski Avatar answered Oct 04 '22 09:10

Grzegorz Szpetkowski