How to have a C++ stack with more than one data type?
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.