2017-04-05 8 views
1

Je suis sûr que c'est une mauvaise idée. Faisons comme si j'avais une bonne raison de le faire. J'ai un arbre de noeuds qui utilise avec succès le polymorphisme statique pour transmettre des messages. Fondamentalement, chaque nœud ne peut pas les types de nœuds auxquels il se connecte, il ne connaît que les types de messages qu'il transmet. Pour parcourir l'arborescence, j'ai implémenté le modèle de visiteur en utilisant CRTP. Cela fonctionne pour le premier niveau de l'arbre. Cependant, lors du passage à la deuxième couche de l'arborescence, le type du nœud suivant est effacé à l'aide de la classe AnyNode ci-dessous. J'ai été incapable de comprendre comment baisser du type effacé au type concret. L'exemple ci-dessous fonctionne dans les tests, mais je pense que c'est probablement aussi vraiment dangereux et que je travaille juste par la chance d'où la mémoire arrive à être présentée.Mélange double répartition et polymorphisme statique

Il semble problématique que je dois effacer le type du visiteur dans AnyNode::Model<T>::acceptDispatch, qui est entièrement connu dans AnyNode::Concept::accept. Mais je ne peux pas comprendre comment baisser du concept au modèle dans le concept (j'ai essayé une fonction virtuelle covariant cast, mais cela n'a pas fonctionné). Et je ne peux pas passer le type de visiteur à la classe Model dérivée en utilisant une méthode virtuelle, car les méthodes virtuelles ne peuvent pas être modélisées.

Y at-il un moyen sûr d'appeler node.accept et de passer le visiteur sans avoir à effacer le type du visiteur, puis statique le repousser? Existe-t-il un moyen de réduire le concept à Model<T> lors de l'exécution? Y a-t-il une meilleure façon d'aborder ce problème? N'y a-t-il pas une nouvelle façon de résoudre ce problème, peut-être avec SFINAE?

class AnyNode 
{ 
    struct Concept 
    { 
     virtual ~Concept() = default; 

     template< typename V > 
     void accept(V & visitor) 
     { 
      acceptDispatch(&visitor); 
     } 

     virtual void acceptDispatch(VisitorBase *) = 0; 
    }; 

    template< typename T > 
    struct Model : public Concept 
    { 
     Model(T &n) : node(n) {} 

     void acceptDispatch(VisitorBase * v) override 
     { 
      // dynamic cast doesn't work, probably for good reason 
      NodeVisitor<T>* visitor = static_cast< NodeVisitor<T>* >(v); 
      std::cout << "CAST" << std::endl; 
      if (visitor) { 
       std::cout << "WAHOO" << std::endl; 
       node.accept(*visitor); 
      } 
     } 

    private: 
     T &node; 
    }; 

    std::unique_ptr<Concept> mConcept; 
public: 

    template< typename T > 
    AnyNode(T &node) : 
      mConcept(new Model<T>(node)) {} 


    template< typename V > 
    void accept(V & visitor) 
    { 
     mConcept->accept(visitor); 
    } 
}; 

