La réponse de Leushenko est vraiment cool - uniquement: l' foo
exemple ne compile pas avec GCC, qui échoue foo(7)
, trébuchant sur la FIRST
macro et l'appel de fonction réel ( (_1, __VA_ARGS__)
, restant avec une virgule excédentaire. De plus, nous sommes en difficulté si nous voulons fournir des surcharges supplémentaires , tel quefoo(double)
.
J'ai donc décidé d'élaborer un peu plus la réponse, notamment pour permettre une surcharge de vide (foo(void)
- ce qui a causé pas mal de problèmes ...).
L'idée est maintenant: Définissez plus d'un générique dans différentes macros et laissez sélectionner le bon en fonction du nombre d'arguments!
Le nombre d'arguments est assez facile, basé sur cette réponse :
#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
C'est bien, nous résolvons l'un SELECT_1
ou l' autre SELECT_2
(ou plusieurs arguments, si vous le souhaitez / en avez besoin), nous avons donc simplement besoin de définitions appropriées:
#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 \
) \
)
OK, j'ai déjà ajouté la surcharge void - cependant, celle-ci n'est en fait pas couverte par la norme C, ce qui ne permet pas d'arguments variadiques vides, c'est-à-dire que nous comptons alors sur des extensions de compilateur !
Au tout début, un appel de macro vide ( foo()
) produit toujours un jeton, mais vide. Ainsi, la macro de comptage renvoie en fait 1 au lieu de 0 même lors d'un appel de macro vide. Nous pouvons "facilement" éliminer ce problème, si nous plaçons la virgule après__VA_ARGS__
conditionnellement , selon que la liste est vide ou non:
#define NARG(...) ARG4_(__VA_ARGS__ COMMA(__VA_ARGS__) 4, 3, 2, 1, 0)
Cela semblait facile, mais la COMMA
macro est assez lourde; heureusement, le sujet est déjà traité dans un blog de Jens Gustedt (merci Jens). L'astuce de base est que les macros de fonction ne sont pas développées si elles ne sont pas suivies de parenthèses, pour plus d'explications, jetez un œil au blog de Jens ... Il suffit de modifier un peu les macros selon nos besoins (je vais utiliser des noms plus courts et moins d'arguments pour la brièveté).
#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 ,
Et maintenant nous allons bien ...
Le code complet en un seul bloc:
/*
* 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;
}