Depuis que cette question a été posée, la norme C (sans extensions) a effectivement a gagné la prise en charge de la surcharge des fonctions (et non des opérateurs), grâce à l'ajout de la fonction _Generic
mot-clé dans C11. (supporté dans GCC depuis la version 4.9)
(La surcharge n'est pas vraiment "intégrée" de la manière indiquée dans la question, mais il est très facile d'implémenter quelque chose qui fonctionne comme ça).
_Generic
est un opérateur de temps de compilation de la même famille que sizeof
et _Alignof
. Il est décrit dans la section 6.5.1.1 de la norme. Il accepte deux paramètres principaux : une expression (qui ne sera pas évaluée à l'exécution), et une liste d'association type/expression qui ressemble un peu à un switch
bloc. _Generic
obtient le type global de l'expression, puis "commute" sur celui-ci pour sélectionner l'expression du résultat final dans la liste pour son type :
_Generic(1, float: 2.0,
char *: "2",
int: 2,
default: get_two_object());
L'expression ci-dessus donne la valeur suivante 2
- le type de l'expression de contrôle est int
et choisit donc l'expression associée à int
comme valeur. Rien de tout cela ne subsiste au moment de l'exécution. (La default
est facultative : si vous la laissez de côté et que le type ne correspond pas, cela provoquera une erreur de compilation).
La façon dont cela est utile pour la surcharge de fonction est qu'elle peut être insérée par le préprocesseur C et choisir une expression de résultat basée sur le type des arguments passés à la macro de contrôle. Ainsi (exemple tiré de la norme C) :
#define cbrt(X) _Generic((X), \
long double: cbrtl, \
default: cbrt, \
float: cbrtf \
)(X)
Cette macro implémente une surcharge de cbrt
en répartissant sur le type de l'argument de la macro, en choisissant une fonction d'implémentation appropriée, puis en passant l'argument original de la macro à cette fonction.
Donc, pour mettre en œuvre votre exemple original, nous pourrions faire ceci :
foo_int (int a)
foo_char (char b)
foo_float_int (float c , int d)
#define foo(_1, ...) _Generic((_1), \
int: foo_int, \
char: foo_char, \
float: _Generic((FIRST(__VA_ARGS__)), \
int: foo_float_int))(__VA_ARGS__)
(assumer FIRST
est une macro permettant de prendre un élément en tête d'une liste de préprocesseur)
Dans ce cas, nous aurions pu utiliser un default:
pour le troisième cas, mais cela ne démontre pas comment étendre le principe à des arguments multiples.
Le résultat final est que vous pouvez utiliser foo(...)
dans votre code sans vous soucier (beaucoup[1]) du type de ses arguments.
C'est plus qu'un peu lourd, mais vous devriez être en mesure de définir quelques macros d'aide pour mettre de l'ordre dans le texte passe-partout si vous finissez par l'utiliser beaucoup (peut-être générer automatiquement certaines des fonctions de résultat selon un schéma de nommage ?)
1] Notez que la façon dont C évalue les types peut vous faire trébucher. Ceci choisira foo_int
si vous essayez de lui passer un caractère littéral, par exemple, et vous devez faire un peu de bricolage si vous voulez que vos surcharges supportent les chaînes de caractères. Mais dans l'ensemble, c'est plutôt cool.