2017-04-05 103 views
1

我相信这是一个坏主意。假设我有一个很好的理由去做。我有一个成功使用静态多态传递消息的节点树。关键的是,每个节点不能连接到的节点类型,它只知道它传递的消息类型。为了遍历树,我使用CRTP实现了访问者模式。这适用于树的第一级。混合双派遣和静态多态性

但是,当遍历树的第二层时,使用下面的AnyNode类删除下一个节点的类型。我一直无法弄清楚如何从删除类型转换为具体类型。下面的例子在测试中起作用,但我认为这也可能是非常危险的,只是在内存恰好布局的地方运气。

似乎有一个问题,我必须在中删除访问者的类型,这在AnyNode::Concept::accept中是完全已知的。但我无法弄清楚如何从概念模型概念(我尝试了协变虚拟cast功能,但没有工作)。而且我无法使用虚拟方法将类型访问者传递给派生模型类,因为虚拟方法无法进行模板化。

有没有一种安全的方式可以拨打node.accept并传递访问者而不必删除访问者的类型,然后静态地将其返回?有什么方法可以在运行时将Concept降级到Model<T>?有没有更好的方法来解决这个问题?是不是有一些疯狂的新的C + + 11的方式来解决这个问题,可能与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); 
    } 
}; 

编辑的访问者基类,并举例衍生游客。派生的访问者通过客户端代码实现(这是一个库的一部分),所以基类不知道访问者将实现什么。我担心这会分散中心问题,但希望它有助于解释这个问题。除了在outlet_visitor::operator()的AnyNode指针上调用->accept(visitor)时,此处的所有内容都可以正常工作。

// 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

为什么'dynamic_cast'没有工作? – 1201ProgramAlarm

+0

是否有数量有限的模型或访问者?他们可以在任何地方被枚举吗?什么是“VisitorBase”?这些是3个问题,请全部回答3. – Yakk

+0

为了回答您的两个问题,我添加了周围的代码。我希望这不是TMI。 @ 1201ProgramAlarm我认为dynamic_cast不起作用,因为'NodeVisitor < T >'只是访问者的类层次结构的一部分。 – Ian

回答

1

啊,我想我现在看到你的问题了。这里dynamic_cast(和static_cast)的问题在于,具有多种类型的NodeVisitor不会生成所有单类型的Visitor类。

在您提供的例子中,类VNodeVisitor< Int_IONode, IntString_IONode > derrived,这将最终产生Visitor< Int_IONode, IntString_IONode >Visitor<IntString_IONode>类作为基地。请注意,未生成Visitor<Int_IONode>。 (visit<Int_IONode>Visitor< Int_IONode, IntString_IONode >。)您也没有NodeVisitor<Int_IONode>NodeVisitor<IntString_IONode>。将任何东西投射到任何一个类都将是Undefined Behavior,因为您正在投射的类不能是其中之一。

为了解决您需要生成所有单类型Visitor类。我觉得这样的事情可能工作(注:未测试):

这将定义单一类型Visitor类中的所有visit方法。

接下来,改变visitoracceptDispatch

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

由于vVisitorBase,如果一切正常宣称这应该得到你想要的Visitor类和含visit方法。

+0

太棒了!这很有效,另外'class Visitor < T >'需要使用虚拟继承('virtual public VirtualBase')从'VirtualBase'继承,以避免死亡。 – Ian

0

不,这是不可能的。

假设您有3个模块。模块1是你的图书馆。模块2定义了一个节点类型。模块3定义了一个访问者。

它们分别编译为二进制动态库,然后在运行时加载。

如果访问者知道节点类型的完整类型,它将能够对节点类型的属性进行任意的编译时检查,确实会改变其行为方式。例如,它在编译时检查静态node_type::value是否编码“P = NP”的证明。

同时,节点类型DLL中没有人使用node_type::value,所以编译器在那里存在它的存在(非常有效)。

要做到你的要求,你必须送不只是node_type编译结果的事,但相当于node_type整个源visitor DLL,并在DLL中他们可以重新编译他们的visitor针对此特定node_type

如果您放宽了一打隐含要求中的任何一个,这是可行的,但您已选择了一组不兼容的要求。很可能你所要求的并不是你实际需要的东西,你只是想问一个非常普遍的要求,并指出它已经足够了,然后就不明白为什么你不能这么做。

+0

所有的模板都在标题中,所以这个工作。 – Ian