根据其参数值展开宏

时间:2016-12-18 00:39:57

标签: c c-preprocessor

我有一个看起来像这样的宏:

M(id,...)

如果id == 0以及其他内容,我希望它扩展为空。

这可能吗?如果是这样,怎么样?

我的第一直觉是尝试这样的事情:

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

但是这里的最后一行显然无效,我无法找到使这种模式有效的方法。

注意:

  • id是介于0到255之间的整数,但理想情况下,我希望避免创建256个单独的宏定义。
  • 请不要质疑问题的前提。 M(id,...)宏本身无法更改。
  • 不能对可扩展的最终代码做出任何假设。

3 个答案:

答案 0 :(得分:5)

宏CHECK0根据宏中遇到的令牌数量工作。

如果传递给宏的标记值为0,则会扩展为HIDDEN0,扩展为两个标记。否则它会扩展为不存在的宏,并且此宏被视为一个令牌。

宏SECOND然后选择第二个令牌。如果遇到HIDDEN0,则选择0,如果遇到不存在的令牌,则选择1.

然后,该结果与用户选择的前缀相结合。这可以在宏HELLO和PRINT中看到。一个是普通的宏,另一个是宏功能。生成的宏是HELLO0或HELLO1。一个被定义为有用的东西,另一个被定义为空。 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 ;
}

答案 1 :(得分:4)

几乎完全基于@PaulFultzII对this question的非常详细的回答,这是一种方法:

#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")

如果我们通过预处理器(在GNU C编译器的情况下为cpp)运行它,我们得到这个:

printf("ok to print %s, %d\n", "with a string", 1)
printf("ok to print %d\n", 172)

答案 2 :(得分:0)

假设some_code()实际上是代码

这可能很棘手:如果从另一个令牌扩展id,则可能是(0)而不是0,或0x10而不是16 }。此外,还有运行时值。

但是,如果你有一个足够现代的编译器,你可以使用死代码消除:

#define M(id, ...) do {             \
    if (id) {                       \
        some_code(__VA_ARGS__);     \
    }                               \
} while (/* CONSTCOND */ 0)

即使在诸如......的设置中,这也可以解决问题。

void
foo(int bar, int baz) {
    M(bar, baz);
}

...而且,由于id在所有其他情况下应该是常量,if将被优化掉(即使是GCC 3.4.6,没有任何优化标志也会这样做,对于零和nōn-zero值,我刚检查过; LLVM也会这样做,许多商业Unix供应商编译器可能会这样做; PCC可能不会,但不太可能遇到你。)

所以,这不是一个纯粹的cpp解决方案,而是一个可能在野外工作的解决方案,它不会调用Undefined Behavior或类似的......有趣......或者

/* CONSTCOND */用于lint,整个do { … } while (0)块通常用于包含控件构造的宏周围,因此可以普遍使用它们。

如果some_code不是代码而是算术表达式

在这种情况下,相同的解决方案变得更短:

#define M(id, ...) ((id) ? some_code(__VA_ARGS__) : void)

如果void为零,则替换最后的id,替换您希望返回的任何内容。 (我不相信这个问题需要保证这一点,因为写some_code(__VA_ARGS__)几乎看起来像是一个接受争论的函数,但有些评论人坚持这一点。)