I'm writing an interpreter for a simple scripting language, and I'm running into problems performing arithmetic operations on the variables. I need to make two lists of primitives, where every element in the list can be a different primitive type. So that's uint, int, float, and bool, along with different numbers of bits for the numerics: 8, 16, 32 and 64.
Then I need to perform some arithmetic operation between an element from list 1 and an element in list 2, and store the result. But I can't find any elegant way of doing it.
The best I've come up with is to make a union of all the primitive types an element can hold, and an enumeration to say which one to use. I'd make a switch statement to get the correct primitive type from the union, which would let me get the value from list 1. Then I'd have to write a second switch statement to get the value from list 2, and a third one to find out the variable type of the result.
In this example, GenericPrimitive is a class that contains the union of primitives and an enumeration that specifies which union type to use:
void DoOperation(vector<GenericPrimitive> first, vector<GenericPrimitive> second, GenericPrimitive& result)
{
switch (first.at(0).PrimitiveType())
{
case PT_UINT8_T: return DoOperationOnT<uint8_t>(first.at(0).GetValueAsUInt8T(), second, result);
...
template <class T>
void DoOperationOnT(T firstValue, vector<GenericPrimitive> second, GenericPrimitive& result)
{
switch (second.at(0).PrimitiveType())
{
case PT_INT16_T: return DoOperationOnTAndU<T, int16_t>(firstValue, second.at(0).GetValueAsInt16T(), result);
...
template <class T, class U>
void DoOperationOnTAndU(T firstValue, U secondValue, GenericPrimitive& result)
{
switch (result.PrimitiveType())
{
case FLOAT: StoreOpResultAsFloat<T, U>(firstValue, secondValue, result);
...
template <class T, class U>
void StoreOpResultAsFloat(T firstValue, U secondValue, GenericPrimitive& result)
{
switch (OperationType)
{
case ADDITION: result.SetFloat(AddValues<T, U, float>(firstValue, secondValue));
....
template <class T, class U, class V>
V AddValues(T first, U second)
{
return (V)(T U);
}
All these switch statements are inefficient. Is there some simpler or faster way I'm missing? I just want to know the types T U and V for my AddValues function without having to recalculate them every time. (or do something else that gives the same effect)
CodePudding user response:
I'd profile first before making assumptions, but still: I'm going to assume that you overlooked one important common case: first.[0].PrimitiveType()==first.[0].PrimitiveType()
- in which case you only need one switch
. Mixed-mode arithmetic tends to be rare.
(Also, I assumed that the .at(0)
was an unintentional bounds check - there really should be a comment in code that intentionally uses .at
)
CodePudding user response:
I would start by re-evaluating your template usage. If the goal is to eliminate the repeated usage of switch statements, I recommend using functors, enums, and variadic templates:
struct AddValuesFunctor
{
template<class V, class T, class U>
V operator()(T firstValue, U secondValue)
{
return static_cast<V>(firstValue) static_cast<V>(secondValue)
}
}
It looks like you are already using an enum of typing, but just to cover all my bases, I will mention that you will need one for this next piece of code.
The next step would be to add a variadic template like so:
template <class FuncT, class... ArgsT>
auto RunExemplarSwitch(FuncT&& func, Typing enumValueRepresentingType, ArgsT&&... args)
{
switch(enumValueRepresentingType)
{
case Typing::FLOAT: {
return func.template operator()<float>(std::forward<ArgsT>(args)...);
}
case Typing::Boolean: {
return func.template operator()<bool>(std::forward<ArgsT>(args)...);
}
...
default:
{
throw std::runtime_error("Unhandled Type Passed In");
}
}
And since your template structure should be able to determine values of the firstValue
and secondValue
the variadic template should implement the typing of class V
The actual function call looks like this [assuming you are parsing the entire vectors and they are of equal length]:
vector<GenericPrimitive> first;
vector<GenericPrimitive> second;
for(size_t sharedIndex = 0; sharedIndex < first.size(); sharedIndex )
{
RunExemplarSwitch(AddValuesFunctor{}, Typing::FLOAT, first[sharedIndex], second[sharedIndex]);
}
This will likely have to be adapted to fit your specific use case, but hopefully using an implementation like this will help cut down on switch usage.
PS: This is my first post so I apologize if its not up to par.