2
votes

Comment stocker n'importe quel type de fonction dans une variable en C ++?

Comment pourrais-je stocker n'importe quel type de fonction avec n'importe quel nombre d'arguments dans une variable? Quelque chose comme ceci:

int add(int i, int j) {
return i + j;
}

template<typename Function, typename... Args>
class Class {
private:
    std::function<Function(Args...)> function;
public:
    Class(Function _function, Args... _args) {
        Now what?
    }
};

int main() {
    Class test(add, 1, 2);
    test.function();
}


8 commentaires

Que comptez-vous faire de cette collection de fonctions? Comment comptez-vous les appeler?


Tout comme je l'ai démontré dans la fonction principale.


@ user686368 Ce n'est pas vraiment ce que vous voulez. Nous essayons de déterminer si vous n'essayez pas réellement de résoudre un problème XY ici.


@ user686368 Je voulais dire plus généralement. Si vous aviez vraiment besoin d'appeler une seule fonction une seule fois, vous écrivez simplement add (1, 2) . Vous devez expliquer vos contraintes.


Ceci est juste un exemple simple pour illustrer ma question. La classe aurait beaucoup plus de fonctionnalités et la fonction n'en serait qu'une facette.


Oui, et je demande quelle est cette fonctionnalité.


La classe sera un bouton et la fonction sera utilisée lorsque vous appuyez sur le bouton. Les autres fonctionnalités seront des choses comme la position, la profondeur, le cadre et d'autres choses comme ça.


D'accord, oui, c'est une bonne approche alors.


5 Réponses :


5
votes

Vous pouvez essayer quelque chose comme ça

#include <iostream>
#include <functional>
#include <tuple>

int add(int i, int j) {
    return i + j;
}

template<typename Function, typename... Args>
class Class {
private:
    Function function_;
    std::tuple<Args...> args;
public:
    Class(Function _function, Args... _args) :  
        function_ { std::forward<Function>(_function) } ,
        args{std::forward<Args>(_args)...}
    {}

    auto function()
    {
        return std::apply(function_,args);
    }
};

int main() {
    Class test(add, 1, 2);
    std::cout<< test.function() << std::endl ;
}

Démo: https: // wandbox.org/permlink/ZqFSSN2K5HU9HMRm


4 commentaires

Réinvente std :: function !


Eh bien, je n'ai pas pensé à lier. L'avantage de ma solution est qu'il peut changer les paramètres plus tard. Mais je suis d'accord que votre solution est meilleure.


Ma solution peut se relier comme la vôtre


Nécessite d'initialiser le tuple directement, pas avec _ .



3
votes

C'est à cela que sert bind !

Votre Classe est en fait juste std :: bind . Imaginons que vous en ayez une autre utilisation, et faites en sorte qu'il encapsule la logique std :: bind . Nous devons donc stocker une std :: function qui n'accepte aucun autre argument, et renvoie (dans ce cas) int … mais nous la rendrons générique ( ReturnType ).

Ensuite, lors de l'initialisation, vous "liez" les arguments avec lesquels vous avez construit Class , et les transférez dans la std :: function code> (l'utilisation de std :: forward permettant de déplacer la sémantique pour des types d'arguments plus complexes).

#include <functional>
#include <iostream>

int add(int i, int j)
{
    return i + j;
}

int main()
{
    auto func = std::bind(add, 1, 2);
    std::cout << func() << '\n';
}

// Output: 3

( démo en direct )

Ou, si vous n'avez pas besoin de Class code > pour faire autre chose, c'est juste:

#include <functional>
#include <iostream>

int add(int i, int j)
{
    return i + j;
}

template <typename Function, typename... Args>
class Class
{
private:
    using ReturnType = std::invoke_result_t<Function, Args...>;
    std::function<ReturnType()> function;

public:
    Class(Function _function, Args... _args) 
        : function(std::bind(_function, std::forward<Args>(_args)...))
    {}

    auto Call()
    {
        return function();
    }
};

int main() {
    Class test(add, 1, 2);
    std::cout << test.Call() << '\n';
}

// Output: 3

( démo en direct )


Bootnote

Votre type de std::function est faux car Function est le type de la fonction entière, mais vous l'avez utilisé comme un type de retour.


3 commentaires

-1: n'utilisez jamais bind dans le code générique. Si les arguments sont des expressions de liaison / des espaces réservés, les choses deviennent complètement folles d'une manière que personne qui n'a pas implémenté bind ne comprendra probablement. Avoir un cas de coin explosif sans raison valable est une mauvaise idée en code C ++ débutant, intermédiaire ou avancé; et l'éviter avec bind est une vraie douleur.


@ Yakk-AdamNevraumont Je n'ai pas encore implémenté bind, je n'ai jamais eu de problème du genre auquel vous faites référence. Pourriez-vous fournir un exemple?


@LightnessRacesinOrbit Que se passe-t-il si vous transmettez le résultat de std :: bind à std :: bind ? "Si l'argument stocké arg est de type T pour lequel std :: is_bind_expression :: value == true (par exemple, une autre expression de liaison a été passée directement dans l'appel initial à bind), alors bind effectue la composition de la fonction" - - en.cppreference.com/w/cpp/utility/functional/bind - votre exemple de code ci-dessus transmet des arguments génériques à std :: bind , ce qui signifie que le code d'espace réservé / composition entre en jeu, et cela peut se casser de manière ridicule.




2
votes
int main() {
  Class test(add, 1, 2);
  test.function();
}

0 commentaires

1
votes

La réponse acceptée n'est pas bonne car std :: bind peut être totalement remplacé par lambda, l'utilisation de std :: bind est démodée dans le c ++ moderne et peut causer un problème de performances car la construction de std :: function est assez lourde.

[voir Pourquoi utiliser std :: bind sur lambdas en C ++ 14? ]

Permettez-moi de vous présenter c ++ 20 std :: bind_front , cette fonction est destinée à remplacer std::bind:

int add(int i, int j, int k) {
    return i + j + k;
}

auto f1 = std::bind_front(add, 1);
auto f2 = std::bind_front(add, 1, 2);
auto f3 = std::bind_front(add, 1, 2, 3);
// all will print '6'
std::cout << f1(2, 3) << '\n';
std::cout << f2(3) << '\n';
std::cout << f3() << '\n';

Et n'oubliez pas d'inclure l'en-tête :)


0 commentaires