Expanding a macro to a different default macro if an argument is missing

南笙酒味 提交于 2019-12-06 11:25:18

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.

Gull_Code

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
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!