I'm writing a simple parser in C++ designed to parse a subset of an s-expression language.
I am trying to design my inheritance hierarchy for the tokenizer in a clean manner, but I'm running into issues with object slicing because I've been trying to avoid dynamic allocation. The reason I'd like to avoid dynamic allocation is to sidestep the issue of introducing memory leaks in my tokenizer and parser.
The overall structure is: a Parser has a Tokenizer instance. The parser calls Tokenizer::peek() which returns the token at the head of the input. I want peek() to return a Token instance by value, instead of dynamically allocating a Token of the correct derived class and returning the pointer.
More concretely, suppose there are two token types: Int and Float. Here is an example which will hopefully clarify the issue:
class Token {
public:
virtual std::string str() { return "default"; }
};
template <typename T>
class BaseToken : public Token {
public:
T value;
BaseToken(const T &t) : value(t) {}
virtual std::string str() {
return to_str(value);
}
};
class TokenInt : public BaseToken<int> {
public:
TokenInt(int i) : BaseToken(i) {}
};
class TokenFloat : public BaseToken<float> {
TokenFloat(float f) : BaseToken(f) {}
};
Token peek() {
return TokenInt(10);
}
int main() {
Token t = peek();
std::cout << "Token is: " << t.str() << "\n";
return 0;
}
As is probably obvious, the output is "Token is: default" instead of "Token is: 10" because of the TokenInt being sliced down to a Token.
My question is: is there a proper inheritance structure or design pattern to accomplish this type of polymorphism without using dynamic allocation?