Is it possible to expand a macro which accepts multiple arguments to a different macro if first argument is not the expected value
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); */
}
I would have tried this kind of over loading if I knew total number of args.
I really recommend to write two separate macros for this, just as you would write two differently named functions for the two signatues in C. (I would rather write macros that tell you what level they are explicitly, like ERROR(...)
, WARNING(..)
etc. than introduce a default argument.)
That said, there are two possibilities to achieve what you want.
C11 _Generic
selections
The _Generic
keyword was introduced with C11. It allows to expand macros in a switch
-like manner according to the type of an argument; Robert Gamble has a good introduction.
You want to distinguish two cases: First argument is a string and first argument is an integer. A drawback is that in _Generic
, a string literal isn't treated as char *
or const char *
, but as char[size]
. For example, "%d"
is a char[3]
.
In your case, we can get around this by treating a string as anything that isn't an integer. The compiler will sort out all non-string, non-integer arguments later. So:
#define PRINT(fmt, ...) \
_Generic(fmt, \
int: syslog(fmt, __VA_ARGS__), \
default: syslog(3, fmt, __VA_ARGS__))
There are drawbacks: You can't have a single-argument call, because that would leave a comma in the call. (gcc's ##__VA_ARGS__
gets around that.) And the _Generic
keyword is not yet widely implemented; this solution will make your code highly unportable.
String introspection hack
Ordinary C99 macros have no information on their type. C code can make a guess, however. Here's an example that checks whether a macro argument is a string literal:
#define PRINT(sev, ...) \
if (#sev[0] == '"') syslog(3, sev, __VA_ARGS); \
else syslog(sev, __VA_ARGS__);
This works -- almost. The compiler will probably compile the constant condition away and only gererate code for one of the branches. But it will parse the branches anyway and the dead branch will have a wrong function signature, which will generate warnings.
You can get around this by writing a variadic front-end function in C. Here's an example that works:
#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;
}
This solution is dangerous, because the msg
function is only safe if it is generated by the macro. And the macro is only safe if the format string is a string literal beginning with a double quote. The macro expands the arguments by one boolean argument to the left and hides the argument incompatibility in a variadic argument list.
It may be a nice trick, but you'll be better off having separate, clearly named macros.
C macros do not have the ability to inspect their arguments. As noted in the answer you posted, there is a sneaky way to do different things based on the number of arguments, but that's the extent of it. If you already have a variable number of arguments outside of the overload you are trying to do, it will not be possible. If all you need is a default level:
#define PRINTNORM(...) PRINT(3, __VA_ARGS__)
or whatever you'd like to call it. IMHO, cleaner code than overloading PRINT
.
Simply use another value for your need. And perhaps a bit of magic with variadic macro would help.
something like:
#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 ;
Edit: Doc on variadic macro: https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html
P99 has conditional macro evaluation. Here you could probably use something like P99_IF_EMPTY
for something like
#define PRINT(LEV, ...) my_print(P99_IF_EMPTY(LEV)(3)(LEV), __VA_ARGS__)
this would still have you insert a ,
for the case of the empty argument but comes probably close to what you want to achieve.
Optional arguments coming before other mandatory arguments can potentially be handled by folding them together in parentheses:
PRINT((2, "%d%d\n"), i, j);
PRINT("%d%d\n", i, j);
Define PRINT
like this:
#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
detects whether the first argument is an atom (just the format string) or a parenthesized list of two elements (printlevel + string), and expands into the real implementation PRINT_LEVEL
accordingly, either extracting the level from the first argument, or supplying a default value.
Definitions for IS_SPLIT
and the other helpers are as follows:
#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
来源:https://stackoverflow.com/questions/25848461/expanding-a-macro-to-a-different-default-macro-if-an-argument-is-missing