c++templatesc++20template-meta-programmingcompile-time

Mapping elements of a structure to different vector elements using reflection (compile-time)


I recently have hit a wall, and can't seem to find an elegant solution to my problem:

I have different data structures of different number of elements, all of which are aligned to pointer-size and are not larger than a pointer, ex.:

struct DATA_0{
    size_t      Value;
    size_t      *PtrValue;
    bool        BoolValue;
};

or

struct DATA_1{
    size_t      Value0;
    size_t      Value1;
    size_t      *PtrValue;
    void        *PtrBuffer;
    bool        BoolValue;
    const char  *StringBuffer;
}

The input comes in a form like this:

struct INPUT{
    size_t *DataBuffer;
    size_t DataVector[16];
};

I need to map the data structures to different elements from either the DataBuffer or the DataVector, at compile time:

Some example of desired usage:

INPUT_DATA *input;

data_mapping<DATA_0> mapping_0(input);
//will fetch data from input->DataVector[0] in some cases, input->DataBuffer[0] in other
size_t tmpValue = mapping_0[Value];

data_mapping<DATA_1> mapping_1(input);
//will fetch data from input->DataVector[1] in some cases, input->DataBuffer[3] in other
size_t tmpPtrBuffer = mapping_1[PtrBuffer];

NOTE: the data does not need to be necessarily represented by structures, it can be even variadic template argument too, ex.: <size_t, Value, size_t, PtrValue, bool, BoolValue>

Here is an pseudo-code example of what i am trying to achieve:

template<class TMember, class TData>
TMember GetElement(INPUT_DATA *Input, TData Data)
{
#ifdef FIRST_CASE
    if(indexof(TMember) == 0)
    {
        return Input->DataVector[0];
    }
    if(indexof(TMember) == 1)
    {
        return Input->DataVector[1];
    }
    return Input->DataBuffer[indexof(TMember) - 2];
#else
    if(indexof(TMember) == 0)
    {
        return Input->DataVector[2];
    }
    if(indexof(TMember) == 1)
    {
        return Input->DataVector[3];
    }
    if(indexof(TMember) == 2)
    {
        return Input->DataVector[4];
    }
    if(indexof(TMember) == 3)
    {
        return Input->DataVector[5];
    }
    return Input->DataBuffer[indexof(TMember)];
#endif
}

size_t tmpValue = GetElement<Value>(input,DATA_0);
size_t tmpPtrBuffer = GetElement<PtrBuffer>(input,DATA_1);

Solution

  • I ended up using some (finitely) recursing macros to generate class types, that has named functions, that map to different data fields, depending on compile-time macros:

    template<class TBase> class DataMapping : public TBase {
    protected:
        template<class T> inline const T &GetData(size_t Index) const{
    #ifdef FIRST_CASE
            return (T &)(this->GetDataBuffer()[Index]);
    #elif SECOND_CASE
            if (0 == Index) { return (T &)(this->DataVector()->data_0); }
            if (1 == Index) { return (T &)(this->DataVector()->data_1); }
            if (2 == Index) { return (T &)(this->DataVector()->data_2); }
            return (T &)(this->GetDataBuffer()[Index]);
    #elif THIRD_CASE
            if (0 == Index) { return (T &)(this->DataVector()->data_0); }
            if (1 == Index) { return (T &)(this->DataVector()->data_1); }
            return (T &)(this->GetDataBuffer()[Index]);
    #endif
        }
    };
    
    #define GEN_GETTER(_Type, _Name, _Index) \
        typedef _Type FIELD_##_Name##_TYPE; \
        inline const FIELD_##_Name##_TYPE &Data_##_Name() const { \
            return GetData<_Type>(_Index); \
        } \
        inline FIELD_##_Name##_TYPE & Data_##_Name() { \
            return const_cast<_Type &>(const_cast<decltype(this)>(this)->GetData<_Type>(_Index)); \
        }
    
    #define EXPAND(_expr) _expr
    
    #define GEN_GETTER_0(...)
    #define GEN_GETTER_1(_Index, _Type, _Name) GEN_GETTER(_Type, _Name, _Index)
    #define GEN_GETTER_2(_Index, _Type, _Name, ...)  GEN_GETTER(_Type, _Name, _Index) EXPAND(GEN_GETTER_1(_Index + 1, __VA_ARGS__))
    ...
    #define GEN_GETTER_31(_Index, _Type, _Name, ...) GEN_GETTER(_Type, _Name, _Index) EXPAND(GEN_GETTER_30(_Index + 1, __VA_ARGS__))
    #define GEN_GETTER_32(_Index, _Type, _Name, ...) GEN_GETTER(_Type, _Name, _Index) EXPAND(GEN_GETTER_31(_Index + 1, __VA_ARGS__))
    #define GEN_GETTER_N(_NData, ...) EXPAND(GEN_GETTER_##_NData(0, __VA_ARGS__))
    
    
    #define DECLARE_DATA_MAP(_ClassName, _BaseClass, _NData, ...) \
    struct _ClassName : public DataMapping<_BaseClass> { \
        GEN_GETTER_N(_NData, __VA_ARGS__) \
    };