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

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

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

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


person tez    schedule 15.09.2014    source источник
comment
Нет. Расширение условного макроса невозможно в C.   -  person fuz    schedule 15.09.2014
comment
@FUZxxl, с некоторыми хитростями.   -  person Jens Gustedt    schedule 15.09.2014
comment
возможный дубликат Перегрузка макроса по количеству аргументов   -  person Ciro Santilli 新疆再教育营六四事件ۍ    schedule 21.06.2015


Ответы (5)


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

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

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__))

Есть и недостатки: у вас не может быть вызова с одним аргументом, потому что это оставит запятую в вызове. (##__VA_ARGS__ gcc решает эту проблему.) И _Generic ключевое слово еще не получило широкого распространения; это решение сделает ваш код крайне непереносимым.

Строковый хак самоанализа

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

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

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

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

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

person M Oehm    schedule 15.09.2014

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

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

или как бы вы хотели это назвать. ИМХО, код чище, чем перегрузка PRINT.

person Luis    schedule 15.09.2014

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

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

#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

person Gull_Code    schedule 15.09.2014

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

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

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

person Jens Gustedt    schedule 15.09.2014

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

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
person Leushenko    schedule 15.09.2014
comment
вау, это [умно и] новость для меня, источник для них? - person Luis; 24.09.2014
comment
@Луис пояснить? Весь необходимый исходник (для этого случая) присутствует в примере. - person Leushenko; 24.09.2014
comment
правильно, мне просто любопытно, вы их откуда-то взяли или это ваше собственное изобретение. - person Luis; 24.09.2014
comment
@ Луис, они были готовы ответить на этот вопрос, хотя методы довольно хорошо известны. IS_SPLIT основан на известном макросе NARGS; Я даже не знаю, кто это придумал изначально. - person Leushenko; 24.09.2014