2016-12-12 2 views
1

Je veux créer une classe qui contient des fonctions membres statiques et une carte avec des pointeurs vers ces fonctions. Ils peuvent cependant prendre des nombres et des types d'arguments différents. Donc, suivant this thread J'ai essayé quelque chose comme:Comment créer une carte de pointeurs vers des fonctions membres statiques dans C++ 11?

class BeliefCondFunc 
{ 
    static std::unordered_map<std::string, std::function<bool()>> FuncMap; 

    static bool Greater(int A, int B) 
    { 
     return A > B; 
    } 

    static bool Between(float A, float B, float C) 
    { 
     return A > B && A < C; 
    } 

    static void InitMap() 
    { 
     FunctionMap["Greater"] = std::bind(&BeliefCondFunc::Greater, ???); 
     FunctionMap["Between"] = std::bind(&BeliefCondFunc::Between, ???); 
    } 
}; 

Maintenant, qu'est-ce que je dois remplacer le ??? avec le code ci-dessus pour le faire fonctionner? En fin de compte, je veux pouvoir l'appeler comme ceci:

BeliefCondFunc::FuncMap["Greater"](1, 2); 
BeliefCondFunc::FuncMap["Between"](1.0f, 2.0f, 3.0f); 

Est-ce même possible en C++ 11, sans l'utilisation d'une bibliothèque externe comme coup de pouce?

+0

vous devez lancer votre fonction membre statique à un type de fonction non-membre et lier à nouveau. use (bool (*) (int, int)) pour votre exemple – lsbbo

+1

'std :: function ' ne permet pas de stocker une fonction prenant des paramètres. De plus 'static const ... FuncMap = InitMap();' ne fonctionnera peut-être pas, mais cela fonctionnerait si 'InitMap()' était 'constexpr' – Christoph

+0

De plus,' InitMap' traîne une instruction return. – Christoph

Répondre

4

Vous devez remplacer le ??? par des espaces réservés, pour transférer les arguments de la fermeture à la fonction.

std::bind(&BeliefCondFunc::Greater, _1, _2); 

Vous devriez noter cependant que votre carte contient des objets de fonction qui n'acceptent aucun argument. Vous risquez donc d'obtenir une erreur d'incompatibilité d'appel. Depuis C++ 14, probablement même sur l'assignation tentée dans la nouvelle valeur construite. Je sens aussi que vous compliquez trop la solution. Pour les fonctions membres statiques, un type de pointeur de fonction simple peut suffire. Vous devrez ensuite préformer une partie coulée, mais qui peut être encapsulée dans une autre fonction membre statique basée sur un modèle. Maintenant, lancer des types de pointeurs de fonction n'est pas très sûr (pensez à ce qui se passe quand vous obtenez les arguments de nombre faux). Mais vous semblez l'intention de contourner le système de type déjà, la voici:

#include <unordered_map> 
#include <string> 
#include <utility> 

struct BeliefCondFunc 
{ 
    using cb_type = bool(*)(); 
    static std::unordered_map<std::string, cb_type> const FuncMap; 

    static bool Greater(int A, int B) 
    { 
     return A > B; 
    } 

    static bool Between(float A, float B, float C) 
    { 
     return A > B && A < C; 
    } 

    template<typename ...Args> 
    static bool call(std::string const& key, Args&&... args){ 
     using prototype = bool(*)(Args...); 
     return reinterpret_cast<prototype>(FuncMap.at(key))(std::forward<Args>(args)...); 
    }; 
}; 

std::unordered_map<std::string, BeliefCondFunc::cb_type> const BeliefCondFunc::FuncMap { 
    {"Greater", reinterpret_cast<cb_type>(&BeliefCondFunc::Greater) }, 
    {"Between", reinterpret_cast<cb_type>(&BeliefCondFunc::Between) } 
}; 

int main() { 
    BeliefCondFunc::call("Greater", 1, 2); 
    return 0; 
} 

See it live


Si les soucis de coulée vous, vous pouvez stocker std::type_info de pointeurs de fonction prototype pointeur à côté avec elle. Ensuite, il suffit de comparer les deux et de lancer une exception en cas d'incompatibilité au moment de l'exécution. Comme ceci:

#include <unordered_map> 
#include <string> 
#include <utility> 
#include <typeinfo> 
#include <stdexcept> 
#include <iostream> 

