2
votes

Une fonction qui renvoie un pointeur de fonction pour les fonctions de différents types de retour

Le contexte de cette question tourne autour de la programmation génétique typée en dur.

Je voudrais renvoyer un pointeur de fonction à partir d'une fonction mais ces pointeurs de fonction pointent vers des fonctions avec différents types de retour. Dans une autre question de débordement de pile ( Pointeurs de fonction avec différents types de retour C ) un type de retour d'union a été suggéré mais j'ai du mal avec l'implémentation.

Je suis assez nouveau dans le C ++ donc veuillez pardonner mon ignorance si cela se voit.

#include <iostream>
#include <string>

float Add(float a, float b) { return a + b; }
bool IfElse(bool a) { if (a) { return true; } else { return false; }; }

union return_type
{
    float(*ffptr)(float, float);
    bool(*bfptr)(bool);
};  

union fptr(std::string OPERATION) {
    if (OPERATION == "Add") {
        return_type.ffptr = Add;
    } else if (OPERATION == "IfElse") {
        return_type.bfptr = IfElse;
    }

    return return_type;
}

int main() {
    std::cout << fptr("Add") << std::endl
    return 0;
}

J'attends (ou plutôt aimerais) que cela imprime l'adresse de la fonction Ajouter


6 commentaires

Vous utilisez les en-têtes et les espaces de noms corrects, vous êtes donc bien en avance sur beaucoup de gens, nouveaux ou non en C ++. Gloire.


"Ajouter" est-il un paramètre d'exécution?


Je ne pense pas. Je dirais non car il est codé en dur dans main.


Comment allez-vous utiliser le résultat de fptr ?


Ce qui précède est l'extrait minimal. Dans le code réel, il est stocké dans une classe et utilisé pour définir un arbre de calcul.


godbolt.org/z/s2DENw


3 Réponses :


1
votes

Je ne suis pas tout à fait sûr du but ultime, mais voici comment vous pouvez faire la compilation ci-dessus et imprimer l'adresse de la fonction (impression d'un simple Add ajouté à titre de comparaison):

#include <iostream>
#include <string>

float Add(float a, float b) { return a + b; }
bool IfElse(bool a) { return a; }

union return_type
    {
    float(*ffptr)(float, float);
    bool(*bfptr)(bool);
    };  

union return_type fptr(std::string OPERATION) {
    union return_type r;
    if (OPERATION == "Add") {
        r.ffptr = Add;
    } else if (OPERATION == "IfElse") {
        r.bfptr = IfElse;
    }

    return r;
}

int main()
{
    /*the void*-cast is technically nonportable but it's hard to
      print fn-pointers portably*/
    std::cout << reinterpret_cast<void*>(Add) << '\n';

    /*you should know which union member is active: */
    std::cout << reinterpret_cast<void*>(fptr("Add").ffptr) << '\n';
    /*^should be the same address*/
    return 0;
}


2 commentaires

Spot on monsieur, merci! union return_type r; était mon oie d'or.


