2015-12-07 2 views
6

(Je comprends que C ne soit pas destiné à être utilisé de manière fonctionnelle Cependant, ayant la programmation fonctionnelle appris, j'ai du mal à penser différemment..)Comment soulever une fonction pour prendre un paramètre supplémentaire en C?

Compte tenu de ces restrictions:

  • Aucune fonctions imbriquées parce que clang l'interdit (donc pas d'expressions lamda)
  • pas variables globales

peut-on penser à une façon de soulever une fonction f pour prendre un paramètre supplémentaire pour adapter un prototype, comme e ce nouveau paramètre sera ignoré lorsque f est exécuté?

Voici en détail ce que je veux faire:

Je veux adapter une fonction f dont le type est:

f :: void f(char *s) 

dans une fonction g qui prend une fonction comme un argument (appeler arg), dont le type est la suivante:

arg :: void f(unsigned int i, char *s) 

Ainsi, le type de g est:

g :: void g(void (*f) (unsigned int, char)) 

La solution dans haskell serait la suivante:

g (const f) 

Est-ce même possible, peut-être avec une sorte de magie macro?

EDIT: Pour mieux comprendre, voici le code actuel. Le corps de ft_striter doit être complété. Le but est: appliquer la fonction f à chaque caractère d'une chaîne, en utilisant ou non son index i.

void  ft_striter(char *s, void (*f) (char *s)) 
{ 
    ft_striteri(?); 
} 

static void ft_striteri_helper(char *s, unsigned int i, void (*f)(unsigned int, char*)) 
{ 
    if (*s) 
    { 
     f(i, s); 
     ft_striteri_helper(s + 1, i + 1, f); 
    } 
} 

void  ft_striteri(char *s, void (*f)(unsigned int, char*)) 
{ 
    ft_striteri_helper(s, 0, f); 
} 
+0

Est-ce que 'f' est fixe ou variable? – fuz

+0

Une façon de répondre à cette question serait de trouver en quelque sorte un moyen de permettre à f d'être visible par arg, de pouvoir appeler f dans son corps de fonction. – qleguennec

+0

f est une variable relative à g – qleguennec

Répondre

4

Vous ne pouvez pas implémenter cela dans le portable C. Le problème est que la fonction g. est mal conçu. Il devrait avoir le type

void g(void (*f) (void *, unsigned int, char), void *ctxt) 

et il devrait passer son argument ctxt comme premier argument à tous f appels qu'il fait.

Vous pouvez maintenant mettre en œuvre ce que vous voulez avec le code comme

struct const_ctxt { 
    void (*fun)(char *); 
} 

void const(void *ctxt, unsigned int i, char *s) 
{ 
    ((struct const_ctxt *)ctxt)->fun(s); 
} 

void call_g_using_const_f(void (*f)(char *)) 
{ 
    struct const_ctxt *ctxt = malloc(sizeof (struct const_ctxt)); 
    ctxt->fun = f; 
    g(const, (void *)ctxt); 
    free(ctxt); 
} 

Attention: Si les arguments g pourraient échapper à la portée dynamique de g alors vous aurez besoin de trouver une autre stratégie pour gérer l'affectation des ctxt s.

Le motif g prenant une paire d'un pointeur de code et un pointeur de "contexte"/"environnement" de données est la façon dont les langages de niveau supérieur implémentent généralement des fermetures.

+0

Cela pourrait être la réponse la plus proche, mais malheureusement, cela ne peut pas fonctionner parce que les prototypes sont immuables, il doit être exactement comme indiqué. Je vais cependant valider cette réponse. – qleguennec

+0

@qleguennec En C, les bibliothèques bien conçues impliquant des rappels utilisent souvent ce type d'idiome pour obtenir quelque chose de similaire à des fermetures fonctionnelles. Une fermeture peut être implémentée en tant que terme lambda entièrement abstrait (sans variables libres) associé à un environnement (fournissant les valeurs pour les premiers arguments 'k'). En C, cela est rendu comme un pointeur de fonction, plus un pointeur «void *» vers l'environnement/contexte. Si une bibliothèque ne fournit pas cette fonctionnalité, il n'y a pas de solution de contournement portable. Même la bibliothèque standard échoue à ce ('qsort'), cependant: -/ – chi

+0

Cela va probablement vous déranger et je suis désolé à ce sujet. Après avoir lu la documentation, j'ai compris cette solution et c'est la bonne réponse à mon problème. Cependant, et je reconnais que je ne l'ai pas déclaré dans mon post, je ne suis pas autorisé à déclarer une structure ni un typedef dans un fichier c. Je pourrais déclarer les structures dont j'ai besoin dans mon en-tête, mais ensuite je devrais trouver un nom pour chacune d'entre elles. Je suppose que je ne peux pas utiliser cette solution, mais merci quand même, j'ai appris quelque chose! – qleguennec

