Generalized iteration over arguments of macro in the C preprocessor - c-preprocessor

There were several questions here regarding variadic macros in C. These include:
How to make a variadic macro (variable number of arguments) which explains the basics, e.g., passing a variable number of arguments to functions such as printf
Is it possible to iterate over arguments in variadic macros?, which explains how to iteratively apply a macro to each of the arguments of the variadic macro.
https://github.com/swansontec/map-macro which explains how to do so on pairs
My question is related to the iteration technique. I am interested in a macro with this generalized semantics.
ITERATE(Before, Action, Between, After, Empty, ...)
that will place Before prior to all expansions, apply Action to each argument, place Between between every two consecutive applications, and will finally place the expansion of After. Moreover, if the number of argument With such a macro, it should be possible to write
// Loop elements
#define A(x) (x)
#define Befor (
#define After )
#define Between ||
#define Empty 1
// Define an OR macro
#define OR(...) ITERATE(Before, A, Between, Empty, __VA_ARGS__)
// Use it
OR() // Expands to 1
OR(a) // Expands to ((a))
OR(a,b) // Expands to ((a)||(b))
OR(a,b,c) // Expands to to ((a)||(b)||(c))
The purpose of course is not to write an OR function. A generalized functionality could be for more intricate applications. E.g., a macro for defining classes and functions, something to print the trace, etc.

