Reputation: 136
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.)
Upvotes: 1
Views: 231
Reputation: 76370
This is a somewhat nasty situation, but it can be handled with a bit of indirection:
struct TokenValue {
virtual bool equals(const TokenValue&) const = 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&) const { return true; }
};
struct TV_string : TokenValue {
std::string value;
bool equals(const TokenValue& other) const {
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.
Upvotes: 1
Reputation: 596673
What you are attempting to do will not work, because TokenValue
is a base class and you are storing it by value in Token
, so if you attempt to assign a TV_String
object, a TV_Int
object, etc to Token::value
, you will slice that object, losing all info about the derived class type and its data fields.
To work with polymorphic classes correctly, you will need to make the Token::value
field be a pointer to a TokenValue
object instead, eg:
class TokenValue
{
public:
virtual ~TokenValue() = default;
virtual bool equals(const TokenValue*) const = 0;
bool operator==(const TokenValue &rhs) const {
return equals(&rhs);
}
};
class TV_Empty : public TokenValue {
public:
bool equals(const TokenValue* tv) const override {
return (dynamic_cast<const TV_Empty*>(tv) != nullptr);
}
};
class TV_String : public TokenValue
{
public:
std::string value;
TV_String(const std::string &value) : value(value) {}
bool equals(const TokenValue* tv) const override {
TV_String *s = dynamic_cast<const TV_String*>(tv);
return (s) && (s->value == value);
}
};
class TV_Int : public TokenValue
{
public:
int value;
TV_Int(int value) : value(value) {}
bool equals(const TokenValue* tv) const override {
TV_Int *i = dynamic_cast<const TV_Int*>(tv);
return (i) && (i->value == value);
}
};
class TV_Float : public TokenValue
{
public:
float value;
TV_Float(float value) : value(value) {}
bool equals(const TokenValue* tv) const override {
TV_Float *f = dynamic_cast<const TV_Float*>(tv);
return (f) && (f->value == value);
}
};
...
struct EmptyToken {};
class Token
{
public:
enum class TokenType
{
Empty,
String,
Int,
Float
...;
};
TokenType type;
std::unique_ptr<TokenValue> value;
static TokenType GetTokenType(const TokenValue *tv) {
if (dynamic_cast<TV_Empty*>(tv) != nullptr)
return TokenType::Empty;
if (dynamic_cast<TV_String*>(tv) != nullptr)
return TokenType::String;
if (dynamic_cast<TV_Int*>(tv) != nullptr)
return TokenType::Int;
if (dynamic_cast<TV_Float*>(tv) != nullptr)
return TokenType::Float;
return ...;
}
Token(std::unique_ptr<TokenValue> value) : Token(GetTokenType(value.get()), std::move(value)) {}
Token(TokenType type, std::unique_ptr<TokenValue> value) : type(type), value(std::move(value)) {}
explicit Token(const EmptyToken &) : type(TokenValue::Empty), value(std::make_unique<TV_Empty>()) {}
explicit Token(const std::string &value) : type(TokenValue::String), value(std::make_unique<TV_String>(value)) {}
explicit Token(int value) : type(TokenValue::Int), value(std::make_unique<TV_Int>(value)) {}
explicit Token(float value) : type(TokenValue::Float), value(std::make_unique<TV_Float>(value)) {}
...
};
Token tk1(std::string("test"));
Token tk2(12345);
if (*(tk1.value) == *(tk2.value)) ...
if (tk1.value->equals(tk2.value.get())) ...
...
However, what you are essentially doing is replicating what std::variant
already is (a tagged union), so you should get rid of TokenValue
completely and just use std::variant
instead, eg:
struct EmptyToken {};
class Token
{
public:
enum class TokenType
{
Empty,
String,
Int,
Float
...;
};
std::variant<EmptyToken, std::string, int, float, ...> value;
explicit Token(const EmptyToken &value) : value(value) {}
explicit Token(const std::string &value) : value(value) {}
explicit Token(int value) : value(value) {}
explicit Token(float value) : value(value) {}
...
TokenType GetTokenType() const
{
static const TokenType types[] = {TokenType::Empty, TokenType::String, TokenType::Int, TokenType::Float, ...};
return types[value.index()];
};
...
};
Token tk1(std::string("test"));
Token tk2(12345);
if (tk1.value == tk2.value) ...
...
Upvotes: 1