2

Puisque vous ne souhaitez que d'accepter et d'ignorer le paramètre supplémentaire, vous pouvez accomplir cela en créant une fonction enveloppe avec la signature requise, que simplement les délégués à la fonction d'origine:

void f2(unsigned int i, char *s) { 
    f(s); 
} 

Avec un bonne déclaration de cette fonction portée, vous pouvez alors appeler simplement

g(f2); 

fonction f2() peut être déclarée statique si vous le souhaitez, de sorte qu'il ne soit pas visible pour coder en dehors du fichier dans lequel il est défini. Il n'a pas besoin d'être imbriqué.

+0

f n'est pas visible à la fonction que vous avez écrite – qleguennec

+0

@qleguennec, pourquoi pas? Sûrement que 'f()' est visible à 'f2()' ne dépend pas directement de 'f2()' lui-même. –

+0

@qleguennec Cela n'a aucun sens. Peut-être que vous devriez éditer votre question et fournir le programme de squelette C pour démontrer le problème. – user694733

-1

Eh bien, considérons quelques options.

Tout d'abord, C n'a pas d'expressions lambda, bien que le dernier standard C++ le fasse. Cela pourrait valoir la peine de considérer C++ au lieu de C si c'est un problème majeur pour vous.

C peut définir et passer function pointers. Donc est parfaitement légal pour passer une fonction en tant que paramètre à une autre fonction. Vous pouvez également définir functions to take a variable number of arguments, ce qui peut également vous être utile.

Des macros C peuvent également être utiles. Vous pouvez, par exemple faire:

#define f1(x) g((x), 1) 
#define f2(x) g((x), 2) 

int g(int x, int y) 
{ 
    return (x + y) ; 
} 

Vous pouvez même faire des choses intelligentes avec des macros pour permettre macro overloading. Ce n'est pas exactement idéal en termes de style de codage, mais c'est possible.

Donc, il peut y avoir quelques outils pour vous ce que vous voulez.

Mise à jour

L'OP a ajouté une mise à jour à son poste pendant que je faisais la mienne, si je comprends son but est une suggestion grossière ici. Comme il semble que l'OP souhaite que la fonction choisie ait accès à une variable d'index qui peut être définie ailleurs, il pourrait essayer d'utiliser une structure pour les paramètres. Peut-être quelque chose comme:

typedef struct mystring_s { 
    char *s ; 
    int i ; 
    } mystring_t ; 

void f1(mystring_t *strp) 
{ 
    /* can access strp->s and strp->i */ 
} 

/* and similar for other functions f2, f2, etc. */ 

/* And to use this you can call any function using 
*/ 

void g(void (*fn)(mystring_t *), mystring_t *strp) 
{ 
    (*fn)(strp) ; 
} 

void set_i(mystring_t *strp, int v) 
{ 
    strp->i = v ; 
} 

/* for example */ 

mystring_t s ; 

/* set up s */ 

set_i(&s, 11) ; 
g(&f1, &s) ; 
set_i(&s, 31) ; 
g(&f2, &s) ; 

Bien sûr, il pourrait également essayer d'utiliser une valeur globale (dont il ne semble pas vouloir ou pense est pas possible), et il pourrait même stocker un pointeur de fonction avec les données. Difficile de savoir avec certitude si c'est une idée réalisable pour l'OP, car il essaie vraiment de faire quelque chose pour lequel C n'est pas conçu. Je pense que le problème est qu'il a une idée de design dans sa tête et c'est un mauvais choix pour C. Il vaudrait peut-être mieux regarder comment ces exigences sont apparues, et modifier cette conception globale pour quelque chose de plus convivial que d'essayer et faire en sorte que C fasse quelque chose pour remplacer les capacités de Haskell.

+0

Les fonctions Varargs ne sont pas pertinentes au problème, car les fonctions impliquées * ne sont pas des fonctions * varargs. Aucune valeur de retour, via varargs ou autre, n'autorise un pointeur de fonction de type 'void (*) (unsigned int, char *)' à porter des informations sur ce que la fonction * other * doit appeler la fonction pointée. Cela doit être déterminé d'une manière ou d'une autre par l'implémentation de la fonction. –

+0

Les macros ne sont pas utiles dans le cas de l'OP, car il veut une solution * dynamic *, pas statique. –

+0

Possiblement C++ et lambdas seraient une alternative, mais l'OP a affirmé que ce n'est pas le cas. A moins d'avoir des raisons de le contredire sur ce point, il est inutile de suggérer lambdas (ou C++). –