C Preprocessor MACro Specialisation Based on an Argument

C preprocessor macro specialisation based on an argument

Tuns out it is possible. This anwser is based on Pauls macros, but much simpler and does not need definition for each user.

#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
#define PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__

#define IIF(c) PRIMITIVE_CAT(IIF_, c)
#define IIF_0(t, ...) __VA_ARGS__
#define IIF_1(t, ...) t

#define PROBE(x) x, 1

Now, because of the MSVC bug I had to modify CHECK macro a bit.

#define MSVC_VA_ARGS_WORKAROUND(define, args) define args
#define CHECK(...) MSVC_VA_ARGS_WORKAROUND(CHECK_N, (__VA_ARGS__, 0))
#define CHECK_N(x, n, ...) n

Instead of defining CURRENT_USER I switched to following macros.

#define ENABLE_USER_gwiazdorrr () // gwiazdorrr is now enabled
#define ENABLE_USER_foo () // foo is also enabled
// #define ENABLE_USER_bar () // bar is NOT enabled

It actually gives more flexibility, because one can enable multiple user at the same time. The parenthesis is required. The macros below actually detect, whether ENABLE_USER_<user> is expanded into parenthesis or not.

#define USER_ENABLED_PROBE(user)            USER_ENABLED_PROBE_PROXY( ENABLE_USER_##user ) // concatenate prefix with user name
#define USER_ENABLED_PROBE_PROXY(...) USER_ENABLED_PROBE_PRIMIVIE(__VA_ARGS__) // expand arguments
#define USER_ENABLED_PROBE_PRIMIVIE(x) USER_ENABLED_PROBE_COMBINE_##x // merge
#define USER_ENABLED_PROBE_COMBINE_(...) PROBE(~) // if merge successful, expand to probe

USER_ENABLED_PROBE(gwiazdorrr) // expands to ~, 1
USER_ENABLED_PROBE(bar) // expands to USER_ENABLED_PROBE_COMBINE_bar

From now it is a childs play:

#define IS_USER_ENABLED(user) CHECK(USER_ENABLED_PROBE(user))

IS_USER_ENABLED(gwiazdorrr) // expands to 1
IS_USER_ENABLED(bar) // expands to 0

Having this macro and IIF (thanks Paul!) I decided to implement the optimisation macro mentioned in the original question:

#define TURN_OPTIMISATION_OFF(user) IIF( IS_USER_ENABLED(user) ) \
(\
__pragma optimize("", off),\
/* nothing */ \
)

TURN_OPTIMISATION_OFF(gwiazdorrr) // expands into __pragma optimize("", off)
TURN_OPTIMISATION_OFF(foo) // expands into __pragma optimize("", off)
TURN_OPTIMISATION_OFF(bar) // nothing emitted

Thanks for input!

EDIT: here's the GCC version: http://ideone.com/129eo

Define a preprocessor macro with preprocessor comands

You can define two different macros depending on the context:

#ifdef __cplusplus
#define CFUNC(ARGUMENT) extern "C" ARGUMENT;
#else
#define CFUNC(ARGUMENT) ARGUMENT;
#endif

CFUNC(FOO)

However, the common way to do this is the following. It includes the braces and can be used both in definitions and declarations.

#ifdef __cplusplus
#define EXTERN_C extern "C" {
#define EXTERN_C_END }
#else
#define EXTERN_C
#define EXTERN_C_END
#endif

EXTERN_C
void foo(void) { ... }
EXTERN_C_END

Prepend argument to a list and apply another macro in C preprocessor macro

If you want to incorporate an unmatched opening parenthesis in a replacement list, you need to add a layer of indirection:

#define LPAREN (

#define PREPEND_AND_APPLY_STRANGE(x) STRANGE_MACRO LPAREN x,STRIP_PAREN

This way the parenthesis won't be considered part of an invocation until the correct syntax is complete.

Unfortunately there's a limitation to this technique: by the time the call to STRANGE_MACRO is fully constructed, it's past the point where it would be eligible for rescanning - since the start and end of the expression were built across two different calls that took place at the same level, the whole thing never appeared in one rescan list - and will never actually expand; you'll just get STRANGE_MACRO ( x,y,z,w) dumped in your output. You need to force a rescan at the top level:

#define EXPAND(...) __VA_ARGS__

The macro does nothing, but does mean its argument gets to be put in a rescan list as a complete unit and will thus finally expand. So the closest you can get to your desired syntax would be this:

EXPAND(

MACRO1(identifier) (
....
)
MACRO1(identifier) (
....
)

)

...so you don't need to disfigure each of your custom blocks with its own EXPAND, but you do need to wrap the whole program in one. And no you can't hide the wrapper by putting an #include directive within EXPAND, as directives can't appear within invocations. (Still, perhaps you can make something useful of it by reinventing namespace or something similar that your syntax might need.)

