Могу ли я использовать CRTP с виртуальными функциями или функторами для алгоритмов посетителей, допускающих изменения в классах - PullRequest
6 голосов
/ 14 мая 2019

Я работаю над перезаписью IR компилятора, в котором и классы IR, и алгоритмы постоянно меняются.Текущий компилятор имеет как минимум 2 текущих IR, которые используются на разных этапах, которые я хочу объединить.

Во-первых, у нас есть иерархия AST, основанная на абстрактном базовом классе Node и шаблоне посетителя, связанном с ним.Далее, у нас есть отдельная семантическая иерархия, которая использует множество классов (которые я, вероятно, могу перебазировать, чтобы Node был классом самого низкого уровня для всех них).Семантическая иерархия, вероятно, будет расти по мере того, как мы узнаем больше специализаций.Для этих классов есть отдельный шаблон Visitor.Существует 2 «исполняемых» IR, которые создаются для выполнения результирующей программы.

Моя цель - объединить AST и семантическую иерархию и объединить исполняемые формы, которые они генерируют.Это уменьшит количество ошибок из-за несоответствий в двух формах.

Однако, как я заметил, в семантическую иерархию могут быть добавлены новые классы.Более того, мы также, вероятно, добавим новые алгоритмы для посетителей.

Итак, я хотел бы сделать что-то вроде этого:

class VisitorBase;
class Node;

template Visitable<T> {
   virtual preVisitAccept(VisitorBase *v) { 
      v->preVisit(static_cast<T *>this); }
   virtual inVisitAccept(VisitorBase *v) { 
      v->inVisit(static_cast<T *>this); }
   virtual postVisitAccept(VisitorBase *v) { 
      v->postVisit(static_cast<T *>this); }
   };

template Visitor<T> {
   virtual preVisit(Node *n) { /* do nothing by default */ }
   virtual inVisit(Node *n) { /* do nothing by default */ }
   virtual postVisit(Node *n) { /* do nothing by default */ }
   };

class VisitorBase : Visitor<VistorBase> {
   };

class Node : Visitable<Node> {
   // Code that walks the tree will be probably here 
   // invoking pre, in, and post visit functions as appropriate
}

class ArithOp: node {
   // I don't mind repeating this in each class
   // Some visitor may be specialized on this function
   preVisitAccept(VisitorBase *v) override { 
      v->preVisit(static_cast<arithOp *>this); }
   ...
}

class PrettyPrintVisitor : VisitorBase {
    //  Here is a specialized visitor
    preVisit(ArithOp *n) override { /* specialized code /* }
}

Я не против повторять некоторый код в каждомпроизводный класс узла или каждый класс посетителя.

От чего я хочу избавиться - это хрупкий статический список (который мы должны обновить) всех типов, унаследованных от Node, но при этом возможность двойной отправки по ним,Было бы особенно плохо, если бы нам пришлось повторять такой список более одного раза.По сути, я не хочу, чтобы узлы знали о посетителях (за исключением, возможно, когда есть посетители, настроенные для этого узла), а также о других классах узлов.Я также не хочу, чтобы посетители знали об узлах (за исключением типов узлов, для которых настроен посетитель).Более того, я не хочу никаких центральных хранилищ такой информации, так как это будет заголовок, который всегда вызывает перекомпиляцию мира ....

Какие-нибудь мысли по поводу экспериментов с кодом, которые я должен попробовать?Мы можем скомпилировать это с помощью G ++ или CLang.

1 Ответ

1 голос
/ 14 мая 2019

Двойная диспетчеризация, выполняемая в посетителе, является функцией (в математическом смысле функции), которая связывает пару (visitor_dynamic_type, acceptor_dynamic_type) с функцией (функция C ++).

Поскольку вы не хотите, чтобы базовый посетитель знал все типы, производные от базового акцептора, acceptor_dynamic_type должен быть идентифицирован способом, который абстрагирует тип. Таким образом, мы можем использовать type_index для этого. (Вы можете найти лучший подход, чтобы дать идентификатор, который мог бы быть более дружелюбным для некоторых типов карт)

Чтобы вы могли реализовать посетителя следующим образом (почти в псевдокоде):

template<class T>
struct Visitor {
   map<type_index,void(T&,Node&)>* vfuncs //equivalent a vtbl_ptr
   void Visit(Node& n){
      //manual devirtualization step1: look if a function exist
      //                               for the dynamic type of the acceptor
      if (auto it=vfuncs.find(typeid(n));it!=vfuncs.end()){
          (*it)(statis_cast<T&>(*this),n);
          }
      else{ //do default stuff}
      };
   };
//only visit concrete_Node
struct concrete_visitor:visitor_base{
   static void visit(visitor_base& v,Node& n){
       //manual devirtualization step2: cast references
       auto& cn = static_cast<concrete_Node&>(n);
       auto& self = static_cast<concrete_visitor&>(v);
       //...
       }
   static inline map<type_index,void(visitor_base&,Node&)> vfuncs
                                             {typeid(concrete_Node),&visit};
   concrete_visitor():visitor_base{&vfuncs}{} //vtbl_ptr initialization
   };

Конкретно это уже не шаблон посетителей. Это всего лишь грубая де-виртуализация грубой силой за счет поиска по карте.

...