EDIT est ici les classes de base des visiteurs, et un exemple dérivé visiteur. Les visiteurs dérivés sont implémentés par le code client (cela fait partie d'une bibliothèque), donc les classes de base ne peuvent pas savoir quels visiteurs seront implémentés. J'ai peur que cela distrait de la question centrale, mais j'espère que cela aide à expliquer un peu le problème. Tout fonctionne ici, sauf lorsque ->accept(visitor) est appelée sur le pointeur AnyNode dans outlet_visitor::operator().

// Base class for anything that implements accept 
class Visitable 
{ 
public: 
}; 


// Base class for anything that implements visit 
class VisitorBase 
{ 
public: 
    virtual ~VisitorBase() = default; 
}; 

// Visitor template class 

template< typename... T > 
class Visitor; 

template< typename T > 
class Visitor<T> : public VisitorBase 
{ 
public: 
    virtual void visit(T &) = 0; 
}; 

template< typename T, typename... Ts > 
class Visitor< T, Ts... > : public Visitor<Ts...> 
{ 
public: 
    using Visitor<Ts...>::visit; 

    virtual void visit(T &) = 0; 
}; 

template< class ... T > 
class NodeVisitor : public Visitor<T...> 
{ 
public: 

}; 

// Implementation of Visitable for nodes 

template< class V > 
class VisitableNode : public Visitable 
{ 
    template< typename T > 
    struct outlet_visitor 
    { 
     T &visitor; 
     outlet_visitor(T &v) : visitor(v) {} 


     template< typename To > 
     void operator()(Outlet<To> &outlet) 
     { 
      for (auto &inlet : outlet.connections()) { 
       auto n = inlet.get().node(); 
       if (n != nullptr) { 
        // this is where the AnyNode is called, and where the 
        // main problem is 
        n->accept(visitor); 
       } 
      } 
     } 
    }; 

public: 
    VisitableNode() 
    { 
     auto &_this = static_cast< V & >(*this); 
     _this.each_in([&](auto &i) { 
      // This is where the AnyNode is stored on the inlet, 
      // so it can be retrieved by the `outlet_visitor` 
      i.setNode(*this); 
     }); 
    } 

    template< typename T > 
    void accept(T &visitor) 
    { 
     auto &_this = static_cast< V & >(*this); 
     std::cout << "VISITING " << _this.getLabel() << std::endl; 

     visitor.visit(_this); 

     // The outlets are a tuple, so we use a templated visitor which 
     // each_out calls on each member of the tuple using compile-time 
     // recursion. 
     outlet_visitor<T> ov(visitor); 
     _this.each_out(ov); 
    } 
}; 

// Example instantiation of `NodeVistor<T...>` 

class V : public NodeVisitor< Int_IONode, IntString_IONode > { 
public: 

    void visit(Int_IONode &n) { 
     cout << "Int_IONode " << n.getLabel() << endl; 
     visited.push_back(n.getLabel()); 
    } 

    void visit(IntString_IONode &n) { 
     cout << "IntString_IONode " << n.getLabel() << endl; 
     visited.push_back(n.getLabel()); 
    } 

    std::vector<std::string> visited; 
}; 
+0

Pourquoi 'dynamic_cast' ne fonctionne-t-il pas? – 1201ProgramAlarm

+0

Y a-t-il un nombre fini de modèles ou de visiteurs? Peuvent-ils être énumérés n'importe où? Qu'est-ce qu'un 'VisitorBase'? Ce sont 3 questions, s'il vous plaît répondre tous les 3. – Yakk

+0

Dans l'espoir de répondre à vos deux questions, j'ai ajouté le code environnant. J'espère que ce n'est pas TMI. @ 1201ProgramAlarm Je crois que dynamic_cast ne fonctionne pas parce que 'NodeVisitor < T >' est seulement une partie de la hiérarchie des classes du visiteur. – Ian

Répondre

1

Ah, je pense que je vois vos problèmes maintenant. Le problème ici avec dynamic_cast (et static_cast ainsi) est qu'un NodeVisitor avec plusieurs types ne génère pas toutes les classes Visitor à un seul type.

Dans votre exemple fourni, la classe V est derrived de NodeVisitor< Int_IONode, IntString_IONode >, qui finira par générer Visitor< Int_IONode, IntString_IONode > et Visitor<IntString_IONode> classes comme bases. Notez que Visitor<Int_IONode> n'est pas généré. (visit<Int_IONode> est dans Visitor< Int_IONode, IntString_IONode >.) Vous n'avez pas non plus NodeVisitor<Int_IONode> ou NodeVisitor<IntString_IONode>. Lancer n'importe quoi dans n'importe quelle classe sera Undefined Behavior puisque la classe que vous lancez ne peut pas être l'une d'entre elles.

Pour résoudre ce problème, vous devez générer toutes les classes de type unique Visitor. Je pense que quelque chose comme cela pourrait fonctionner (REMARQUE: non testé):

Cela définira toutes les méthodes visit dans le type Visitor unique classes.

Ensuite, le changement visitor dans acceptDispatch à

auto visitor = dynamic_cast< Visitor<T>* >(v); 

Depuis v est un VisitorBase, si tout est bien déclaré cela devrait vous rendre à la classe Visitor désirée et la méthode contenue visit.

+0

Brillant! Cela a fonctionné, avec l'ajout que 'class Visitor < T >' doit hériter de 'VirtualBase' en utilisant l'héritage virtuel (' virtual public VirtualBase') afin d'éviter le diamant de la mort. – Ian

0

Non, ce n'est pas possible.

Supposons que vous ayez 3 modules. Le module 1 est votre bibliothèque. Le module 2 définit un type de noeud. Le module 3 définit un visiteur.

Ils sont compilés séparément dans des bibliothèques dynamiques binaires, puis chargés au moment de l'exécution.

Si le visiteur connaissait le type complet du type de nœud, il pourrait effectuer des vérifications arbitraires au moment de la compilation sur les propriétés du type de nœud et en modifier le comportement. Par exemple, il vérifie au moment de la compilation si le node_type::value statique code une preuve de "P = NP" ou non.

Pendant ce temps, personne dans le type de nœud DLL n'utilise node_type::value, donc son existence même est optimisée (assez valablement) par le compilateur.

Pour faire ce que vous demandez, vous auriez à envoyer pas seulement le résultat compilé de node_type, mais quelque chose d'équivalent à la source toute de node_type à la DLL visitor, et dans cette DLL ils pourraient recompiler leur visitor contre ce particulier node_type.

Si vous avez assoupli l'une des douzaines d'exigences implicites, cela est possible, mais vous avez choisi un ensemble de requêtes incompatibles. Très probablement ce que vous demandez n'est pas ce dont vous avez réellement besoin, vous avez juste pensé à demander des demandes très générales et noté que c'était suffisant, puis étonné pourquoi vous ne pouviez pas le faire.

+0

Tous les modèles sont dans les en-têtes, donc cela fonctionne. – Ian