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

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

例如

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的总数,我会尝试过这种过载。

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

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

C11 _Generic选择

_Generic关键字是随C11引入的。 它允许根据参数的类型以类似switch的方式扩展宏; Robert Gamble有一个很好的介绍 。

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

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

 #define PRINT(fmt, ...) \ _Generic(fmt, \ int: syslog(fmt, __VA_ARGS__), \ default: syslog(3, fmt, __VA_ARGS__)) 

有一些缺点:您不能进行单参数调用,因为这会在调用中留下逗号。 (gcc的##__VA_ARGS__可以解决这个问题。) ##__VA_ARGS__关键字尚未广泛实现; 此解决方案将使您的代码高度不可移植。

字符串内省黑客

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

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

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

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

 #include  #include  #include  #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; } 

这个解决方案很危险,因为msg函数只有在宏生成时才是安全的。 如果格式字符串是以双引号开头的字符串文字,则宏只是安全的。 宏将参数扩展为左侧的一个布尔参数,并在可变参数列表中隐藏参数不兼容性。

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

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

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

或者你想称之为什么。 恕我直言,比重载PRINT清洁代码。

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

就像是:

 #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

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

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

对于空参数的情况,这仍然可以插入一个,但可能接近你想要实现的目标。

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

 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检测第一个参数是primefaces(只是格式字符串)还是两个元素的括号列表(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