混合双重分派和静态多态性
Mixing double dispatch and static polymorphism
我敢肯定这是个坏主意。让我们假装我有充分的理由这样做。我有一个节点树,它成功地使用静态多态性来传递消息。至关重要的是,每个节点都不知道它所连接的节点的类型,它只知道它传递的消息的类型。为了遍历树,我使用 CRTP 实现了访问者模式。这适用于树的第一层。
但是,当遍历到树的第二层时,使用下面的AnyNode class擦除下一个节点的类型。我一直无法弄清楚如何从擦除类型向下转换为具体类型。下面的例子在测试中有效,但我认为它也可能真的很危险,只是运气好,内存恰好被布置了。
似乎有问题,我必须删除 AnyNode::Model<T>::acceptDispatch
中的访问者类型,这在 AnyNode::Concept::accept
中是完全已知的。但我无法弄清楚如何从 Concept 向下转换到模型 in Concept(我尝试了协变虚拟 cast
函数,但没有用)。而且我无法使用虚拟方法将类型化的访问者传递给派生模型 class,因为虚拟方法无法模板化。
有没有一种安全的方法可以调用 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 );
}
};
编辑 这是访问者基础 classes,以及派生访问者的示例。派生的 Visitors 由客户端代码实现(这是库的一部分),因此基础 classes 无法知道将实现什么 Visitors。恐怕这会分散中心问题的注意力,但希望它有助于解释问题。这里的一切都有效,除非在 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;
};
啊,我想我现在明白你的问题了。 dynamic_cast
(以及 static_cast
)的问题是具有多种类型的 NodeVisitor
不会生成所有单一类型的 Visitor
classes。
在您提供的示例中,class V
派生自 NodeVisitor< Int_IONode, IntString_IONode >
,最终将生成 Visitor< Int_IONode, IntString_IONode >
和 Visitor< IntString_IONode >
classes作为基地。请注意,不会生成 Visitor< Int_IONode >
。 (visit<Int_IONode>
在 Visitor< Int_IONode, IntString_IONode >
中。)您也没有 NodeVisitor< Int_IONode >
或 NodeVisitor< IntString_IONode >
。将任何内容投射到 class 都将是未定义的行为,因为您从中投射的 class 不能是其中之一。
要解决这个问题,您需要生成所有单一类型的 Visitor
classes。我认为这样的事情可能有效(注意:未测试):
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
classes.
中定义所有 visit
方法
接下来,将acceptDispatch
中的visitor
改为
auto visitor = dynamic_cast< Visitor< T >* >( v );
因为 v
是一个 VisitorBase
,如果正确声明了所有内容,这应该会让您获得所需的 Visitor
class 和包含的 visit
方法.
不,这不可能。
假设您有 3 个模块。模块 1 是您的图书馆。模块 2 定义节点类型。模块 3 定义了访问者。
它们被单独编译为二进制动态库,然后在运行时加载。
如果访问者知道节点类型的完整类型,它将能够对节点类型的属性进行任意编译时检查,从而改变它的行为方式。例如,它在编译时检查静态 node_type::value
是否编码 "P = NP" 的证明。
同时,节点类型 DLL 中没有人使用 node_type::value
,因此它的存在被那里的编译器优化(非常有效地)。
要完成您的要求,您不仅需要发送 node_type
的编译结果,还必须发送相当于 整个源代码 的内容 node_type
到 visitor
DLL,在那个 DLL 中,他们可以针对这个特定的 node_type
.
重新编译他们的 visitor
如果您放宽了十几个隐含要求中的任何一个,这是可行的,但是您选择了一组不相容的要求。很可能你要求的不是你真正需要的,你只是想提出非常笼统的要求并注意到它已经足够了,然后困惑为什么你不能做到。
我敢肯定这是个坏主意。让我们假装我有充分的理由这样做。我有一个节点树,它成功地使用静态多态性来传递消息。至关重要的是,每个节点都不知道它所连接的节点的类型,它只知道它传递的消息的类型。为了遍历树,我使用 CRTP 实现了访问者模式。这适用于树的第一层。
但是,当遍历到树的第二层时,使用下面的AnyNode class擦除下一个节点的类型。我一直无法弄清楚如何从擦除类型向下转换为具体类型。下面的例子在测试中有效,但我认为它也可能真的很危险,只是运气好,内存恰好被布置了。
似乎有问题,我必须删除 AnyNode::Model<T>::acceptDispatch
中的访问者类型,这在 AnyNode::Concept::accept
中是完全已知的。但我无法弄清楚如何从 Concept 向下转换到模型 in Concept(我尝试了协变虚拟 cast
函数,但没有用)。而且我无法使用虚拟方法将类型化的访问者传递给派生模型 class,因为虚拟方法无法模板化。
有没有一种安全的方法可以调用 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 );
}
};
编辑 这是访问者基础 classes,以及派生访问者的示例。派生的 Visitors 由客户端代码实现(这是库的一部分),因此基础 classes 无法知道将实现什么 Visitors。恐怕这会分散中心问题的注意力,但希望它有助于解释问题。这里的一切都有效,除非在 outlet_visitor::operator()
.
->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
(以及 static_cast
)的问题是具有多种类型的 NodeVisitor
不会生成所有单一类型的 Visitor
classes。
在您提供的示例中,class V
派生自 NodeVisitor< Int_IONode, IntString_IONode >
,最终将生成 Visitor< Int_IONode, IntString_IONode >
和 Visitor< IntString_IONode >
classes作为基地。请注意,不会生成 Visitor< Int_IONode >
。 (visit<Int_IONode>
在 Visitor< Int_IONode, IntString_IONode >
中。)您也没有 NodeVisitor< Int_IONode >
或 NodeVisitor< IntString_IONode >
。将任何内容投射到 class 都将是未定义的行为,因为您从中投射的 class 不能是其中之一。
要解决这个问题,您需要生成所有单一类型的 Visitor
classes。我认为这样的事情可能有效(注意:未测试):
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
classes.
visit
方法
接下来,将acceptDispatch
中的visitor
改为
auto visitor = dynamic_cast< Visitor< T >* >( v );
因为 v
是一个 VisitorBase
,如果正确声明了所有内容,这应该会让您获得所需的 Visitor
class 和包含的 visit
方法.
不,这不可能。
假设您有 3 个模块。模块 1 是您的图书馆。模块 2 定义节点类型。模块 3 定义了访问者。
它们被单独编译为二进制动态库,然后在运行时加载。
如果访问者知道节点类型的完整类型,它将能够对节点类型的属性进行任意编译时检查,从而改变它的行为方式。例如,它在编译时检查静态 node_type::value
是否编码 "P = NP" 的证明。
同时,节点类型 DLL 中没有人使用 node_type::value
,因此它的存在被那里的编译器优化(非常有效地)。
要完成您的要求,您不仅需要发送 node_type
的编译结果,还必须发送相当于 整个源代码 的内容 node_type
到 visitor
DLL,在那个 DLL 中,他们可以针对这个特定的 node_type
.
visitor
如果您放宽了十几个隐含要求中的任何一个,这是可行的,但是您选择了一组不相容的要求。很可能你要求的不是你真正需要的,你只是想提出非常笼统的要求并注意到它已经足够了,然后困惑为什么你不能做到。