Home > OS >  How to dynamically pick a member from two similar C structures while avoiding code duplication?
How to dynamically pick a member from two similar C structures while avoiding code duplication?

Time:09-28

Having the following structures:

class XY
{
    int test;
};
class XYZ
{
    short extra;
    int test;
};

I want to use XY::test and XYZ::test depending on a runtime variable. Like:

if (x == 1)
{
    reinterpret_cast<XY*> (object)->test = 1;
}
else if (x == 2)
{
    reinterpret_cast<XYZ*> (object)->test = 1;
}

Is it possible to make this into a nice one-liner? (using templates, macros, etc...?) What to do about &object->test depending on a different type of object? What if the object is passed onto a function like:

void Function(void* object)
{
    if (x == 1)
    {
        reinterpret_cast<XY*> (object)->test = 1;
    }
    else if (x == 2) 
    {
        reinterpret_cast<XYZ*> (object)->test = 1;
    }
}

How can I properly deal with this? I have so many codes like this so I can't write an if condition every time, also, I am unable to change the structures' layout in any way or form.

CodePudding user response:

Since XY::test and XYZ::test are at different byte offsets, and you have stated that you cannot change these structs, I think the best you can do is using an int* pointer, eg:

int *ptest;

switch (x) {
    case 1: ptest = &(static_cast<XY*>(object)->test); break;
    case 2: ptest = &(static_cast<XYZ*>(object)->test); break;
    ...
}

*ptest = 1;

Though, if you really want something more general, an alternative would be to do something like this:

const std:unordered_map<int, size_t> offsets = {
    {1, offsetof(XY, test)},
    {2, offsetof(XYZ, test)}
    ...
};

...

*reinterpret_cast<int*>(static_cast<char*>(object)   offsets[x]) = 1;

CodePudding user response:

Well, since you explicitly say macros may be ok:

#define CAST_IF(N, XY) if (x == N) reinterpret_cast<XY*>(object)

#define ASSIGN(FIELD, VALUE) \
    do { \
        CAST_IF(1, XY)->FIELD = VALUE; \
        else CAST_IF(2, XYZ)->FIELD = VALUE; \
    } while (false)

This kind of thing tends to be tolerable in an implementation file (.cpp, .cc or whatever), but a bad idea in an include file that a lot of other code may include (you should at least pick much more unique names in that case, and #undef them afterwards).

CodePudding user response:

#define FlexibleOffset(class1, class2, member) (x == 1 ? offsetof(class1, member) : offsetof(class2, member))
#define FlexibleMember(object, member) *reinterpret_cast<decltype(XY::member)*>(reinterpret_cast<uintptr_t>(object)   FlexibleOffset(XY, XYZ, member))

FlexibleMember(object, test) = 1;
void* addressTo = &FlexibleMember(object, test);

CodePudding user response:

Write a function.that converts a void pointer to a variant of pointers.

Then visit and dereference.

You can write a magic member pointer that operates on a variant of pointers if you are addicted to syntax. But that will increase total line count.

End use could look like:

pick_cast<XY,XYZ>(x==2)(object)->*test = 3;

after dozens of lines of abtuse boilerplate.

Things get less stupid if you do away with void ptr, and just pass around variant of pointers.

With no boilerplate:

using ptr_t= std::variant<XY*, XYZ*>;
void foo(ptr_t ptr){
  std::visit([&](auto*ptr){ptr->test=3;}, ptr);
}

To turn a void ptr into a ptr_t, you can write one switch to.keep.it simple:

ptr_t get_ptr(int x, void*p){
  switch(x){
    case 1: return reinterpret_cast<XY*>(p);
    case 2: return reinterpret_cast<XYZ*>(p);
  }
}
  •  Tags:  
  • c
  • Related