【问题标题】:Mixing double dispatch and static polymorphism混合双重调度和静态多态性
【发布时间】:2017-04-05 00:27:46
【问题描述】:

我确定这是个坏主意。让我们假装我有充分的理由这样做。我有一棵成功使用静态多态性传递消息的节点树。至关重要的是,每个节点不能知道它连接的节点的类型,它只知道它传递的消息的类型。为了遍历树,我使用 CRTP 实现了访问者模式。这适用于树的第一级。

但是,当遍历到树的第二层时,下一个节点的类型会使用下面的 AnyNode 类擦除。我一直无法弄清楚如何从擦除类型向下转换为具体类型。下面的示例在测试中有效,但我认为它也可能非常危险,并且只是靠碰巧布置内存的位置来工作。

我必须删除AnyNode::Model<T>::acceptDispatch 中的访问者类型似乎有问题,这在AnyNode::Concept::accept 中是完全已知的。但我不知道如何从概念向下转换到概念中的模型in(我尝试了协变虚拟cast 函数,但没有奏效)。而且我不能使用虚拟方法将类型化的访问者传递给派生的模型类,因为虚拟方法不能被模板化。

有没有一种安全的方法来调用node.accept 并传递访问者,而不必删除访问者的类型然后将其静态转换回来?有什么方法可以在运行时将概念向下转换为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 指针上调用 -&gt;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;
};

【问题讨论】:

  • 为什么dynamic_cast 不起作用?
  • 模型或访问者的数量是否有限?它们可以在任何地方枚举吗?什么是VisitorBase?这是 3 个问题,请全部回答 3 个。
  • 为了回答你的两个问题,我添加了周围的代码。希望不是TMI。 @1201ProgramAlarm 我认为 dynamic_cast 不起作用,因为 NodeVisitor&lt; T &gt; 只是访问者类层次结构的一部分。
  • @Yakk 访问者或模型的数量不是有限的,因为访问者和节点是由库的客户端实现的。但是,正如您在编辑中看到的那样,模型应该都有一个对应的 VisitableNode CRTP 类。我的一个问题是,如果某种模板魔法可以让我枚举访问者类型,而无需对它们进行硬编码。 VisitorBase 只是访客的占位符类,但始终通过 Visitor&lt; T... &gt; 派生。

标签: c++ c++11 templates visitor-pattern double-dispatch


【解决方案1】:

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

在您提供的示例中,V 类派生自 NodeVisitor&lt; Int_IONode, IntString_IONode &gt;,最终将生成 Visitor&lt; Int_IONode, IntString_IONode &gt;Visitor&lt; IntString_IONode &gt; 类作为基础。请注意,不会生成 Visitor&lt; Int_IONode &gt;。 (visit&lt;Int_IONode&gt;Visitor&lt; Int_IONode, IntString_IONode &gt; 中。)您也没有NodeVisitor&lt; Int_IONode &gt;NodeVisitor&lt; IntString_IONode &gt;。将任何内容投射到任一类都将是未定义的行为,因为您从中投射的类不能是其中任何一个。

要解决这个问题,您需要生成所有单一类型的 Visitor 类。我认为这样的事情可能有效(注意:未测试):

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

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

接下来,将acceptDispatch中的visitor更改为

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

由于vVisitorBase,如果所有内容都正确声明,这应该可以让您获得所需的Visitor 类和包含的visit 方法。

【讨论】:

  • 太棒了!这很有效,此外class Visitor&lt; T &gt; 需要使用虚拟继承 (virtual public VirtualBase) 从 VirtualBase 继承,以避免死亡钻石。
【解决方案2】:

不,这是不可能的。

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

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

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

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

要按照您的要求进行操作,您不仅要发送 node_type 的编译结果,还要将与 node_type整个源代码等效的内容发送到 visitor DLL ,并且在那个 DLL 中,他们可以针对这个特定的 node_type 重新编译他们的 visitor

如果您放宽了十几个隐含要求中的任何一个,这是可行的,但是您选择了一组不兼容的要求。很可能你所要求的并不是你真正需要的,你只是想问一些非常笼统的要求,并注意到这已经足够了,然后不明白为什么你不能这样做。

【讨论】:

  • 所有模板都在标题中,所以这行得通。
猜你喜欢
  • 2011-12-21
  • 1970-01-01
  • 1970-01-01
  • 2015-12-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多