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