c++ - 混合双重分派(dispatch)和静态多态性

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

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

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

似乎有问题,我必须删除 AnyNode::Model<T>::acceptDispatch 中的访客类型,这在 AnyNode::Concept::accept 中是完全已知的.但我无法弄清楚如何从 Concept Concept 中的模型向下转换(我尝试了一个协变虚拟 cast 函数,但那没有用)。而且我无法使用虚方法将类型化的 Visitor 传递给派生的 Model 类,因为虚方法无法模板化。

有没有安全的方式调用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 );
    }
};

编辑 这是访问者基类,以及派生访问者的示例。派生的 Visitors 由客户端代码实现(这是库的一部分),因此基类无法知道将实现什么 Visitors。恐怕这会分散中心问题的注意力,但希望它有助于解释问题。这里的一切都有效,除非->accept( visitor )outlet_visitor::operator() 中的 AnyNode 指针上调用.

// 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 的问题(还有static_cast)是一个NodeVisitor具有多种类型不会生成所有单类型 Visitor类。

在您提供的示例中,类 V派生自 NodeVisitor< Int_IONode, IntString_IONode > ,最终会生成 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 > .将任何内容转换到任何一个类都将是未定义的行为,因为您从中转换的类不能是其中任何一个。

要解决这个问题,您需要生成所有单一类型的 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;
};

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

接下来,更改visitoracceptDispatch

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

vVisitorBase ,如果一切都被正确声明,这应该会让你到达所需的 Visitor类和包含的visit方法。

关于c++ - 混合双重分派(dispatch)和静态多态性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43219907/

相关文章:

c++ - 将自定义命令行参数传递给 Rust 测试

c++ - session 0 捕获屏幕

c++ - 异步写入位数组

c++ - std::make_shared() 是否使用自定义分配器?

c++ - 检测同时按下两个键

c++ - 为什么 unordered_set 操作像计数和删除返回一个 size_type?

c++ - 三元运算符不适用于 lambda 函数

java - printf 模板的格式错误字符串问题

C++:模板代码在 clang++ 下编译和运行良好,但在 g++ 下失败

c++ - 如果 constexpr 而不是标签分派(dispatch)