Comment réaliser une surcharge de fonction en C?

Existe-t-il un moyen d'obtenir une surcharge de fonction en C? Je regarde des fonctions simples à surcharger comme

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

Je pense qu'il n'y a pas de moyen simple; je cherche des solutions de contournement s'il en existe.

208
demandé sur Gaurang Tandon 2009-01-26 12:20:35

14 réponses

Il y a peu de possibilités:

  1. fonctions de style printf (type en argument)
  2. fonctions de style opengl (entrez le nom de la fonction)
  3. sous-ensemble c de C++ (si vous pouvez utiliser un compilateur C++)
113
répondu Jacek Ławrynowicz 2009-01-26 09:24:41

Oui!

Depuis que cette question a été posée, la norme C (pas d'extensions) a effectivement pris en charge pour la surcharge de fonctions (pas d'opérateurs), grâce à l'ajout du mot clé _Generic dans C11. (pris en charge 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 compilation dans la même famille comme sizeof et _Alignof. Elle est décrite à 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 bloc switch. _Generic obtient le type global de l'expression, puis "bascule" dessus pour sélectionner l'expression de 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 est évaluée à 2 - le type de l'expression de contrôle est int, donc elle choisit le expression associée à int comme valeur. Rien de tout cela ne reste à l'exécution. (La clause default est facultative: si vous la laissez désactivée 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'il peut être inséré par le préprocesseur C et choisir une expression de résultat en fonction du type des arguments passés à la macro de contrôle. Donc (exemple de la norme C):

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

Cette macro implémente une surcharge cbrt opération, en répartissant sur le type de l'argument à la macro, en choisissant une fonction d'implémentation appropriée, puis en passant l'argument de la macro d'origine à cette fonction.

Donc, pour implémenter 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))(_1, __VA_ARGS__)
#define FIRST(A, ...) A

Dans ce cas, nous aurions pu utiliser une association default: pour le troisième cas, mais cela ne démontre pas comment étendre le principe à plusieurs arguments. Le résultat final est que vous pouvez utiliser foo(...) dans votre code, sans inquiétant (beaucoup[1]) sur le type de ses arguments.


Pour des situations plus compliquées, par exemple des fonctions surchargeant un plus grand nombre d'arguments, ou des nombres variables, vous pouvez utiliser des macros utilitaires pour générer automatiquement des structures de répartition statiques:

void print_ii(int a, int b) { printf("int, int\n"); }
void print_di(double a, int b) { printf("double, int\n"); }
void print_iii(int a, int b, int c) { printf("int, int, int\n"); }
void print_default(void) { printf("unknown arguments\n"); }

#define print(...) OVERLOAD(print, (__VA_ARGS__), \
    (print_ii, (int, int)), \
    (print_di, (double, int)), \
    (print_iii, (int, int, int)) \
)

#define OVERLOAD_ARG_TYPES (int, double)
#define OVERLOAD_FUNCTIONS (print)
#include "activate-overloads.h"

int main(void) {
    print(44, 47);   // prints "int, int"
    print(4.4, 47);  // prints "double, int"
    print(1, 2, 3);  // prints "int, int, int"
    print("");       // prints "unknown arguments"
}

(implémentation ici ) donc, avec un peu d'effort, vous pouvez réduire la quantité de passe-partout à ressembler à un langage avec un support natif pour la surcharge.

En aparté, c'était déjà possible de surcharger sur le Nombre {[24] } d'arguments (pas le type) en C99.


[1] notez que la façon dont C évalue les types peut vous faire trébucher. Cela choisira foo_int Si vous essayez de lui passer un littéral de caractère, par exemple, et vous devez gâcher un peu si vous voulez que vos surcharges prennent en charge les littéraux de chaîne. Encore dans l'ensemble assez cool cependant.

190
répondu Leushenko 2017-05-23 12:10:31

