Comment réaliser une surcharge de fonctions en C?

Existe-t-il un moyen de surcharger la 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 solution simple Je recherche des solutions de contournement, le cas échéant.

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 ++)

Oui!

Depuis que cette question a été posée, le standard C (pas d’extensions) a été efficacement pris en charge pour la surcharge de fonctions (pas les 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” à la manière indiquée dans la question, mais il est difficile de mettre en œuvre quelque chose qui fonctionne comme ça.)

_Generic est un opérateur de compilation dans la même famille que sizeof et _Alignof . Il est décrit dans la section standard 6.5.1.1. Il accepte deux parameters principaux: une expression (qui ne sera pas évaluée à l’exécution) et une liste d’association de type / expression qui ressemble un peu à un bloc de 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 , il choisit donc l’expression associée à int comme valeur. Rien de tout cela rest à l’exécution. (La clause default est facultative: si vous la laissez et que le type ne correspond pas, cela entraînera une erreur de compilation.)

La façon dont cela est utile pour la surcharge de fonctions est qu’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 du standard C):

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

Cette macro implémente une opération cbrt surchargée, 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 le faire:

 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 par 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 complexes, par exemple des fonctions surchargeant un 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 certain effort, vous pouvez réduire la quantité de passe-partout à ressembler à un langage avec un support natif pour la surcharge.

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


[1] Notez que la façon dont C évalue les types peut vous faire trébucher. Cela va choisir foo_int si vous essayez de lui passer un littéral de caractère, par exemple, et vous devez vous occuper un peu si vous voulez que vos surcharges prennent en charge les littéraux de chaîne. Toujours dans l’ensemble plutôt cool cependant.

Comme cela a déjà été dit, la surcharge dans le sens que vous voulez dire n’est pas prise en charge par C. Un idiome commun pour résoudre le problème est que la fonction accepte une union balisée . Ceci est implémenté par un paramètre struct , où la struct elle struct même consiste en une sorte d’indicateur de type, tel qu’un enum , et une union des différents types de valeurs. Exemple:

 #include  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); } 

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 macro et obtenir le résultat souhaité en termes d’appel,

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 pas

EDIT: C1X va prendre en charge les expressions génériques de type:

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

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

 #include  #include  #include  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

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 va sortir 0 et bonjour .. de printA et printB.

L’approche suivante est similaire à celle de a2800276 , mais avec de la magie de macro C99 ajoutée:

 // we need `size_t` #include  // 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  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; } 

Cela peut ne pas vous aider du tout, mais si vous utilisez clang, vous pouvez utiliser l’atsortingbut surchargeable – Cela fonctionne même lors de la compilation en C

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

Entête

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

la mise en oeuvre

 void __atsortingbute__((overloadable)) DecodeImageNow(CGImageRef image, CGContextRef usingContext { ... } void __atsortingbute__((overloadable)) DecodeImageNow(CGImageRef image) { ... } 

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() .

Normalement, une verrue pour indiquer que le type est ajouté ou ajouté au nom. Vous pouvez vous en sortir avec certaines macros, mais cela dépend plutôt de ce que vous essayez de faire. Il n’y a pas de polymorphism 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 placées dans la macro. Vous pouvez alors avoir le symbole foo (x) pour 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 pouvez coller le type au nom et utiliser un second collage pour y accéder (je n’ai pas essayé).

La réponse de Leushenko est vraiment cool – uniquement: l’exemple foo ne comstack pas avec GCC, qui échoue à foo(7) , trébuchant sur la macro FIRST et l’appel de fonction réel ( (_1, __VA_ARGS__) , avec une virgule excédentaire). nous sums en difficulté si nous voulons fournir des surcharges supplémentaires, telles que foo(double) .

J’ai donc décidé de développer la réponse un peu plus loin, notamment pour permettre une surcharge vide ( foo(void) – ce qui a causé pas mal de problèmes…).

L’idée est maintenant: Définir plus d’un générique dans différentes macros et laisser 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 SELECT_1 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 void – cependant, celle-ci n’est pas couverte par le standard C, ce qui ne permet pas les arguments variadiques vides, c’est-à-dire que nous nous appuyons alors sur les extensions du compilateur !

Tout d’abord, un appel de macro vide ( foo() ) produit toujours un jeton, mais vide. Donc, la macro de comptage renvoie en fait 1 au lieu de 0 même sur un appel de macro vide. On peut “facilement” éliminer ce problème, si on place la virgule après __VA_ARGS__ conditionnellement , en fonction de la liste 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 fonction ne sont pas développées si elles ne sont pas suivies de parenthèses, pour plus d’explications, consultez le blog de Jens … Il suffit de modifier les macros selon nos besoins (je vais utiliser des noms plus courts) et moins d’arguments pour la concision).

 #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  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; } 

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

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

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

 #include  #include 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 *)); }