web-dev-qa-db-fra.com

Comment obtenir une surcharge de fonctions en C?

Existe-t-il un moyen de surcharger la fonction en C? Je cherche 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 solution simple; Je cherche des solutions de contournement s'il en existe.

227
FL4SOF

Il y a peu de possibilités:

  1. fonctions de style printf (tapez comme argument)
  2. fonctions de style opengl (tapez le nom de la fonction)
  3. c sous-ensemble de c ++ (si vous pouvez utiliser un compilateur c ++)
122
Jacek Ławrynowicz

Oui!

Depuis que cette question a été posée, la norme C (pas d'extensions) a effectivement gagné le support de la surcharge de fonctions (pas d'opérateurs), grâce à l'ajout du mot clé _Generic dans C11. (supporté par GCC depuis la version 4.9)

(La surcharge ne fait pas vraiment partie intégrante de la mode montré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 de la même famille que sizeof et _Alignof. Il est décrit dans la section standard 6.5.1.1. Il accepte deux paramètres principaux: une expression (qui ne sera pas évaluée au moment de l'exécution) et une liste d'associations type/expression qui ressemble un peu à un bloc switch. _Generic récupère le type général de l'expression puis "bascule" dessus 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 est évaluée comme suit: 2 - le type de l'expression de contrôle est int. Elle choisit donc l'expression associée à int. Rien de 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, une erreur de compilation se produira.)

Cela est utile en cas de surcharge de fonction, car elle peut être insérée par le préprocesseur C et choisir une expression de résultat en fonction du type des arguments transmis à 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 opération surchargée cbrt en envoyant le type d'argument à la macro, en choisissant une fonction d'implémentation appropriée, puis en transmettant l'argument de 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 montre pas comment étendre le principe à plusieurs arguments. Le résultat final est que vous pouvez utiliser foo(...) dans votre code sans vous soucier (beaucoup [1]) du type de ses arguments.


Pour des situations plus compliquées, par exemple fonctions surchargeant un plus grand nombre d’arguments, ou des nombres variables, vous pouvez utiliser des macros d’utilitaires pour générer automatiquement des structures de dispatch 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"
}

( implementation here ) Donc, avec un peu d’effort, vous pouvez réduire la quantité de passe-partout à ressembler beaucoup à une langue avec un support natif pour la surcharge.

En passant, il était déjà possible de surcharger le nombre d'arguments (pas le type) dans C99.


[1] notez que la façon dont C évalue les types peut vous trébucher. Ceci choisira foo_int si vous essayez de lui passer un littéral de caractère, par exemple, et vous aurez besoin de déconner un pe si vous voulez que vos surcharges prennent en charge les littéraux de chaîne. Globalement, c'est quand même assez cool.

211
Leushenko

Comme déjà indiqué, la surcharge dans le sens que vous voulez dire n’est pas supportée par C. Un idiome courant pour résoudre le problème est de faire accepter par la fonction un tagged union . Ceci est implémenté par un paramètre struct, où struct est lui-même 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); 
}
75
a2800276

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

regardez __builtin_types_compatible_p, puis utilisez-le pour définir une macro faisant 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

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

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

Voici l'exemple le plus clair et le plus concis que j'ai trouvé, illustrant la surcharge de fonctions 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

17
Jay Taylor

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
Nautical

Cela n’aidera peut-être pas du tout, mais si vous utilisez clang, vous pouvez utiliser l’attribut overloadable - Ceci fonctionne même lors de la compilation en C

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

Entête

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

La mise en oeuvre

void __attribute__((overloadable)) DecodeImageNow(CGImageRef image, CGContextRef usingContext { ... }
void __attribute__((overloadable)) DecodeImageNow(CGImageRef image) { ... }
11
Steazy

L’approche suivante est similaire à celle de a2800276, mais avec l’ajout de la magie des macros C99:

// 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
Christoph

Dans le sens où 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 transmettre des informations sur le nombre de variables et leurs types dans le premier argument, comme le fait printf().

10
Quassnoi

Normalement, une verrue indiquant le type est ajoutée ou ajoutée au nom. Vous pouvez vous en tirer avec des macros dans certains cas, 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 complexes peuvent être insérées dans la macro. Le symbole foo (x) peut alors prendre en charge différents types d'opération, mais vous ne pouvez pas modifier le comportement entre différentes surcharges. Si vous voulez des fonctions réelles plutôt que des macros, vous pourrez peut-être coller le type dans le nom et utiliser un deuxième collage pour y accéder (je n'ai pas essayé).

6
Pete Kirkham

La réponse de Leushenko C'est vraiment cool - uniquement: l'exemple foo ne compile pas avec GCC, qui échoue à la foo(7), trébuchant sur la macro FIRST et la fonction réelle call ((_1, __VA_ARGS__), restant avec une virgule en excès. De plus, nous sommes en difficulté si nous voulons fournir des surcharges supplémentaires, telles que foo(double).

J'ai donc décidé de préciser un peu plus la réponse, notamment pour permettre une surcharge de vide (foo(void) - qui a causé pas mal de problèmes ...).

L'idée est maintenant la suivante: définissez plusieurs génériques dans différentes macros et laissez le bon sélectionner 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 soit en SELECT_1 ou SELECT_2 (ou en plusieurs arguments, si vous le souhaitez/vous 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 vide. Cependant, celle-ci n’est en fait pas couverte par le standard C, qui ne permet pas les arguments variadiques vides, i. e. 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. La macro de comptage renvoie donc 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 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 fonctions ne sont pas développées si elles ne sont pas suivies de parenthèses, pour plus d'explications, jetez un coup d'œil au blog de Jens ... Il suffit de modifier les macros en fonction de nos besoins (je vais utiliser des noms plus courts et moins d’arguments en faveur de 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 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;
}
4
Aconcagua

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 strict, je recommanderais plutôt fonctions variadiques .

0
Tim Matthews