Comme déjà indiqué, la surcharge dans le sens que vous voulez dire n'est pas supportée par C. un idiome commun pour résoudre le problème consiste à faire accepter à la fonction une union étiquetée . Ceci est implémenté par un paramètre struct, où le struct lui-même est constitué d'une sorte d'indicateur de type, tel qu'un enum et un union des différents types de valeurs. Exemple:

#include <stdio.h>

typedef enum {
    T_INT,
    T_FLOAT,
    T_CHAR,
} my_type;

typedef struct {
    my_type 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); 
}
71
répondu a2800276 2014-11-09 00:30:56

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

Regardez _ _ builtin _ types_compatible_P, puis utilisez-le 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, juste ne pas

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

#define cbrt(X) _Generic((X), long double: cbrtl, \
                              default: cbrt, \
                              float: cbrtf)(X)
19
répondu Spudd86 2011-07-13 21:13:51

Voici l'exemple le plus clair et le plus concis que j'ai trouvé démontrant la surcharge de fonction en C:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int addi(int a, int b) {
    return a + b;
}

char *adds(char *a, char *b) {
    char *res = malloc(strlen(a) + strlen(b) + 1);
    strcpy(res, a);
    strcat(res, b);
    return res;
}

#define add(a, b) _Generic(a, int: addi, char*: adds)(a, b)

int main(void) {
    int a = 1, b = 2;
    printf("%d\n", add(a, b)); // 3

    char *c = "hello ", *d = "world";
    printf("%s\n", add(c, d)); // hello world

    return 0;
}

Https://gist.github.com/barosl/e0af4a92b2b8cabd05a7

16
répondu Jay Taylor 2018-03-21 15:54:46

Oui, en quelque sorte.

Ici, vous allez par 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 affichera 0 et bonjour .. de printA et printB.

13
répondu Nautical 2014-07-08 07:41:24

L'approche suivante est similaire à celle de a2800276 , mais avec quelques macros magiques C99 ajoutées:

// we need `size_t`
#include <stddef.h>

// argument types to accept
enum sum_arg_types { SUM_LONG, SUM_ULONG, SUM_DOUBLE };

// a structure to hold an argument
struct sum_arg
{
    enum sum_arg_types type;
    union
    {
        long as_long;
        unsigned long as_ulong;
        double as_double;
    } value;
};

// determine an array's size
#define count(ARRAY) ((sizeof (ARRAY))/(sizeof *(ARRAY)))

// this is how our function will be called
#define sum(...) _sum(count(sum_args(__VA_ARGS__)), sum_args(__VA_ARGS__))

// create an array of `struct sum_arg`
#define sum_args(...) ((struct sum_arg []){ __VA_ARGS__ })

// create initializers for the arguments
#define sum_long(VALUE) { SUM_LONG, { .as_long = (VALUE) } }
#define sum_ulong(VALUE) { SUM_ULONG, { .as_ulong = (VALUE) } }
#define sum_double(VALUE) { SUM_DOUBLE, { .as_double = (VALUE) } }

// our polymorphic function
long double _sum(size_t count, struct sum_arg * args)
{
    long double value = 0;

    for(size_t i = 0; i < count; ++i)
    {
        switch(args[i].type)
        {
            case SUM_LONG:
            value += args[i].value.as_long;
            break;

            case SUM_ULONG:
            value += args[i].value.as_ulong;
            break;

            case SUM_DOUBLE:
            value += args[i].value.as_double;
            break;
        }
    }

    return value;
}

// let's see if it works

#include <stdio.h>

int main()
{
    unsigned long foo = -1;
    long double value = sum(sum_long(42), sum_ulong(foo), sum_double(1e10));
    printf("%Le\n", value);
    return 0;
}
11
répondu Christoph 2009-01-26 10:25:46

Cela peut ne pas aider du tout, mais si vous utilisez clang, vous pouvez utiliser l'attribut overloadable-cela fonctionne même lors de la compilation en tant que C

Http://clang.llvm.org/docs/AttributeReference.html#overloadable

En-tête

