在C++中实现访客模式使用模板
我目前正在尝试在C ++中实现一种编程语言。解析阶段后,我有一个可以操作的抽象语法树,其中包括类型检查和字节码生成。之后,在这棵树上有不同的分析类,例如Astprinter和上述类型的检查器。
以前,访问者类'访问()
方法返回了void,但我最近意识到有些访问者可能需要返回一些值。我尝试使用模板,但是我遇到了与静态时间多态性和运行时多态性有关的问题,就像我从这里学到的那样: C ++虚拟模板方法。
这是与此有关的类(我知道它没有编译,但它说明了我要做的事情):
express.h
class Expression {
public:
template<typename R> R accept(ExprVisitor<R>& visitor);
};
exprvisitor.h
template<typename R>
class ExprVisitor {
public:
virtual R visitAssignmentExpression(class Assignment* expression) = 0;
virtual R visitBinaryExpression(class Binary* expression) = 0;
// Rest of the visit methods...
};
示例表达式类(signment.h)
class Assignment: public Expression {
public:
template<typename R> R accept(ExprVisitor<R>& visitor);
};
示例访问者类(astprinter.h)
class ASTPrinter: public ExprVisitor<std::string> {
public:
std::string visitAssignmentExpression(Assignment* expression) override;
std::string visitBinaryExpression(Binary* expression) override;
// Rest of the visit methods...
};
为看到,Astprinter需要返回std :: String
。我相信这个问题源于以下事实:accept()
方法在expression.h
中不是虚拟的(因为我正在使用模板)。
这是我收到的确切错误消息(每种AST节点类型重复):
undefined reference to `std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > Expression::accept<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >(ExprVisitor<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&)'
我的问题是:有其他方法可以实现同一件事吗?我已经陷入了很长时间,感谢任何帮助。 最小可再现
的示例:
main.cpp
#include "Literal.h"
#include "Binary.h"
#include "ASTPrinter.h"
int main(int argc, char** argv) {
Binary expr = Binary(Literal("10"), "+", Literal("10"));
ASTPrinter ast = ASTPrinter();
ast.constructTree(expr);
}
expression.h
#ifndef CODEPULSAR_EXPRESSION_H
#define CODEPULSAR_EXPRESSION_H
#include "ExprVisitor.h"
class Expression {
public:
template<typename R> R accept(ExprVisitor<R>& visitor);
};
#endif
exprvisitor.h
#ifndef CODEPULSAR_EXPRVISITOR_H
#define CODEPULSAR_EXPRVISITOR_H
template<typename R> class ExprVisitor {
public:
virtual R visitBinaryExpression(class Binary expression) = 0;
virtual R visitLiteralExpression(class Literal expression) = 0;
};
#endif
astprinter。*
// ASTPrinter.h
#ifndef CODEPULSAR_ASTPRINTER_H
#define CODEPULSAR_ASTPRINTER_H
#include <string>
#include <iostream>
#include "ExprVisitor.h"
#include "Expression.h"
#include "Binary.h"
#include "Literal.h"
class ASTPrinter: public ExprVisitor<std::string> {
public:
void constructTree(Expression ast);
// Expression AST Visitors
std::string visitBinaryExpression(Binary expression) override;
std::string visitLiteralExpression(Literal expression) override;
};
#endif
// ASTPrinter.cpp
#include "ASTPrinter.h"
void ASTPrinter::constructTree(Expression ast) {
std::cout << ast.accept(*this) << std::endl;
}
std::string ASTPrinter::visitBinaryExpression(Binary expression) {
return "Binary(" + expression.left.accept(*this) + expression.operatorType + expression.right.accept(*this);
}
std::string ASTPrinter::visitLiteralExpression(Literal expression) {
return "Literal(" + expression.value + ")";
}
二进制
// Binary.h
#ifndef CODEPULSAR_BINARY_H
#define CODEPULSAR_BINARY_H
#include <string>
#include "Expression.h"
class Binary: public Expression {
public:
Binary(Expression left, std::string operatorType, Expression right);
template<typename R> R accept(ExprVisitor<R>& visitor);
Expression left;
std::string operatorType;
Expression right;
};
#endif
// Binary.cpp
#include "Binary.h"
Binary::Binary(Expression left, std::string operatorType, Expression right) {
this->left = left;
this->operatorType = operatorType;
this->right = right;
}
template<typename R>
R Binary::accept(ExprVisitor<R>& visitor) {
visitor.visitBinaryExpression(this);
}
。
// Literal.h
#ifndef CODEPULSAR_LITERAL_H
#define CODEPULSAR_LITERAL_H
#include <string>
#include "Expression.h"
class Literal: public Expression {
public:
Literal(std::string value);
template<typename R> R accept(ExprVisitor<R>& visitor);
std::string value;
};
#endif
// Literal.cpp
#include "Literal.h"
Literal::Literal(std::string value) {
this->value = value;
}
template<typename R>
R Literal::accept(ExprVisitor<R>& visitor) {
visitor.visitLiteralExpression(this);
}
I am currently trying to implement a programming language in C++. After the parsing stage, I have an Abstract Syntax Tree that I can operate on, which includes type checking and bytecode generation. After that, there are different analysis classes that operate on this tree, like an ASTPrinter and the aforementioned type checker.
Previously, the visitor class' visit()
methods returned void, but I recently realized that some visitors may need to return some values. I tried using templates, but I ran into an issue relating to static time polymorphism and runtime polymorphism as I learned about from here: C++ Virtual template method.
Here are the classes relating to this (I know it doesn't compile, but it illustrates what I am trying to do):
Expression.h
class Expression {
public:
template<typename R> R accept(ExprVisitor<R>& visitor);
};
ExprVisitor.h
template<typename R>
class ExprVisitor {
public:
virtual R visitAssignmentExpression(class Assignment* expression) = 0;
virtual R visitBinaryExpression(class Binary* expression) = 0;
// Rest of the visit methods...
};
Example Expression Class (Assignment.h)
class Assignment: public Expression {
public:
template<typename R> R accept(ExprVisitor<R>& visitor);
};
Example Visitor Class (ASTPrinter.h)
class ASTPrinter: public ExprVisitor<std::string> {
public:
std::string visitAssignmentExpression(Assignment* expression) override;
std::string visitBinaryExpression(Binary* expression) override;
// Rest of the visit methods...
};
As seen, the ASTPrinter needs to return a std::string
. I believe the issue arises from the fact that the accept()
method is not virtual in Expression.h
(because I am using templates).
This is the exact error message I am getting (repeated for each AST node type):
undefined reference to `std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > Expression::accept<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >(ExprVisitor<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >&)'
My question is: is there another way to achieve the same thing? I have been stuck on this for a long time, and I appreciate any help. Thanks
Minimum Reproducible Example:
main.cpp
#include "Literal.h"
#include "Binary.h"
#include "ASTPrinter.h"
int main(int argc, char** argv) {
Binary expr = Binary(Literal("10"), "+", Literal("10"));
ASTPrinter ast = ASTPrinter();
ast.constructTree(expr);
}
Expression.h
#ifndef CODEPULSAR_EXPRESSION_H
#define CODEPULSAR_EXPRESSION_H
#include "ExprVisitor.h"
class Expression {
public:
template<typename R> R accept(ExprVisitor<R>& visitor);
};
#endif
ExprVisitor.h
#ifndef CODEPULSAR_EXPRVISITOR_H
#define CODEPULSAR_EXPRVISITOR_H
template<typename R> class ExprVisitor {
public:
virtual R visitBinaryExpression(class Binary expression) = 0;
virtual R visitLiteralExpression(class Literal expression) = 0;
};
#endif
ASTPrinter.*
// ASTPrinter.h
#ifndef CODEPULSAR_ASTPRINTER_H
#define CODEPULSAR_ASTPRINTER_H
#include <string>
#include <iostream>
#include "ExprVisitor.h"
#include "Expression.h"
#include "Binary.h"
#include "Literal.h"
class ASTPrinter: public ExprVisitor<std::string> {
public:
void constructTree(Expression ast);
// Expression AST Visitors
std::string visitBinaryExpression(Binary expression) override;
std::string visitLiteralExpression(Literal expression) override;
};
#endif
// ASTPrinter.cpp
#include "ASTPrinter.h"
void ASTPrinter::constructTree(Expression ast) {
std::cout << ast.accept(*this) << std::endl;
}
std::string ASTPrinter::visitBinaryExpression(Binary expression) {
return "Binary(" + expression.left.accept(*this) + expression.operatorType + expression.right.accept(*this);
}
std::string ASTPrinter::visitLiteralExpression(Literal expression) {
return "Literal(" + expression.value + ")";
}
Binary.*
// Binary.h
#ifndef CODEPULSAR_BINARY_H
#define CODEPULSAR_BINARY_H
#include <string>
#include "Expression.h"
class Binary: public Expression {
public:
Binary(Expression left, std::string operatorType, Expression right);
template<typename R> R accept(ExprVisitor<R>& visitor);
Expression left;
std::string operatorType;
Expression right;
};
#endif
// Binary.cpp
#include "Binary.h"
Binary::Binary(Expression left, std::string operatorType, Expression right) {
this->left = left;
this->operatorType = operatorType;
this->right = right;
}
template<typename R>
R Binary::accept(ExprVisitor<R>& visitor) {
visitor.visitBinaryExpression(this);
}
Literal.*
// Literal.h
#ifndef CODEPULSAR_LITERAL_H
#define CODEPULSAR_LITERAL_H
#include <string>
#include "Expression.h"
class Literal: public Expression {
public:
Literal(std::string value);
template<typename R> R accept(ExprVisitor<R>& visitor);
std::string value;
};
#endif
// Literal.cpp
#include "Literal.h"
Literal::Literal(std::string value) {
this->value = value;
}
template<typename R>
R Literal::accept(ExprVisitor<R>& visitor) {
visitor.visitLiteralExpression(this);
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
您可以将“ OUT”参数添加到
expression :: Accept
的实际实现中,并使用它将指针传递给默认构造的结果值,并在模板函数中执行结果值的创建:You add an "out" parameter to the actual implementation of
Expression::accept
and use it to fill pass a pointer to a default constructed result value and do the creation of the result value in a template function: