如果缺少参数,则将宏扩展为不同的默认宏

时间:2014-09-15 12:45:37

标签: c c-preprocessor variadic

如果第一个参数不是预期值,是否可以扩展一个接受多个参数到另一个宏的宏

E.g

int main()
{
    PRINT(2, "%d%d\n", i, j); //should expand to syslog(2, "%d%d\n", i, j)
    PRINT("%d%d\n", i, j); //arg1 which is expected to be an int is not preset.
    /* This should expand differently may be to a default level say 3. syslog(3, "%d%d\n", i,j); */
}

如果我知道args的总数,我会尝试this过载。

5 个答案:

答案 0 :(得分:2)

我真的建议为此编写两个单独的宏,就像你为C中的两个signatues编写两个不同命名的函数一样。(我宁愿编写宏来告诉你它们的显式级别,如{{1} },ERROR(...)等,而不是引入默认参数。)

也就是说,实现你想要的东西有两种可能性。

C11 WARNING(..)选择

C11引入了_Generic关键字。它允许根据参数的类型以类似_Generic的方式扩展宏; Robert Gamble有good introduction

您想区分两种情况:第一个参数是字符串,第一个参数是整数。缺点是在switch中,字符串文字不会被视为_Genericchar *,而是被视为const char *。例如,char[size]"%d"

在你的情况下,我们可以通过将字符串视为非整数的任何东西来解决这个问题。编译器将在以后对所有非字符串,非整数参数进行排序。所以:

char[3]

有一些缺点:您不能进行单参数调用,因为这会在调用中留下逗号。 (gcc的##__VA_ARGS__可以解决这个问题。)#define PRINT(fmt, ...) \ _Generic(fmt, \ int: syslog(fmt, __VA_ARGS__), \ default: syslog(3, fmt, __VA_ARGS__)) 关键字尚未广泛实施;此解决方案将使您的代码非常难以移植。

字符串内省黑客

普通C99宏没有关于其类型的信息。但是,C代码可以猜测。这是一个检查宏参数是否为字符串文字的示例:

_Generic

这很有效 - 差不多。编译器可能会编译常量条件,并且只为其中一个分支创建代码。但无论如何它都将解析分支,死分支将有一个错误的函数签名,这将产生警告。

你可以通过在C中编写一个可变前端函数来解决这个问题。这是一个有效的例子:

#define PRINT(sev, ...)                            \
    if (#sev[0] == '"') syslog(3, sev, __VA_ARGS); \
    else syslog(sev, __VA_ARGS__);

此解决方案很危险,因为#include <stdlib.h> #include <stdio.h> #include <stdarg.h> #define HEAD(X, ...) X #define STR_(x) #x #define STR(x) STR_(x) #define PRINT(...) \ msg(*STR(HEAD(__VA_ARGS__)) == '"', __VA_ARGS__) int msg(int dflt, ...) { va_list va; int sev = 3; const char *fmt; va_start(va, dflt); if (!dflt) sev = va_arg(va, int); fmt = va_arg(va, const char *); fprintf(stderr, "[%d] ", sev); vfprintf(stderr, fmt, va); fprintf(stderr, "\n"); va_end(va); return 0; } int main() { PRINT(1, "Incompatible types %s and %s", "Apple", "Orange"); PRINT("Microphone test: %d, %d, %d, ...", 1, 2, 3); return 0; } 函数只有在宏生成时才是安全的。如果格式字符串是以双引号开头的字符串文字,则宏只是安全的。宏将参数扩展为左边的一个布尔参数,并在可变参数列表中隐藏参数不兼容。

这可能是一个很好的技巧,但你最好有单独的,明确命名的宏。

答案 1 :(得分:1)

根据您的需要使用其他值。或许有一些变量宏的魔法会有所帮助
类似的东西:

#define PRINT( print_level , print_string , ... )\
    switch( print_level ) \
        /* as many syslog cas as needed */
        case( 5 ):\
        case( 4 ):\
        case( 3 ):\
        case( 2 ):\
        case( 2 ):\
        case( 1 ):\
           syslog( print_level , __VA_ARGS__ );\
        break ; \
        default: \
        case( 0 ): \
           printf( __VA_ARGS__ ); \ /* else we simply want to print it */
        break ; 

编辑: 可变宏的文档:https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html

答案 2 :(得分:1)

C宏无法检查其参数。正如你发布的答案中所指出的,根据参数的数字,有一种偷偷摸摸的方式来做不同的事情,但这是它的范围。如果您在尝试执行的重载之外已经有可变数量的参数,则无法实现。如果您只需要一个默认级别:

#define PRINTNORM(...) PRINT(3, __VA_ARGS__)

或者您想要称之为的任何内容。恕我直言,比重载PRINT更清晰的代码。

答案 3 :(得分:1)

P99有条件的宏评估。在这里你可能会使用像P99_IF_EMPTY这样的东西,比如

#define PRINT(LEV, ...) my_print(P99_IF_EMPTY(LEV)(3)(LEV), __VA_ARGS__)

这仍然会为空参数的情况插入,,但可能接近你想要达到的目的。

答案 4 :(得分:1)

之前的可选参数可以通过在括号中将它们折叠在一起来处理其他必需参数:

PRINT((2, "%d%d\n"), i, j);
PRINT("%d%d\n", i, j);

像这样定义PRINT

#define PRINT(SL, ...) PRINT_LEVEL(APPLY(CAT(LEVEL, IS_SPLIT(SL)), IDENTITY SL), APPLY(CAT(FSTRING, IS_SPLIT(SL)), IDENTITY SL), __VA_ARGS__)
#define PRINT_LEVEL(LEVEL, ...) syslog(LEVEL, __VA_ARGS__)

PRINT检测第一个参数是原子(只是格式字符串)还是两个元素的括号列表(printlevel + string),然后相应地扩展到实际的实现PRINT_LEVEL中第一个参数的级别,或提供默认值。

IS_SPLIT和其他助手的定义如下:

#define LEVEL_0(_S) 3
#define LEVEL_1(L, S) L
#define FSTRING_0(S) K_##S
#define FSTRING_1(L, S) S

#define CAT(A, B) CAT_(A, B)
#define CAT_(A, B) A ## B

#define APPLY(F, ...) F(__VA_ARGS__)
#define IDENTITY(...) __VA_ARGS__
#define K_IDENTITY

#define IS_SPLIT(...) IS_SPLIT_1(IDENTITY __VA_ARGS__)
#define IS_SPLIT_1(...) IS_SPLIT_2(__VA_ARGS__, _1, _0, _)
#define IS_SPLIT_2(_X, _Y, R, ...) R