Home > Back-end >  Finding the actual type (float, uint32_t...) based on the value of an enum (kFloat, kUint32...)
Finding the actual type (float, uint32_t...) based on the value of an enum (kFloat, kUint32...)

Time:12-02

I am reading data from a file and the type of the data is stored as a uint8_t which indicates the type of data I am about to read. This is the enum corresponding to the declaration of these values.

enum DataType
{
    kInt8,
    kUint16,
    kInt16,
    kUint32,
    kInt32,
    kUint64,
    kInt64,
    kFloat16,
    kFloat32,
    kFloat64
};

I then get a function that reads the values stored in the file with the specific type, something like this:

template<typename T>
readData(T*& data, size_t numElements)
{
   ifs.read((char*)data, sizeof(T) * numElements);
}

uint8_t type;
ifs.read((char*)&type, 1);
uint32_t numElements;
ids.read((char*)&numElements, sizeof(uint32_t));
switch (type) {
    case kUint8:
        uint8_t* data = new uint8_t[numElements];
        readData<uint8_t>(data, numElements);
        // eventually delete mem
        ...
        break;
    case kUint16:
       uint16_t* data = new int16_t[numElements];
       readData<uint16_t>(data, numElements);
        // eventually delete mem
        ...
        break;
    case ... etc.
    default:
        break;
}

I have just represented 2 cases but eventually, you'd need to do it for all types. It's a lot of code duplication and so what I'd like to do is find the actual type of the values given the enum value. For example, if the enum value was kUint32 then the type would be uint32_t, etc. If I was able to do so, the code could become more compact with something like this (pseudo-code):

DataType typeFromFile = kUint32;
ActualC  Type T = typeFromFile.getType();
readData<T>(data, numElements);

What technique could you recommend to make it work (or what alternative solution may you recommend?).

CodePudding user response:

You're trying to take a runtime value and map it to a compile-time type. Since C is a compile-time typed language, there's no escaping that, at some point, you're going to have to do something like a switch/case statement at some point. This is because each option needs to have its own separate code.

So the desire is to minimize how much of this you actually do. The best way to do it with standard library tools is to employ a variant.

So, you create a variant<Ts>, such that the Ts types are unique_ptr<T[]>s, where the various Ts are the sequence of types in your enumeration in order. This way, the enumeration index matches the variant index. The unique_ptr part is important, as it will make the variant destroy the array for you without having to know which type it stores.

So the only thing the switch/case needs to do is create the array. The processing of the array can happen in a visitor, which can be a template, as follows:

    variant_type data_array{};

    switch (type) {
        case kUint8:
            data_array = std::make_unique<std::uint8_t[]>(numElements);
        case kUint16:
            data_array = std::make_unique<std::uint16_t[]>(numElements);
        default:
        //error out. `break` is not an option.
          break;
    }

    std::visit([&](auto &arr)
    {
        using arrtype = std::remove_cvref_t<decltype(arr)>;
        readData<typename arrtype::element_type>(arr.get(), numElements);
       ///...
    }, data_array);

//data_array's destructor will destroy the allocated arrays.

CodePudding user response:

I would use a higher-order macro for this sort of thing. Start with a macro that defines the mapping between the enum values and the types:

#define FOREACH_DATATYPE(OP)                    \
  OP(kUint8, uint8_t)                           \
  OP(kInt8, int8_t)                             \
  OP(kUint16, uint16_t)                         \
  OP(kInt16, int16_t)

  // Fill in the rest yourself

Then use it to generate the switch statement for all the enum values and types:

  switch (type) {
#define CASE_TYPE(name, type)                   \
    case name: {                                \
      type* data = new type[numElements];       \
      readData<type>(data, numElements);        \
      break;}
    FOREACH_DATATYPE(CASE_TYPE)
#undef CASE_TYPE
  default:
      break;
  }
  • Related