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