Ответ Леушенко действительно классный - исключительно: пример foo
не компилируется с GCC, который завершается ошибкой при foo(7)
, спотыкаясь о макросе FIRST
и фактическом вызове функции ((_1, __VA_ARGS__)
, оставаясь с лишней запятой. Кроме того, у нас проблемы, если мы хотим обеспечить дополнительные перегрузки, такие как foo(double)
.
Поэтому я решил развить ответ немного дальше, в том числе допустить пустую перегрузку (foo(void)
- это вызвало некоторые проблемы ...).
Идея теперь такова: определите более одного универсального в разных макросах и позвольте выбрать правильный в соответствии с количеством аргументов!
Количество аргументов довольно простое, исходя из этого ответа :
#define foo(...) SELECT(__VA_ARGS__)(__VA_ARGS__)
#define SELECT(...) CONCAT(SELECT_, NARG(__VA_ARGS__))(__VA_ARGS__)
#define CONCAT(X, Y) CONCAT_(X, Y)
#define CONCAT_(X, Y) X ## Y
Это хорошо, мы разрешаем либо SELECT_1
, либо SELECT_2
(или больше аргументов, если вы хотите / нуждаетесь в них), поэтому нам просто нужны соответствующие определения:
#define SELECT_0() foo_void
#define SELECT_1(_1) _Generic ((_1), \
int: foo_int, \
char: foo_char, \
double: foo_double \
)
#define SELECT_2(_1, _2) _Generic((_1), \
double: _Generic((_2), \
int: foo_double_int \
) \
)
ОК, я уже добавил void перегрузку & ndash; однако, этот на самом деле не охватывается стандартом C, который не допускает пустых переменных аргументов, т.е. е. тогда мы полагаются на расширения компилятора !
Вначале пустой вызов макроса (foo()
) по-прежнему создает токен, но пустой. Таким образом, счетный макрос фактически возвращает 1 вместо 0 даже при пустом вызове макроса. Мы можем «легко» устранить эту проблему, если поставить запятую после __VA_ARGS__
условно , в зависимости от того, пустой список или нет:
#define NARG(...) ARG4_(__VA_ARGS__ COMMA(__VA_ARGS__) 4, 3, 2, 1, 0)
Это выглядело просто, но макрос COMMA
довольно тяжелый; К счастью, эта тема уже освещена в блоге Jens Gustedt (спасибо, Jens). Основная хитрость в том, что макросы функций не раскрываются, если за ними не следуют круглые скобки, для дальнейших объяснений загляните в блог Йенса ... Нам просто нужно немного изменить макросы в соответствии с нашими потребностями (я собираюсь использовать более короткие имена и меньше аргументов для краткости).
#define ARGN(...) ARGN_(__VA_ARGS__)
#define ARGN_(_0, _1, _2, _3, N, ...) N
#define HAS_COMMA(...) ARGN(__VA_ARGS__, 1, 1, 1, 0)
#define SET_COMMA(...) ,
#define COMMA(...) SELECT_COMMA \
( \
HAS_COMMA(__VA_ARGS__), \
HAS_COMMA(__VA_ARGS__ ()), \
HAS_COMMA(SET_COMMA __VA_ARGS__), \
HAS_COMMA(SET_COMMA __VA_ARGS__ ()) \
)
#define SELECT_COMMA(_0, _1, _2, _3) SELECT_COMMA_(_0, _1, _2, _3)
#define SELECT_COMMA_(_0, _1, _2, _3) COMMA_ ## _0 ## _1 ## _2 ## _3
#define COMMA_0000 ,
#define COMMA_0001
#define COMMA_0010 ,
// ... (all others with comma)
#define COMMA_1111 ,
А теперь у нас все хорошо ...
Полный код в одном блоке:
/*
* demo.c
*
* Created on: 2017-09-14
* Author: sboehler
*/
#include <stdio.h>
void foo_void(void)
{
puts("void");
}
void foo_int(int c)
{
printf("int: %d\n", c);
}
void foo_char(char c)
{
printf("char: %c\n", c);
}
void foo_double(double c)
{
printf("double: %.2f\n", c);
}
void foo_double_int(double c, int d)
{
printf("double: %.2f, int: %d\n", c, d);
}
#define foo(...) SELECT(__VA_ARGS__)(__VA_ARGS__)
#define SELECT(...) CONCAT(SELECT_, NARG(__VA_ARGS__))(__VA_ARGS__)
#define CONCAT(X, Y) CONCAT_(X, Y)
#define CONCAT_(X, Y) X ## Y
#define SELECT_0() foo_void
#define SELECT_1(_1) _Generic ((_1), \
int: foo_int, \
char: foo_char, \
double: foo_double \
)
#define SELECT_2(_1, _2) _Generic((_1), \
double: _Generic((_2), \
int: foo_double_int \
) \
)
#define ARGN(...) ARGN_(__VA_ARGS__)
#define ARGN_(_0, _1, _2, N, ...) N
#define NARG(...) ARGN(__VA_ARGS__ COMMA(__VA_ARGS__) 3, 2, 1, 0)
#define HAS_COMMA(...) ARGN(__VA_ARGS__, 1, 1, 0)
#define SET_COMMA(...) ,
#define COMMA(...) SELECT_COMMA \
( \
HAS_COMMA(__VA_ARGS__), \
HAS_COMMA(__VA_ARGS__ ()), \
HAS_COMMA(SET_COMMA __VA_ARGS__), \
HAS_COMMA(SET_COMMA __VA_ARGS__ ()) \
)
#define SELECT_COMMA(_0, _1, _2, _3) SELECT_COMMA_(_0, _1, _2, _3)
#define SELECT_COMMA_(_0, _1, _2, _3) COMMA_ ## _0 ## _1 ## _2 ## _3
#define COMMA_0000 ,
#define COMMA_0001
#define COMMA_0010 ,
#define COMMA_0011 ,
#define COMMA_0100 ,
#define COMMA_0101 ,
#define COMMA_0110 ,
#define COMMA_0111 ,
#define COMMA_1000 ,
#define COMMA_1001 ,
#define COMMA_1010 ,
#define COMMA_1011 ,
#define COMMA_1100 ,
#define COMMA_1101 ,
#define COMMA_1110 ,
#define COMMA_1111 ,
int main(int argc, char** argv)
{
foo();
foo(7);
foo(10.12);
foo(12.10, 7);
foo((char)'s');
return 0;
}