Я уверен, что это плохая идея. Давайте представим, что у меня есть веская причина для этого. У меня есть дерево узлов, которое успешно использует статический полиморфизм для передачи сообщений. Важно отметить, что каждый узел не может типы узлов, к которым он подключается, он просто знает типы сообщений, которые он пропускает. Чтобы пройти по дереву, я реализовал шаблон посетителя, используя CRTP. Это работает для первого уровня дерева.
Однако при переходе ко второму слою дерева тип следующего узла стирается с использованием приведенного ниже класса AnyNode. Я не смог понять, как перейти от стертого типа к конкретному типу. Приведенный ниже пример работает в тестах, но я думаю, что он также, вероятно, действительно опасен и просто работает, если повезет с тем, где находится память.
Кажется проблематичным, что я должен стереть тип посетителя в AnyNode::Model<T>::acceptDispatch
, который полностью известен в AnyNode::Concept::accept
, Но я не могу понять, как перейти от Концепции к Модели. в Концепция (я попробовал ковариантный виртуальный 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 );
}
};
РЕДАКТИРОВАТЬ Вот базовые классы посетителей и пример производного посетителя. Полученные посетители реализованы с помощью клиентского кода (это часть библиотеки), поэтому базовые классы не могут знать, какие посетители будут реализованы. Боюсь, это отвлекает от основного вопроса, но, надеюсь, поможет немного объяснить проблему. Здесь все работает, кроме случаев, когда ->accept( visitor )
вызывается по указателю AnyNode в 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;
};
Ах, я думаю, что вижу твои проблемы сейчас. Проблема здесь с 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
классы.
Далее поменяй visitor
в acceptDispatch
в
auto visitor = dynamic_cast< Visitor< T >* >( v );
поскольку v
это VisitorBase
, если все заявлено правильно, это должно привести вас к желаемому Visitor
класс и содержание visit
метод.
Нет, это невозможно.
Предположим, у вас есть 3 модуля. Модуль 1 — это ваша библиотека. Модуль 2 определяет тип узла. Модуль 3 определяет посетителя.
Они компилируются отдельно в двоичные динамические библиотеки, а затем загружаются во время выполнения.
Если бы посетитель знал полный тип типа узла, он мог бы выполнять произвольные проверки во время компиляции для свойств типа узла, меняющих его поведение. Например, он проверяет во время компиляции, если статический node_type::value
кодирует доказательство «P = NP» или нет.
Между тем никто в DLL типа узла не использует node_type::value
так что само его существование оптимизировано (вполне корректно) компилятором.
Чтобы сделать то, что вы просите, вы должны отправить не только скомпилированный результат node_type
, но что-то эквивалентное весь источник из node_type
к visitor
DLL, и в этой DLL они могли бы перекомпилировать свои visitor
против этого конкретного node_type
,
Если вы отменили одно из дюжин подразумеваемых требований, это выполнимо, но вы выбрали набор несовместимых запросов. Вполне возможно, что вы просите не то, что вам на самом деле нужно, вы просто подумали задать чрезвычайно общие запросы и заметили, что этого достаточно, а затем озадачены, почему вы не можете это сделать.