309 votes

surcharge des fonctions en C

Existe-t-il un moyen de réaliser la surcharge des fonctions en C ? Je cherche des fonctions simples à surcharger, par exemple

foo (int a)  
foo (char b)  
foo (float c , int d)

Je pense qu'il n'y a pas de méthode directe ; je cherche des solutions de contournement si elles existent.

327voto

Leushenko Points 2079

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.

141voto

Jacek Ławrynowicz Points 1253

Il y a quelques possibilités :

  1. fonctions de style printf (type comme argument)
  2. fonctions de style opengl (tapez le nom de la fonction)
  3. c subset of c++ (si vous pouvez utiliser un compilateur c++)

88voto

a2800276 Points 1841

Comme il a déjà été dit, la surcharge dans le sens où vous l'entendez n'est pas prise en charge par le C. Un idiome commun pour résoudre le problème est de faire en sorte que la fonction accepte un struct paramètre. Le site struct elle-même serait constituée d'une sorte d'indicateur de type et d'une union des différents types de valeurs. Exemple :

#include <stdio.h>

#define T_INT   1
#define T_FLOAT 2
#define T_CHAR  3

typedef struct {
    int type;
    union {
        int a; 
        float b; 
        char c;
    } my_union;
} my_struct;

void set_overload (my_struct *whatever) 
{
    switch (whatever->type) 
    {
        case T_INT:
            whatever->my_union.a = 1;
            break;
        case T_FLOAT:
            whatever->my_union.b = 2.0;
            break;
        case T_CHAR:
            whatever->my_union.c = '3';
    }
}

void printf_overload (my_struct *whatever) {
    switch (whatever->type) 
    {
        case T_INT:
            printf("%d\n", whatever->my_union.a);
            break;
        case T_FLOAT:
            printf("%f\n", whatever->my_union.b);
            break;
        case T_CHAR:
            printf("%c\n", whatever->my_union.c);
            break;
    }

}

int main (int argc, char* argv[])
{
    my_struct s;

    s.type=T_INT;
    set_overload(&s);
    printf_overload(&s);

    s.type=T_FLOAT;
    set_overload(&s);
    printf_overload(&s);

    s.type=T_CHAR;
    set_overload(&s);
    printf_overload(&s);

}

19voto

Spudd86 Points 2008

Si votre compilateur est gcc et que cela ne vous dérange pas de faire des mises à jour manuelles à chaque fois que vous ajoutez une nouvelle surcharge, vous pouvez faire un peu de magie macro et obtenir le résultat que vous voulez en termes d'appelants, ce n'est pas aussi agréable à écrire... mais c'est possible.

regarder __builtin_types_compatible_p, puis l'utiliser pour définir une macro qui fait quelque chose comme

#define foo(a) \
((__builtin_types_compatible_p(int, a)?foo(a):(__builtin_types_compatible_p(float, a)?foo(a):)

mais oui, méchant, ne le fais pas.

EDIT : C1X obtiendra un support pour les expressions génériques de type qui ressemblent à ceci :

#define cbrt(X) _Generic((X), long double: cbrtl, \
                              default: cbrt, \
                              float: cbrtf)(X)

13voto

Nautical Points 422

Oui, en quelque sorte.

Voici un exemple :

void printA(int a){
printf("Hello world from printA : %d\n",a);
}

void printB(const char *buff){
printf("Hello world from printB : %s\n",buff);
}

#define Max_ITEMS() 6, 5, 4, 3, 2, 1, 0 
#define __VA_ARG_N(_1, _2, _3, _4, _5, _6, N, ...) N
#define _Num_ARGS_(...) __VA_ARG_N(__VA_ARGS__) 
#define NUM_ARGS(...) (_Num_ARGS_(_0, ## __VA_ARGS__, Max_ITEMS()) - 1) 
#define CHECK_ARGS_MAX_LIMIT(t) if(NUM_ARGS(args)>t)
#define CHECK_ARGS_MIN_LIMIT(t) if(NUM_ARGS(args) 
#define print(x , args ...) \
CHECK_ARGS_MIN_LIMIT(1) printf("error");fflush(stdout); \
CHECK_ARGS_MAX_LIMIT(4) printf("error");fflush(stdout); \
({ \
if (__builtin_types_compatible_p (typeof (x), int)) \
printA(x, ##args); \
else \
printB (x,##args); \
})

int main(int argc, char** argv) {
    int a=0;
    print(a);
    print("hello");
    return (EXIT_SUCCESS);
}

Il sortira 0 et hello de printA et printB.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X