Here's the problem:
I am currently trying to create a simple stack-based programming language (Reverse Polish Notation, FORTH style) as a component of a larger project. I have hit a snag, though.
There is no problem with creating a stack in C++ (by using std::vector<>
) that would contain one type of element (I could use the syntax std::vector<double> Stack
, for instance).
However, a programming language needs to be able to hold multiple data types, such as ints, doubles, strings, and 3D vectors (as in physics vectors with X, Y, and Z components), just to name some simple things.
So, is there a construct in C++ that I could use as a stack that would be able to store more than one kind of primitive type/object/struct?
Sure, one way is to use a tagged union:
enum Type { INTEGER, DOUBLE, /* ... */ };
union Data {
uint64_t as_integer;
double as_double;
// ...
};
struct Value {
Type type;
Data data;
};
The storage for as_integer
, as_double
, etc. will be overlapped, so a Value
structure will take up two words of storage, and your stack will have type std::vector<Value>
. Then you access members of data
according to the value of type
:
void sub(std::vector<Value>& stack) {
// In reality you would probably factor this pattern into a function.
auto b = stack.back();
stack.pop_back();
assert(b.type == INTEGER);
auto a = stack.back();
stack.pop_back();
assert(a.type == INTEGER);
Value result;
result.type = INTEGER;
result.data.as_integer = a.data.as_integer - b.data.as_integer;
stack.push_back(result);
}
Of course, Forths are usually untyped, meaning that the stack consists of words only (std::vector<uint64_t>
) and the interpretation of a data value is up to the word operating on it. In that case, you would pun via a union or reinterpret_cast
to the appropriate type in the implementation of each word:
void subDouble(std::vector<Data>& stack) {
// Note that this has no type safety guarantees anymore.
double b = stack.back().as_double;
stack.pop_back();
double a = stack.back().as_double;
stack.pop_back();
Data result;
result.as_double = a - b;
stack.push_back(result);
}
void subDouble(std::vector<uint64_t>& stack) {
double b = reinterpret_cast<double&>(stack.back());
stack.pop_back();
double a = reinterpret_cast<double&>(stack.back());
stack.pop_back();
double result = a - b;
stack.push_back(reinterpret_cast<uint64_t&>(result));
}
Alternatively, you can store not values but pointers to instances of a class Value
from which other value types such as Integer
or Double
would derive:
struct Value {};
struct Integer : Value { uint64_t value; };
struct Double : Value { double value; };
// ...
Your stack would have type std::vector<unique_ptr<Value>>
or std::vector<Value*>
. Then you needn’t worry about different value sizes, at the cost of making wrapper structures and allocating instances of them at runtime.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With