Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Expand a macro based on its parameter value

I have a macro that looks like this:

M(id,...)

I would like it to expand to nothing if id == 0 and something else otherwise.

Is this possible? If so, how?

My first instinct was to try something like this:

#define M(id,...) M##id(__VA_ARGS__)
#define M0(...)
#define M_NOT_0(...) some_code(__VA_ARGS__)

But the last line here is obviously not valid, and I can't figure out a way to make this pattern work.

Notes:

  • The id is an integer between 0 and 255, but ideally I'd like to avoid creating 256 separate macro definitions.
  • Please do not challenge the premise of the question. The M(id,...) macro itself cannot be changed.
  • No assumptions about the final code being expanded to can be made.
like image 863
Ryan Hilbert Avatar asked Dec 18 '16 00:12

Ryan Hilbert


2 Answers

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 ;
}
like image 108
2501 Avatar answered Oct 12 '22 23:10

2501


Based almost entirely on @PaulFultzII's superbly detailed answer to this question, here is a way to do this:

#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 CHECK_N(x, n, ...) n
#define CHECK(...) CHECK_N(__VA_ARGS__, 0,)
#define PROBE(x) x, 1
#define COMPL(b) PRIMITIVE_CAT(COMPL_, b)
#define COMPL_0 1
#define COMPL_1 0
#define NOT(x) CHECK(PRIMITIVE_CAT(NOT_, x))
#define NOT_0 PROBE(~)
#define BOOL(x) COMPL(NOT(x))
#define IF(c) IIF(BOOL(c))
#define EAT(...) 
#define EXPAND(id, ...) printf(__VA_ARGS__, id)
#define M(id, ...) IF(id)(EXPAND(id, __VA_ARGS__), EAT(id, __VA_ARGS__))

M(0, "don't ever print this!\n")
M(1, "ok to print %s, %d\n", "with a string")
M(172, "ok to print %d\n")

If we run this through the preprocessor (cpp in the case of the GNU C compiler), we get this:

printf("ok to print %s, %d\n", "with a string", 1)
printf("ok to print %d\n", 172)
like image 22
Edward Avatar answered Oct 13 '22 01:10

Edward