So I am trying to write a simple interpreter in c , but ran into some problems. I have a Token
class, which holds an enum TokenType
, and a TokenValue
object. The TokenValue
class is the base class of several other classes (TV_String
, TV_Int
, and TV_Float
).
Here is the code for the TokenValue
and its children classes:
// TokenValue.h
class TokenValue
{
public:
void* value = NULL;
virtual bool operator ==(const TokenValue& tv) const
{
return typeid(this) == typeid(tv) && value == tv.value;
}
};
class TV_Empty : public TokenValue {};
class TV_String : public TokenValue
{
public:
std::string value;
TV_String(std::string value); // The constructors just assign the value argument to the value field
};
class TV_Int : public TokenValue
{
public:
int value;
TV_Int(int value);
};
class TV_Float : public TokenValue
{
public:
float value;
TV_Float(float value);
};
Here's the code for Token
:
// Token.h
class Token
{
public:
enum class TokenType
{
// all the different types
}
TokenType type;
TokenValue value;
Token(TokenType type, TokenValue value); // just initialises type and value, nothing else
}
The problem I am having is that the value
field is not being changed when I use any of the children classes (it always shows 00000000 when I print it, I assume that's the value of void* value = NULL
, but not sure). From research I think it could be solved by using templates, but in my case I can't use templates because Token
never know the type of its corresponding TokenValue
.
So how can I override the type and value of the value
field and access the correct value
in the children classes, and in the == operator?
(Thanks to Jarod42 I realised it doesn't "override" the field, it creates a new field with a different type and the same name.)
CodePudding user response:
A quick and dirty solution without any type validation could look like this:
class ITokenValue {};
enum class TokenType {
Integer,
Boolean
};
template<typename T>
class TokenValue : public ITokenValue {
public:
T getValue() const { return m_value; }
void setValue(T val) { m_value = val; }
private:
T m_value;
};
class Token {
public:
template<typename T>
void setValue(T value) {
setType<T>();
delete m_value;
m_value = new TokenValue<T>();
((TokenValue<T>*)m_value)->setValue(value);
}
template<typename T>
T getValue() const {
return ((TokenValue<T>*)m_value)->getValue();
}
private:
template<typename T>
void setType();
private:
ITokenValue* m_value;
TokenType m_type;
};
template<>
void Token::setType<int>() {
m_type = TokenType::Integer;
}
template<>
void Token::setType<bool>() {
m_type = TokenType::Integer;
}
And the usage code like this:
Token t;
t.setValue<int>(5);
int v = t.getValue<int>();
t.setValue<bool>(true);
bool b = t.getValue<bool>();
Token could store its type in an enum and at some point you will need to map that enum to an actual type to call the correct getValue function. Calling it with the wrong type will cause a crash with the above example. m_value in Token can be replaced by std::variant.
CodePudding user response:
This is a somewhat nasty situation, but it can be handled with a bit of indirection:
struct TokenValue {
virtual bool equals(const TokenValue&) = 0;
};
bool operator==(const TokenValue& lhs, const TokenValue& rhs) {
return typeid(lhs) == typeid(rhs) && lhs.equals(rhs);
}
Now, derived classes can have their own value
field (if that's appropriate), and override equals
, knowing that the argument will always be their own type:
struct TV_empty : TokenValue {
bool equals(const TokenValue&) { return true; }
};
struct TV_string : TokenValue {
std::string value;
bool equals(const TokenValue& other) {
return value == static_cast<TV_string&>(other).value;
}
}
and so on.
Yes, if you're paranoid, you could use dynamic_cast
inside the equals
functions.