extern void DecodeImageNow(CGImageRef image, CGContextRef usingContext) __attribute__((overloadable));
extern void DecodeImageNow(CGImageRef image) __attribute__((overloadable));

Mise en œuvre

void __attribute__((overloadable)) DecodeImageNow(CGImageRef image, CGContextRef usingContext { ... }
void __attribute__((overloadable)) DecodeImageNow(CGImageRef image) { ... }
11
répondu Steazy 2015-04-11 08:52:58

Dans le sens que vous voulez dire-non, vous ne pouvez pas.

Vous pouvez déclarer une fonction va_arg comme

void my_func(char* format, ...);

, mais vous devrez passer une sorte d'information sur le nombre de variables et leurs types dans le premier argument - comme le fait printf().

9
répondu Quassnoi 2009-01-26 09:25:26

Normalement, une verrue pour indiquer le type est ajoutée ou ajoutée au nom. Vous pouvez vous en sortir avec des macros, mais cela dépend plutôt de ce que vous essayez de faire. Il n'y a pas de polymorphisme en C, seulement de la coercition.

Des opérations génériques simples peuvent être effectuées avec des macros:

#define max(x,y) ((x)>(y)?(x):(y))

Si votre compilateur prend en charge typeof , des opérations plus compliquées peuvent être mises dans la macro. Vous pouvez alors avoir le symbole foo (x) pour supporter la même opération différents types, mais vous ne pouvez pas varier le comportement entre les différentes surcharges. Si vous voulez des fonctions réelles plutôt que des macros, vous pourrez peut-être coller le type au nom et utiliser un second collage pour y accéder (Je n'ai pas essayé).

6
répondu Pete Kirkham 2009-01-28 10:10:33

La réponse de Leushenko est vraiment cool-uniquement: l'exemple foo ne compile pas avec GCC, qui échoue à foo(7), trébuchant sur la macro FIRST 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, telles que foo(double).

J'ai donc décidé d'élaborer la réponse un peu plus loin, y compris 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!

Nombre d'arguments est assez facile, basée 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 soit SELECT_1 ou SELECT_2 (ou plus d'arguments, si vous voulez/avez besoin d'eux), donc nous avons 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 vide-cependant, celle-ci n'est pas couverte par la norme C, ce qui ne permet pas les arguments variadiques vides, c'est-à-dire que nous nous appuyons alors sur les extensions du compilateur !

Au tout début, un appel de macro vide (foo()) produit toujours un jeton, mais un jeton vide. Ainsi, la macro de comptage renvoie réellement 1 au lieu de 0 même sur 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)

Que semblait facile, mais la macro COMMA est assez lourde; heureusement, le sujet est déjà couvert 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 oeil au blog de Jens... Nous devons juste Modifier les macros un peu à 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 dans un 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;
}
3
répondu Aconcagua 2017-09-14 15:13:48

Ne pouvez-vous pas simplement utiliser C++ et ne pas utiliser toutes les autres fonctionnalités c++ sauf celle-ci?

Si toujours pas de C juste strict alors je recommanderais fonctions variadiques à la place.

1
répondu Tim Matthews 2009-01-26 09:30:10

Essayez de déclarer ces fonctions comme extern "C++" si votre compilateur le supporte, http://msdn.microsoft.com/en-us/library/s6y4zxec (VS.80).aspx

-3
répondu dmityugov 2009-01-26 10:45:51

J'espère que le code ci-dessous vous aidera à comprendre la surcharge de fonction

#include <stdio.h>
#include<stdarg.h>

int fun(int a, ...);
int main(int argc, char *argv[]){
   fun(1,10);
   fun(2,"cquestionbank");
   return 0;
}
int fun(int a, ...){
  va_list vl;
  va_start(vl,a);

  if(a==1)
      printf("%d",va_arg(vl,int));
   else
      printf("\n%s",va_arg(vl,char *));
}
-3
répondu Madan Gopal 2016-03-09 04:50:31