Home > Enterprise >  c perform operations on lists of varied primitives
c perform operations on lists of varied primitives

Time:12-10

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.

  • Related