struct BeliefCondFunc 
{ 
    using cb_type = bool(*)(); 
    using map_val = std::pair<cb_type, std::type_info const*>; 
    static std::unordered_map<std::string, map_val> const FuncMap; 

    static bool Greater(int A, int B) 
    { 
     return A > B; 
    } 

    static bool Between(float A, float B, float C) 
    { 
     return A > B && A < C; 
    } 

    template<typename ...Args> 
    static map_val make_map_val(bool (*func)(Args...)) { 

     return std::make_pair(reinterpret_cast<cb_type>(func), 
           &typeid(decltype(func))); 
    } 

    template<typename ...Args> 
    static bool call(std::string const& key, Args&&... args){ 
     using prototype = bool(*)(Args...); 

     auto& func_n_typeid = FuncMap.at(key); 

     if (typeid(prototype) != *func_n_typeid.second) 
     throw std::domain_error("Prototype mismatch"); 

     return reinterpret_cast<prototype>(func_n_typeid.first)(std::forward<Args>(args)...); 
    }; 
}; 

std::unordered_map<std::string, BeliefCondFunc::map_val> const BeliefCondFunc::FuncMap { 
    {"Greater", make_map_val(&BeliefCondFunc::Greater) }, 
    {"Between", make_map_val(&BeliefCondFunc::Between) } 
}; 

int main() { 
    BeliefCondFunc::call("Greater", 1, 2); 

    try { 
     BeliefCondFunc::call("Lesser", 1, 2); 
    } catch (std::out_of_range&) { 
     std::cout << "No such function\n"; 
    } 

    try { 
     BeliefCondFunc::call("Between", 1, 2); 
    } catch (std::domain_error&) { 
     std::cout << "Wrong number of arguments\n"; 
    } 

    return 0; 
} 

Encore une fois, see it live.

Et comme promis, une explication:

  1. Les deux lignes suivantes créent deux type aliases (C++ 11 syntaxe, ce qui équivaut à un typedef, mais sépare clairement le nouvel identificateur du type). Je nomme le type de rappel factice, je vais stocker tous les pointeurs comme, et la paire de valeurs que je vais stocker dans la carte.

    using cb_type = bool(*)(); 
    using map_val = std::pair<cb_type, std::type_info const*>; 
    
  2. make_map_val est une fonction de modèle qui peut être transmis tout pointeur de fonction qui renvoie un bool. Chaque fois que je l'appelle avec un pointeur de fonction, il utilise template argument deduction pour déterminer les types d'arguments de la fonction. Je ne sais pas les types d'arguments directement, mais je me base sur decltype pour obtenir le type de la fonction. Rétrospectivement, je pourrais juste passer le pointeur de fonction directement à typeid, puisqu'il aurait déduit le type de la même manière. L'importance d'utiliser une fonction tempérée ici, est de capturer autant de données de type statique sur le pointeur de fonction automatiquement, sans demander au site d'appel de make_map_val pour cela.

  3. call utilise de nouveau la déduction du type d'argument modèle pour déterminer les types de paramètres pour la fonction que nous voulons appeler. Un alias de type est également utilisé pour référencer commodément le type de pointeur de fonction que nous allons lancer, ainsi que le paramètre typeid. L'alias et l'appel de fonction sont écrits à l'aide du paramètre pack expansion. Les paramètres sont transmis à la fonction avec une référence de renvoi, puis au pointeur de fonction via un appel à std::forward, le tout pour préserver la catégorie de valeur du paramètre. Si toutes vos fonctions ne fonctionnent que sur de petites valeurs, peu coûteuses à copier, vous pourriez probablement vous contenter de tout faire passer en valeur.

+0

Alors, est-ce que je vous comprends bien, même avec ces espaces réservés cela ne fonctionnerait pas? Si oui, y a-t-il une solution valide à mon exigence? – Matthias

+1

@Matthias, édité avec une solution possible. – StoryTeller

+0

@Story vous pourriez inclure une note indiquant que ce n'est pas très sûr car vous pourriez appeler "plus grand" avec un nombre quelconque d'arguments. – Christoph

1

si vous voulez utiliser différents types d'objets dans un conteneur il y a 2 façons: 1. héritage + RTTI 2. variante