Reputation: 11
I'm currently writing an interpreter and it's strongly typed. It has short, int, long, float, and double. I want to make binary operation between 2 numbers and have an implicit cast. For example, I want to be able to add an int to a double and return a double, or adding a long to a double and return a double.
The code for the evaulator for a binary expression is written like this
std::any Interpreter::visitBinaryExpression(BinaryExpression& binaryExpression)
{
std::any left = binaryExpression.left->accept(*this);
std::any right = binaryExpression.right->accept(*this);
Token_t op = binaryExpression.op;
if (left.type() == typeid(short) && right.type() == typeid(short))
{
return eva_num(std::any_cast<short>(left), op, std::any_cast<short>(right));
}
if (left.type() == typeid(int) && right.type() == typeid(int))
{
return eva_num(std::any_cast<int>(left), op, std::any_cast<int>(right));
}
if (left.type() == typeid(long) && right.type() == typeid(long))
{
return eva_num(std::any_cast<long>(left), op, std::any_cast<long>(right));
}
if (left.type() == typeid(float) && right.type() == typeid(float))
{
return eva_num(std::any_cast<float>(left), op, std::any_cast<float>(right));
}
if (left.type() == typeid(double) && right.type() == typeid(double))
{
return eva_num(std::any_cast<double>(left), op, std::any_cast<double>(right));
}
if (left.type() == typeid(bool) && right.type() == typeid(bool))
{
return eva_bool(std::any_cast<bool>(left), op, std::any_cast<bool>(right));
}
std::string left_err = left.type().name();
std::string right_err = right.type().name();
throw std::invalid_argument("Runtime Error: couldn't evaluate type '" + left_err + "' with type '" + right_err + "'");
}
The parser generates an AST and then I interpret each statements using the visitor pattern.
The problem is that, since I'm using std::any
I need to check each scenario for the variable type. And by doing this, I'll end up with more than 100 IF statements, which doesn't sound right.
i tried to replace std::any
with std::variant
but i'll still have 100+ if statements.
I tried to use metaprogramming, but i really don't know how to implemenet this (I'm a beginner)
does anyone know how to solve this problem without brutally writing all the if statements?
Upvotes: 1
Views: 103
Reputation: 53
Basic function for your operation is as follows:
template<typename L, typename R>
std::any Add(std::any l, std::any r) {
return std::any(std::any_cast<L>(l) + std::any_cast<R>(r));
}
Note that above function uses c++ rules for finding a common type. You may wish to look at std::common_type
or specializations.
Other than that, this problem is related to multiple dispatch. Popular languages usually don't bother with it and use single dispatch for all dynamically typed values (virtual functions) and sometimes end up with quirks such as __radd__
in python. You may also be interested in reading about as dynamic
cast in C# for overloads
As discussed case is relatively simple, it can be achieved by having a std::map
:
std::map<
std::pair<std::type_index, std::type_index>,
std::any (*)(std::any, std::any)
>
For such cases I prefer not to bother myself with metaprogramming and would write a codegen file, but for the sake of staying in c++ let's just use macros (and have 2N repetitions instead of N^2). In the end any solution will produce N^2 entities in the resulting machine code
#define IT(L, R) { \
{std::type_index(typeid(L)), std::type_index(typeid(R))}, \
&Add<L, R> \
},
#define FOR_T(R) \
IT(short, R) IT(int, R) IT(double, R)
FOR_T(short) FOR_T(int) FOR_T(double)
Complete example below:
#include <any>
#include <map>
#include <typeindex>
template<typename L, typename R>
std::any Add(std::any l, std::any r) {
return std::any(std::any_cast<L>(l) + std::any_cast<R>(r));
}
#define IT(L, R) { \
{std::type_index(typeid(L)), std::type_index(typeid(R))}, \
&Add<L, R> \
},
#define FOR_T(R) \
IT(short, R) IT(int, R) IT(double, R)
using operator_dispatch_key = std::pair<std::type_index, std::type_index>;
std::map<operator_dispatch_key, std::any (*)(std::any, std::any)> all_ops = {
FOR_T(short) FOR_T(int) FOR_T(double)
};
#include <iostream>
int main() {
auto l = std::any(int(1));
auto r = std::any(double(2.5));
auto l_t = std::type_index(l.type());
auto r_t = std::type_index(r.type());
auto res = all_ops[std::make_pair(l_t, r_t)](std::move(l), std::move(r));
std::cout
<< res.type().name()
<< ' '
<< std::any_cast<double>(res)
<< std::endl
;
return 0;
}
Program returned: 0
Output: d 3.5
PS as your operators are "internal" you can just use an array of such maps for them. It won't work for type ids of std::any
because they are not sequential
Upvotes: 0