Шаблон посетителя. Является ли void * приемлемым типом возврата для полностью абстрактного интерфейса?

У меня есть AST, представленный обычным способом (дерево узлов абстрактного типа). У меня есть несколько вариантов использования для обхода этого дерева (оптимизатор, который возвращает другой AST; генерация ИК-кода, который возвращает llvm::Value*; и анализатор отладки, который просто выводит на стандартный вывод и ничего не возвращает).

Посетитель чувствует себя как правильный путь, но разные типы возвращаемого значения в каждом случае использования посетителя затрудняют понимание того, как реализовать интерфейс для этого. Я считал это:

class Visitor;

class ASTNode {
public:
virtual void accept(Visitor *visitor);
};

class Visitor {
public:
virtual void visit(CallNode *node) = 0;
virtual void visit(BinExprNode *node) = 0;
// etc
};

Из-за отсутствия возвращаемого значения каждая реализация посетителя должна была бы создать внутреннее состояние и предоставить result() метод с подходящим типом возврата. Это сложно, однако, так как каждый вызов visit() нужен некоторый контекст вокруг выражения, которое вы посещаете (т. е. сам вызов или он используется как часть двоичного выражения?). Для таких вещей, как генерация кода двоичных выражений, сложно собирать возвращаемые значения при посещении узлов операндов (я мог бы сделать это с помощью внутренней переменной временного состояния или чего-то подобного, но она кажется чрезмерно сложной) и трудно рассуждать о ( состояние постоянно меняется).

class OptimizingVisitor : public Visitor {
ASTNode *m_result;
public:
ASTNode *result() {
return m_result;
}

void setResult(ASTNode *node) {
m_result = node;
}

void visit(CallNode *node) {
node->left()->accept(this);
ASTNode *optimizedL = result();
node->right()->accept(this);
ASTNode *optimizedR = result();
setResult(new CallNode(optimizedL, optimizedR));
}

// ... snip ...
};

Я мог бы сделать его еще более сложным, чтобы избежать проблемы «состояние постоянно меняется», передав нового посетителя для каждого узла. Такое ощущение, что эта проблема требует более функционального решения.

На самом деле, я хотел бы написать выше, намного проще и проще для чтения:

class OptimizingVisitor : public Visitor {
public:
ASTNode* visit(CallNode *node) {
ASTNode *optimizedL = node->left()->accept(this);
ASTNode *optimizedR = node->right()->accept(this);
return new CallNode(optimizedL, optimizedR);
}

// ... snip ...
};

Конечно, теперь я изменил тип возврата, поэтому он не соответствует моему контракту с посетителями. Далекий от определения полностью отдельных интерфейсов для каждой возможной реализации посетителя и информирования AST об этих типах посетителей, кажется, единственной действительно логичной вещью, которую можно использовать, является void*, Гарантирует ли этот вид API использование void*? Есть лучший способ сделать это?

5

Решение

Если я правильно понимаю, у меня может быть что-то полезное. Ссылаясь на ваш образец в https://gist.github.com/d11wtq/9575063, ты не можешь иметь

class ASTNode {
public:
template <class T>
virtual T accept(Visitor<T> *visitor);
};

потому что нет шаблонов виртуальных функций. Тем не менее, вы можете иметь общий класс

template <class D, class T>
struct host {
virtual T accept(Visitor<T> * visitor);
// I guess, { return visitor->visit(static_cast <D*>(this)); }
};

и коллекция

template <class D, class... T>
struct hosts : host <D, T>... { };

так что, когда набор возможных типов возврата ограничен, вы можете сказать, например,

class ASTNode : public hosts <ASTNode, T1, T2, T3> {
public:
// ...
};

Теперь у вас есть три разных типа контрактов для посетителей. T1,T2,T3,

4

Другие решения

Может быть, это полезно.

Используя возвращаемую ковариацию типа сделать accept() Типы звонков безопасны:

class Visitor;

class ASTNode
{
public:
virtual ASTNode* accept(Visitor* visitor);
};

class CallNode;
class BinaryExpressionNode;

class Visitor
{
public:
virtual CallNode* visit(CallNode* node) = 0;
virtual BinaryExpressionNode* visit(BinaryExpressionNode* node) = 0;
};

Реализации:

class CallNode : public ASTNode
{
public:
CallNode(BinaryExpressionNode* left, BinaryExpressionNode* right);

// return type covariance
CallNode* accept(Visitor* visitor) override
{
return visitor->visit(this);
}

BinaryExpressionNode* left();
BinaryExpressionNode* right();
};

class BinaryExpressionNode : public ASTNode
{
public:
BinaryExpressionNode* accept(Visitor* visitor) override
{
return visitor->visit(this);
}
}

class OptimizingVisitor : public Visitor
{
public:
CallNode* visit(CallNode* node) override
{
// return type covariance will make this operation type-safe:
BinaryExpressionNode* left = node->left()->accept(this);
auto right = node->right()->accept(this);
return new CallNode(left, right);
}

BinaryExpressionNode* visit(BinaryExpressionNode* node) override
{
return new BinaryExpressionNode(node);
}
};
3

По вопросам рекламы [email protected]