Расширение макроса до другого макроса по умолчанию, если отсутствует аргумент

Можно ли расширить макрос, который принимает несколько аргументов для другого макроса, если первый аргумент не является ожидаемым значением

например

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); */
}

Я бы попробовал этот > вид более загруженной, если бы знал общее количество аргументов.

Ответ 1

Я действительно рекомендую написать два отдельных макроса для этого, так же, как вы бы написали две разные именованные функции для двух символов в C. (Я бы предпочел писать макросы, которые говорят вам, на каком уровне они явно, например ERROR(...), WARNING(..) и т.д., чем ввести аргумент по умолчанию.)

Тем не менее, есть две возможности достичь того, чего вы хотите.

C11 _Generic выбор

Ключевое слово _Generic было введено с помощью C11. Он позволяет расширять макросы по типу switch в соответствии с типом аргумента; Роберт Гэмбл имеет хорошее введение.

Вы хотите выделить два случая: Первый аргумент - это строка, а первый аргумент - целое число. Недостатком является то, что в _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__ обходит это.) И ключевое слово _Generic еще не широко внедрено; это решение сделает ваш код очень неспортивным.

Строка интроспекции взлома

Обычные макросы C99 не имеют информации об их типе. Однако код C может сделать предположение. Здесь пример, который проверяет, является ли аргумент макроса строковым литералом:

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

Это работает - почти. Компилятор, вероятно, скомпилирует неизменное условие и только свернет код для одной из ветвей. Но он все равно будет разбирать ветки, а мертвая ветвь будет иметь неправильную сигнатуру функции, которая будет генерировать предупреждения.

Вы можете обойти это, написав переменную функцию front-end в C. Вот пример, который работает:

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

Это решение опасно, поскольку функция msg является безопасной только в том случае, если она создается макросом. И макрос только безопасен, если строка формата является строковым литералом, начинающимся с двойной кавычки. Макрос расширяет аргументы одним логическим аргументом влево и скрывает несовместимость аргумента в списке вариационных аргументов.

Это может быть хороший трюк, но вам будет лучше иметь отдельные, четко названные макросы.

Ответ 2

Просто используйте другое значение для ваших нужд. И, возможно, немного волшебства с переменным макросом помогут.

что-то вроде:

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

Изменить: Doc о переменном макросе: https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html

Ответ 3

Макросы

C не имеют возможности проверять свои аргументы. Как отмечалось в ответе, который вы опубликовали, есть проницательный способ делать разные вещи на основе количества аргументов, но в той степени, в которой это происходит. Если у вас уже есть переменное количество аргументов за пределами перегрузки, которые вы пытаетесь сделать, это будет невозможно. Если вам нужен только уровень по умолчанию:

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

или что бы вы ни назвали. IMHO, более чистый код, чем перегрузка PRINT.

Ответ 4

P99 имеет условную макрооценку. Здесь вы, вероятно, можете использовать что-то вроде P99_IF_EMPTY для чего-то вроде

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

это все равно вы введете , для случая пустого аргумента, но, вероятно, приблизитесь к тому, что вы хотите достичь.

Ответ 5

Необязательные аргументы, возникающие перед другими обязательными аргументами, могут быть обработаны путем сворачивания их в круглых скобках:

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 обнаруживает, является ли первый аргумент атомом (только строка формата) или список из двух элементов (печатный уровень + строка) в скобках, и соответственно расширяется в реальную реализацию 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