@JakeDyson Pas de problème. return_type r; fonctionnera également en C ++, BTW (le C brut nécessite le mot-clé union avant le nom de l'union).



2
votes

Vous étiez proche. Veillez à ne pas confondre la déclaration du nom de type (union) et la valeur de retour de la fonction. Puisque vous voulez référencer l'adresse du pointeur, j'ai ajouté un void * à votre union (fpaddr), afin que vous puissiez clairement identifier que vous imprimez une adresse. Notez que votre fptr ("Add") a renvoyé l'union, et que vous deviez lever l'ambiguïté sur l'interprétation de l'union que vous vouliez.

#include <iostream>
#include <string>

float Add(float a, float b) { return a + b; }
bool IfElse(bool a) { if (a) { return true; } else { return false; }; }

//typedef //in C you would use 'typedef'
union fp_return_t
{
    float(* ffptr )(float, float);
    bool(* bfptr )(bool);
    void* fpaddr;
}; //fp_return_t; //in C you would give the name here

fp_return_t fptr(std::string OPERATION) {
    fp_return_t fp_return;
    if (OPERATION == "Add") {
        std::cout << "Add:" << (void*) Add << std::endl;
        fp_return.ffptr = Add;
    } else if (OPERATION == "IfElse") {
        std::cout << "IfElse:" << (void*) IfElse << std::endl;
        fp_return.bfptr = IfElse;
    }

    return fp_return;
}

int main() {
    std::cout << fptr("Add").fpaddr << std::endl;
    return 0;
}


7 commentaires

Merci, malheureusement, votre réponse est arrivée un peu trop tard pour être identifiée comme la réponse, mais vous avez néanmoins détecté l'erreur. Je suis confus quant au but du vide dans les deux réponses, l'esprit en train d'élaborer? Sinon peut-être un autre article SO pour un autre jour :)


L'opérateur << ne sait pas que vous ne voulez que l'adresse de la fonction. Le void * lui donne un indice en transformant le pointeur de fonction en un pointeur anonyme que << ne sait pas comment afficher. Élimine une page ou deux de diagnostics du compilateur.


La réponse choisie est plus `` précise '', mais j'ai ajouté le void * fpaddr pour utiliser l'union pour effectuer le casting, principalement parce que ce serait clair pour un public plus large, et pour illustrer que vous devriez vous qualifier quel membre / type de syndicat utiliser.


Je me trompe sur le volume des diagnostics du compilateur. g ++ crache une ligne et c'est presque lisible. avertissement: l'adresse de 'float Add (float, float)' ne sera jamais NULL n'aura probablement pas beaucoup de sens, mais au moins ce n'est pas un mur de texte intimidant.


Dans l'ensemble, cela signifie-t-il que je devrais connaître le type de membre pour pouvoir l'utiliser?


Je pense que c'est le cas. stackoverflow.com/questions/16623226/...


Oui, vous devez connaître le type de membre. C'est pourquoi vous voyez généralement que ces unions font partie d'une structure (au moins en C). Ainsi, vous pouvez voir ce problème résolu en C ++ à l'aide de l'héritage de classe, où chaque classe de fonction serait dérivée d'une classe de base (probablement abstraite), et C ++ détermine quelle classe dérivée (et donc fonction) a été affectée.



4
votes

Version TL; DR: Je pense que vous essayez peut-être de trouver une solution à l’ajustement un problème . Pensez à utiliser quelque chose comme le modèle de visiteur pour dissocier le problème afin que vous n'ayez pas besoin de savoir le type des données.

Une union n'est pas un type. C'est un type de types, comme une classe ou une struct . Pour utiliser un return_type , vous devez créer un objet qui est un return_type . Cela signifie que

#include <iostream>
#include <string>
#include <variant>

float Add(float a, float b) { return a + b; }
bool IfElse(bool a) { if (a) { return true; } else { return false; }; }

using return_type = std::variant<float (*)(float a, float b), bool (*)(bool a)>;

return_type fptr(std::string OPERATION) {
    return_type result;
    if (OPERATION == "Add") {
        result = Add;
    } else if (OPERATION == "IfElse") {
        result = IfElse;
    }

    return result;
}

int main() {
    std::cout << std::get<float (*)(float a, float b)>(fptr("Add"))(10,20) << std::endl;
    try
    {
        std::cout << std::get<bool (*)(bool a)>(fptr("Add"))(true) << std::endl;
    }
    catch (const std::bad_variant_access& e)
    {
        std::cout << e.what() << std::endl;
    }
        return 0;
}

doit ressembler davantage à

int main() {
    std::cout << fptr("Add").bfptr(true) << std::endl;
    return 0;
}

Alors vous pouvez

int main() {
    std::cout << fptr("Add").ffptr(10,20) << std::endl; // call the stored function
    return 0;
}


2 commentaires

Vous avez anticipé mon prochain problème. Très appréciée.


@JakeDyson Je suis désolé de ne pas avoir de meilleure solution que de faire quelque chose de différent. Si ce n'est pas un visiteur, peut-être si vous avez renvoyé un objet de fonction qui dérive d'un type commun (donc ils se ressemblent tous) et sait comment obtenir les valeurs dont il a besoin à partir des nœuds de votre arbre et effectuer un calcul.