I never liked the recursive REPEAT() macro idiom - it generates horrible hour long read error messages that are.. recursive, so you don't know where the error is and it's also hard to grasp how the OBSTRUCT(REPEAT_INDIRECT) () stuff works. Overall, overloading the macro on number of arguments and using an external tool (shell script or m4 preprocessor) to generate C source code is waay easier, easier to read, maintain and fix and also you can expand the macros on the tools side removing the burden of recursive expansion on C side. With that in mind, your ITERATE can be generated with existing preprocessor libraries, P99_FOR or BOOST_FOREACH comes to mind.
Also, typing shift all the time is strange - I prefer snake case. Here's a reduced example without Before and After macros with overloading the macro on number of arguments:
#define _in_ITERATE_0(f,b,e) e()
#define _in_ITERATE_1(f,b,e,_1) f(_1)
#define _in_ITERATE_2(f,b,e,_1,...) f(_1)b()_in_ITERATE_1(f,b,e,__VA_ARGS__)
#define _in_ITERATE_3(f,b,e,_1,...) f(_1)b()_in_ITERATE_2(f,b,e,__VA_ARGS__)
// or you could expand it instead of reusing previous one with same result:
#define _in_ITERATE_4(f,b,e,_1,_2,_3,_4) f(_1)b()f(_2)b()f(_3)b()f(_4)
// etc.... generate
#define _in_ITERATE_N(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,N,...) _in_ITERATE_##N
#define ITERATE(func, between, empty, ...) \
_in_ITERATE_N(0,##__VA_ARGS__,9,8,7,6,5,4,3,2,1,0)(func, between, empty, ##__VA_ARGS__)
#define _in_OR_OP(x) (x)
#define _in_OR_EMPTY() 1
#define _in_OR_BETWEEN() ||
#define OR(...) (ITERATE(_in_OR_OP, _in_OR_BETWEEN, _in_OR_EMPTY, ##__VA_ARGS__))
// Use it
OR() // Expands to (1)
OR(a) // Expands to ((a))
OR(a,b) // Expands to ((a)||(b))
OR(a,b,c) // Expands to to ((a)||(b)||(c))
outputs:
(1)
((a))
((a)||(b))
((a)||(b)||(c))
For more examples on overloading macro on count of arguments see this thread. I am using ## GNU extension to remove the comma before __VA_ARGS__ because I am used to using it - I think __VA_OPT__(,) should be nowadays preferred, I am not sure.

Related

CPP_Magic IF_ELSE Macro?

cpp_magic extends what can typically be done with the C Preprocessor.
(It's a single header file and is on GitHub, here.)
The IF_ELSE(cond)(<true_result>, <false_result>) is a super useful macro!
How can expressions be evaluated in the cond clause?
It doesn't appear to work as advertised: with expressions in the cond part.
The following returns 10:
int greater = IF_ELSE(10 > 20)(10, 20);
The macro always returns the first argument, unless it is a simple 0 or 1.
Is the c argument (condition) a misnomer (and is really ust a simple value)?
I also tried this, according to a suggestion below, but it gives the same result:
#define GREATER(x,y) BOOL(x > y)
int greater = IF_ELSE(GREATER(10,20))(10, 20);
But it also evaluates to 10.
(Note that IF_ELSE already calls BOOL(c) on its argument.)
Has anyone used IF_ELSE with a general preprocessor expression?
Looking at cpp_magic, it looks a bit basic. If you want to evaluate math in the preprocessor using that, you have to basically implement the math yourself using macros. First off the bat, cpp_magic's IF_ELSE macro is defined as follows:
#define IF_ELSE(condition) _IF_ ## condition
That's a dead stop to using it as you prescribe, because this macro's expansion list contains a paste. The way macros expand involves four steps:
Argument substitution (a.s.; 6.10.3.1), where for each mention of a parameter in the macro's replacement list where said parameter is not participating in a paste or stringification, the corresponding argument is fully expanded, and the resulting expansion replaces the mention in the replacement list.
Stringification (6.10.3.2)
Pasting (6.10.3.3)
Rescan-and-further-replacement (r.a.s.r.; 6.10.3.4), where the resulting replacement list is rescanned; during this rescan the macro in question is marked as invalid for further expansion ("painted blue") to avoid recursion.
So in cpp_magic's implementation of IF_ELSE, no matter what you pass in as the condition, it will not do a.s.; instead, it will simply paste to _IF_. E.g., if you call IF_ELSE(BOOL(x)), you would simply get _IF_BOOL(x). You can patch this (but it's ugly, and there's a much much better library... see below) by adding an indirection macro like this:
#define EVAL_IF_ELSE(condition) IF_ELSE(condition)
...so you need at least this. For a greater comparision, you would need to implement greater. Here's a trivial implementation:
#define GLUE(A,B) GLUE_I(A,B)
#define GLUE_I(A,B) A##B
// repeats X N times
#define N_TIMES(N,X) GLUE(N_TIMES_,N)(X)
#define N_TIMES_1(X) X
#define N_TIMES_2(X) X,N_TIMES_1(X)
#define N_TIMES_3(X) X,N_TIMES_2(X)
#define N_TIMES_4(X) X,N_TIMES_3(X)
#define N_TIMES_5(X) X,N_TIMES_4(X)
// pop; technically non-compliant for one parameter
// which I could code around, but this is a simplified
// demo only (and there's a much better way)
#define POP(...) POP_I(__VA_ARGS__)
#define POP_I(X,...) __VA_ARGS__
#define NTH(N,...) GLUE(NTH_,N)(__VA_ARGS__)
#define NTH_1(...) NTH_1_I(__VA_ARGS__,)
#define NTH_1_I(X,...) X
#define NTH_2(X,...) NTH_1(__VA_ARGS__)
#define NTH_3(X,...) NTH_2(__VA_ARGS__)
#define NTH_4(X,...) NTH_3(__VA_ARGS__)
#define NTH_5(X,...) NTH_4(__VA_ARGS__)
#define COMPARE(X,Y) NTH(X,POP(N_TIMES(Y,L)),E,N_TIMES(5,G))
#define GREATER(X,Y) GLUE(GREATER_RESULT_,COMPARE(X,Y))
#define GREATER_RESULT_L 0
#define GREATER_RESULT_E 0
#define GREATER_RESULT_G 1
...so that's a nice start. And this greater works perfectly... for numbers up to 5... so long as you ignore the 1 case. There's a skeleton here for how to do other comparisons, but they would only work up to 5. A demo working up to 20 is shown here.
This shows what you want to do is possible, but it's still a lot of work. Here I'm only showing a way to do a comparison; but everything else you want to do (add, sub, mul, div, etc) also needs an implementation, and each piece is code. If you want to play with it, knock yourself out, but I would recommend for play ditching the C language and just use your preprocessor like I do in the demo.
There is a much, much better way
...and that is to let someone else do all of the work for you. And they have! What you're in effect trying to do has been pulled into the boost preprocessor library. BPP also has add, sub, mul, div, and so on. For BPP's implementation, the saturation is at 255. Here's how you would do your conditional using boost preprocessor:
#include <boost/preprocessor/comparison.hpp>
#include <boost/preprocessor/control.hpp>
BOOST_PP_IF(BOOST_PP_GREATER(10,20),10,20)
...and a demo

Is there a way to force C preprocessor to evaluate macro arguments before the macro

I have a number of macros in the form
#define F(A,B) Some function of A and B
and for readability I would like to define arguments for these macros e.g.
#define C A,B
so that I can say
F(C)
but the preprocessor tries to expand F before C and complains that F needs 2 arguments. Is there a way to make it expand C before it expands F so that the error does not occur?
You can use an intermediate macro that takes a variable number of arguments:
#define F1(A,B)
#define F(...) F1(__VA_ARGS__)
#define C A,B
int main(void) {
F(C)
F(1,2)
return 0;
}
This should compile. You will still get a compilation failure if you pass more or less than two arguments, or arguments that don't expand to exactly two arguments.
Macro expansion (erroneously) does not trigger argument recount. Therefore, any time macro expansion of a function invocation results in a different number of arguments, it must be forced to recount the number of arguments.
Use this pattern to force expansion and recount before invocation:
//Step 1: wrap the entire affected argument pack in parenthesis
#define mFn(A, ExpandingArgument) mFn1((A, ExpandingArgument))
//Step 2: intermediary layer without ## or # is required to actually expand
#define mFn1(...) mFn2(__VA_ARGS__)
//Step3: Paste the parenthesized arguments to final function identifier to trigger
// function like macro interpretation and invocation
#define mFn2(...) mFn3##__VA_ARGS__
//Step4: Implement the actual function as if the standard were written correctly
#define mFn3(A,B,C,...) //Do things

How to fix `left hand operand has no effect` warning with variadic macro in C

I'm using the variadic macro to simulate a default argument. I compile with -Wunused-value. Thus, I get the following warning:
warning: left-hand operand of comma expression has no effect
Is there a way to somehow fix this warning without having to remove -Wunused-value? or do I have to end up using #pragma GCC diagnostic ignored "-Wunused-value"?
#include <stdio.h>
#define SUM(a,...) sum( a, (5, ##__VA_ARGS__) )
int sum (int a, int b)
{
return a + b;
}
int main()
{
printf("%d\n", SUM( 3, 7 ) );
printf("%d\n", SUM( 3 ) );
}
The ## construct that you are using is a gcc speciality and not portable. Don't use it, there are other ways.
The following should do what you expect
#define SUM2(A, B, ...) sum((A), (B))
#define SUM1(...) SUM2(__VA_ARGS__)
#define SUM(...) SUM1(__VA_ARGS__, 5, 0)
Such games with macro default arguments are frowned upon by many, because they may make the code more difficult to read.
For the moment I'd suggest that you don't use such constructs in your programs. You should perhaps learn more of the basics before you go into such esoteric stuff.
Also your idea to want to silence the compiler is really a bad one. The compiler is there to help you, listen to him. In the contrary, raise the warning level to a maximum and improve your code until it compiles without any warning.
Jens Gustedt proposed a very good problem-specific portable solution. I didn't know, ,##__VA_ARGS__ is a GCC extension (maybe Clang too?). There are however GCC-specific solutions for the authors initial intension.
As a problem-specific and very GCC-specific solution, you can use _Pragma("GCC diagnostic ignored \"-Wunused-value\"") and delimit it around the macro expansion. This will keep the comfort of readability. This does not work everywhere. It mainly fails within static initializer lists placed outside of functions where those pragmas can't be applied. I really was looking for a solution within such initializer lists because I couldn't find any which hides the warning pragmas from the reader. Other than that, for a function call like sum() for example - which I suppose to be only valid in a function body itself -, you can use it:
#define SUM(a,...) ({\
_Pragma("GCC diagnostic push")\
_Pragma("GCC diagnostic ignored \"-Wunused-value\"")\
sum( a, (5, ##__VA_ARGS__) );\
_Pragma("GCC diagnostic pop")\
})
Remember, you can only use it in function bodies and where an expression is expected. The warning will remain turned on after the macro expansion.
But I found a general solution! Conditional-macro-expansion is possible with the ,##__VA_ARGS__ feature. It gives you the power of conditional expansion based on blankness of the argument.
This feature does not necessarily add substitution power to the preprocessor. If you use arguments which include commas like (<...>) for false or 0 and (<...>,<...>) for true or 1, you can achieve the same. But only the conditional comma allows you the comfort of expanding conditionally based on the blankness of an argument.
See: you might be able to write your code like SUM(A) expanding to sum((A),5) without ##__VA_ARGS__ but you might not be able to write SUM(,B) expanding to sum((somevalue),B) . But you can do that with ##__VA_ARGS__ .
Example:
#define _VADIC(...) , ##__VA_ARGS__
//expands to A if A is not blank else to __VA_ARGS__ as fallback value
#define TRY(A,B) _TRY0(_VADIC(A), B)
#define _TRY0(...) _TRY1(__VA_ARGS__) /*expand before call*/
#define _TRY1(A, B, ...) B
//expands to THEN if A is blank otherwise expands to blank
#define IF(A,THEN) _IF0(_VADIC(A),THEN)
#define _IF0(...) _IF1(__VA_ARGS__) /*expand before call*/
#define _IF1(A,B,...) __VA_ARGS__
//expands to ELSE if A is not blank otherwise expands to blank
#define IFNOT(A,ELSE) _IFNOT0(_VADIC(A),,ELSE)
#define _IFNOT0(...) _IFNOT1(__VA_ARGS__) /*expand before call*/
#define _IFNOT1(A,B,C,...) C
#define IF_ELSE(A, THEN, ELSE) IF(A,THEN)IFNOT(A,ELSE)
Without the conditional comma, you only can expand conditionally on the number of arguments or on predefined concatenations but this way, you can use ANY single undefined symbol as condition.
PS: What about loops? Macros in C are designed to be finite for faster compilation. You won't get infinite loops since the limit of loop cycles depends on the source code size. Limited loops is the only thing which hinders you from turing-completeness, but practical real-world computer science problems (different from embedded or operating systems) don't need infinite loops for calculations. They are all limited depending with the problem size. The turing machine also uses a finite alphabet of symbols. You could know the limit of loop cycles which are needed in the worst case and it is possible to create a functional loop (a "reduce" or "filter" macro) running on variable-length macro argument lists which can reformat the macro argument list and do magic. The only requirement is the comma. You can't iterate over elements without a comma in between.

Concatenation of tokens in variadic macros

In C, is it possible to concatenate each of the variable arguments in a a variadic macro?
Example:
MY_MACRO(A, B, C) // will yield HDR_A, HDR_B, HDR_C
MY_MACRO(X, Y) // will yield HDR_X, HDR_Y
The normal ## operator has special meaning for variadic macros (avoiding the comma for empty argument list). And concatenation when used with __VA_ARGS__ takes place with the first token only.
Example:
#define MY_MACRO(...) HDR_ ## __VA_ARGS__
MY_MACRO(X, Y) // yields HDR_X, Y
Suggestions?
First, the comma rule you are mentioning is a gcc extension, standard C doesn't have it and most probably will never have it since the feature can be achieved by different means.
What you are looking for is meta programming with macros, which is possible, but you'd need some tricks to achieve that. P99 provides you with tools for that:
#define MY_PREFIX(NAME, X, I) P99_PASTE2(NAME, X)
#define MY_MACRO(...) P99_FOR(HDR_, P99_NARG(__VA_ARGS__), P00_SEQ, MY_PREFIX, __VA_ARGS__)
Here MY_PREFIX describes what has to be done with the individual
items.
P00_SEQ declares how the items should be separated
P99_NARGS just counts the number of arguments

Swallowing comma in variadic macros on compilers that do not recognise ##

I need to write a variadic macro in C which must take zero or more arguments.
In gcc, that can be achieved by adding "##" after the comma, e.g. ,##____VA_ARGS____ as answered in Variadic macros with zero arguments.
However, the compiler in my build system (beyond my control) does not understand the ,## syntax and so does not swallow the comma.
Is there a workaround I can use?
Yes, gcc swallowing the comma is non standard and you should not rely on that.
With C99 conforming preprocessors you may achieve a similar effect by testing for a macro arguments that is the empty token. For the ideas of how this works you could see here, for a whole set of preprocessor macros that ease the programming of such features there is P99.
Using a simplified version what Jens spelled out, a possible approach is described below.
E_ is used to inject an argument, while I_ is used to cause indirect invocation of a macro. V_ is required to add an additional level of indirection, which seems required for some compilers.
#define V_(...) __VA_ARGS__
#define I_(M,...) V_(M(__VA_ARGS__))
#define E_() _,
Now, suppose you need a macro MAC to handle 0, 1, or 2 arguments:
MAC(1, 2);
MAC(1);
MAC();
One possible implementation might look like:
#define MAC_X(_2,_1,X,...) MAC_##X
#define MAC(...) V_(V_(MAC_X(__VA_ARGS__,2,_))(__VA_ARGS__))
#define MAC_2(...) function_for_2(__VA_ARGS__)
#define MAC_1(...) function_for_1(__VA_ARGS__)
#define MAC_0(...) function_for_0(__VA_ARGS__)
#define MAC_Y(_1,Y,...) MAC_##Y
#define MAC__(...) I_(MAC_Y, E_ __VA_ARGS__ () 0, 1)(__VA_ARGS__)
And expanding it to 3 arguments is relatively straightforward. The only messiness is detecting 1 or 0 arguments, which does not change:
-#define MAC_X(_2,_1,X,...) MAC_##X
-#define MAC(...) V_(V_(MAC_X(__VA_ARGS__,2,_))(__VA_ARGS__))
+#define MAC_X(_3,_2,_1,X,...) MAC_##X
+#define MAC(...) V_(V_(MAC_X(__VA_ARGS__,3,2,_))(__VA_ARGS__))
+#define MAC_3(...) function_for_3(__VA_ARGS__)
#define MAC_2(...) function_for_2(__VA_ARGS__)
For more than 1 argument, the MAC macro expands into MAC_2 (or MAC_3). But, for 1 or 0 arguments, the MAC macro expands to MAC__.
The MAC__ macro applies Jens' trick to detect whether it was passed 1 or 0 arguments. It does this with the E_ helper macro, and injecting its argument between E_ and the () that would cause it to be invoked. If there are 0 arguments, the E_ is invoked, and an argument is injected. The injected argument causes 0 to be the second argument for MAC_Y to select. If there is 1 argument, E_ is not expanded. The first argument to the to MAC_Y becomes E_ ... () 0 (where the ... is whatever the first argument was), and the second argument for MAC_Y is 1. This allows MAC_Y to invoke either MAC_0 or MAC_1.

Resources