This also has the disadvantage that the C compiler will perceive your entire program as being on one line, which will hurt error reporting somewhat - although you were already halfway to this problem, as each declaration block would also have seemed to be on only one line had the original version worked.

Expand a macro based on its parameter value

The macro CHECK0 works based on the number of tokens encountered in a macro.

If a token passed to the macro has a value of 0, it expands to HIDDEN0 that expands to two tokens. Otherwise it expands to a macro that doesn't exist, and this macro is considered as one token.

The macro SECOND then selects the second token. If HIDDEN0 is encountered, it selects 0, if the non-existing token is encountered, it selects 1.

That result is then joined with a prefix selected by the user. This is seen in macros HELLO, and PRINT. One is a plain macro, the other is a macro function. The resulting macro is either HELLO0 or HELLO1. One is defined as something useful, other is defined as empty. The same goes for PRINT.

#include <stdlib.h>
#include <stdio.h>

#define EXPAND(...) __VA_ARGS__ //needed for MSVC compatibility

#define JOIN_EXPAND( a , b ) a##b
#define JOIN( a , b ) JOIN_EXPAND( a , b )

#define SECOND_EXPAND( a , b , ... ) b
#define SECOND(...) EXPAND( SECOND_EXPAND( __VA_ARGS__ ) )

#define HIDDEN0 unused,0
#define CHECK0( value ) SECOND( JOIN( HIDDEN , value ) , 1 , unused )

#define HELLO0 puts( "HELLO" )
#define HELLO1
#define HELLO( value ) JOIN( HELLO , CHECK0( value ) )

#define PRINT0( ... ) printf( __VA_ARGS__ )
#define PRINT1( ... )
#define PRINT( value , ... ) JOIN( PRINT , CHECK0( value ) )( __VA_ARGS__ )

int main( void )
{
HELLO( 54545 ) ; //evaluates to nothing
HELLO( 1 ) ; //evaluates to nothing
HELLO( 0 ) ; //evaluates to puts( "HELLO" )

PRINT( 861151 , "Some values: %lf %d\n" , 3.13 , 12345 ) ; //evaluates to nothing
PRINT( 1 , "Some values: %lf %d\n" , 3.13 , 12345 ) ; //evaluates to nothing
PRINT( 0 , "Some values: %lf %d\n" , 3.13 , 12345 ) ; //evaluates to printf( "Som

printf( "%d %d %d\n", CHECK0( 0 ) , CHECK0( 1 ) , CHECK0( 123456 ) ) ; //outputs 0 1 1
return EXIT_SUCCESS ;
}

Why are some preprocessor macros not expanded unless they are arguments to another macro?

In the bad case, what you intend to be arguments to PRAGMA never appear with PRAGMA in tokens that are scanned for macro replacement.

We can ignore the LOOP_xxx macros; they just expand to various tokens without complications, and the resulting tokens are processed as if they appeared in the source file normally. We can instead consider just DELAY(PRAGMA)(foo) and WRAP(DELAY(PRAGMA)(foo).

Per C 2018 6.10.3.1 and 6.10.3.4, the arguments of a macro are processed for macro replacement, then the resulting tokens are substituted into the macro’s replacement tokens, then the resulting tokens and subsequent tokens of the source file are rescanned for further replacement. (When the tokens of a macro argument are being processed, they are treated as if they constitute the entire source file.)

In DELAY(PRAGMA)(foo):

  1. PRAGMA is the argument x to DELAY, but it is not followed by parentheses, so it is not a macro to replace.
  2. PRAGMA is substituted for x in DELAY’s replacement tokens x EMPTY().
  3. The result, PRAGMA EMPTY(), is scanned for replacement.
  4. EMPTY is replaced by nothing.
  5. The results of the replacement of EMPTY, along with the subsequent tokens ((foo), and anything that follows it) are scanned. Note that PRAGMA is not in these tokens: It is not part of the tokens that resulted from replacing EMPTY.
  6. Macro replacement is complete.

In WRAP(PRAGMA)(foo), the first five steps are the same, and the remaining steps result in replacement of PRAGMA (foo):

  1. PRAGMA is the argument x to DELAY, but it is not followed by parentheses, so it is not a macro to replace.
  2. PRAGMA is substituted for x in DELAY’s replacement tokens x EMPTY().
  3. The result, PRAGMA EMPTY(), is scanned for replacement.
  4. EMPTY is replaced by nothing.
  5. The results of the replacement of EMPTY, along with the subsequent tokens ((foo)) are scanned. As above, PRAGMA is not in these tokens, so it is not replaced.
  6. Macro replacement of the argument to WRAP is complete, having produced PRAGMA (foo).
  7. These tokens from the argument are substituted into WRAP’s { BODY }, producing { PRAGMA (foo) }.
  8. These tokens (along with following tokens in the source file) are rescanned for further replacement. Now PRAGMA (foo) appears in these tokens, so it is replaced.


Related Topics



Leave a reply



Submit