Variadic макрос без аргументов

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

template<typename Value1, typename Value2>
void Log(std::string const& function,
std::string const& variable_1 = "", Value1 value_1 = Value1(0),
std::string const& variable_2 = "", Value2 value_2 = Value2(0)) {
std::cout << function << " "<< variable_1 << " " << value_1 << " "<< variable_2 << " " << value_2 << std::endl;
}
#define LOG0() Log(__PRETTY_FUNCTION__)
#define VARIABLE(value) #value, value
#define LOG1(value) Log(__PRETTY_FUNCTION__, VARIABLE(value))
#define LOG2(value, value1) Log(__PRETTY_FUNCTION__, VARIABLE(value), VARIABLE(value1))
#define LOG(arg0, arg1, arg2, arg, ...) arg
#define CHOOSE(...) LOG(,##__VA_ARGS__, LOG2, LOG1, LOG0)
#define Debug(...) CHOOSE(__VA_ARGS__)(__VA_ARGS__)

Я могу использовать эти макросы как

Debug();
int x = 0;
Debug(x);
int y = 1;
Debug(x, y);

Когда я компилирую этот код с помощью clang, я получаю хороший вывод, содержащий информацию о классе и функции, а также имя и значение переменных.
Но я также получаю предупреждение, что стандартному совместимому коду не разрешено иметь нулевые переменные аргументы.

warning: token pasting of ',' and __VA_ARGS__ is a GNU extension [-Wgnu-zero-variadic-macro-arguments]
#define CHOOSE(...) LOG(,##__VA_ARGS__, LOG2, LOG1, LOG0)
^
warning: must specify at least one argument for '...' parameter of variadic macro [-Wgnu-zero-variadic-macro-arguments]
Debug();

Gcc, с другой стороны, не компилируется с

error: expected primary-expression before ‘)’ token
#define LOG1(value) Log(__PRETTY_FUNCTION__, VARIABLE(value))
^
Debug();

Очевидно, что работать с нулевыми переменными аргументами опасно.

  1. Есть ли способ, которым я могу превратить этот код в стандартный совместимый код, не снимая удобства с одного макроса, который принимает от нуля до двух аргументов?
  2. Если это невозможно, есть ли способ заставить gcc скомпилировать этот код?

7

Решение

Трудной частью этого является различие между Debug() а также Debug(x), В обоих случаях вы технически передаете один аргумент в макрос Debug, В первом случае последовательность токенов этого аргумента пуста, а во втором случае она содержит один токен. Эти случаи можно выделить с трюк из-за Йенса Гастедта.

Вот хитрость:

#define COMMA_IF_PARENS(...) ,

Соблюдайте это COMMA_IF_PARENS X выдает запятую, если X начинается с (...)и в противном случае расширяется до последовательности токенов, не содержащей дополнительных (верхнего уровня) запятых. Точно так же, COMMA_IF_PARENS X () выдает запятую, если X пусто или начинается с (...) иным образом расширяется до последовательности токенов, не содержащей дополнительных (верхнего уровня) запятых. (В каждом случае последовательность токенов также содержит все запятые верхнего уровня из X сам.)

Мы можем использовать этот трюк так:

#define CHOOSE(...) \
LOG(__VA_ARGS__ \
COMMA_IF_PARENS __VA_ARGS__ \
COMMA_IF_PARENS __VA_ARGS__ (), \
CHOICES)

Обратите внимание, что:

  • COMMA_IF_PARENS __VA_ARGS__ производит количество запятых в __VA_ARGS__ плюс 1, если __VA_ARGS__ начинается с (...),
  • COMMA_IF_PARENS __VA_ARGS__ () производит количество запятых в __VA_ARGS__ плюс 1, если __VA_ARGS__ пусто или начинается с (...), (Обратите внимание, что это может не сработать, если __VA_ARGS__ заканчивается именем функционально-подобного макроса, и мы не рассматриваем эту потенциальную проблему здесь.)

Позволять с быть числом запятых в __VA_ARGS__, п быть 1, если __VA_ARGS__ начинается с (...) и 0 в противном случае, и е быть 1, если __VA_ARGS__ пусто и 0 в противном случае.

Количество макро аргументов, созданных до CHOICES это 3 с + 2 п + е. Принято по модулю 3, число запятых равно 0 или 2 для нормального аргумента и 1, если у нас есть пустой список аргументов.

Это дает нам 6 случаев, о которых мы заботимся:

#define CHOICES LOG2, impossible, LOG2, LOG1, LOG0, LOG1
#define LOG(a0, a1, a2, a3, a4, a5, arg, ...) arg

Тем не менее, это не совсем работает, потому что мы должны отложить расширение LOG(...) вызов макроса, пока мы не расширим COMMA_IF_PARENS техника. Один из способов сделать это:

#define LPAREN (
#define EXPAND(...) __VA_ARGS__
#define CHOOSE(...) EXPAND(LOG LPAREN COMMA_IF_PARENS [...]))

Мы также должны добавить еще одну запятую в конце CHOICES так что у нас всегда есть (возможно, пустой) аргумент, соответствующий ... параметр LOG,

Собрав все это вместе, мы получаем это:

#define COMMA_IF_PARENS(...) ,
#define LPAREN (
#define EXPAND(...) __VA_ARGS__
#define CHOOSE(...) \
EXPAND(LOG LPAREN \
__VA_ARGS__ COMMA_IF_PARENS __VA_ARGS__ COMMA_IF_PARENS __VA_ARGS__ (), \
LOG2, impossible, LOG2, LOG1, LOG0, LOG1, ))
#define LOG(a0, a1, a2, a3, a4, a5, arg, ...) arg

со всем остальным неизменным из вашего кода. (Это можно обобщить гораздо дальше, но вышеприведенного достаточно для демонстрации техники.)

6

Другие решения


По вопросам рекламы [email protected]