How can I dynamically pick a member from two similar C++ structures while avoiding code duplication?

Viewed 2921

Having the following structures:

class XY
{
private:
    int test;
};
class XYZ
{
private:
    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 should I do about &object->test depending on a different type of object? What if the object is passed into a function like the following?

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 much code like this, so I can't write an if condition every time. Also, I am unable to change the structures' layout in any way, shape, or form.

8 Answers

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;

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).

#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);

If you want to keep the syntax of ->, there is no way but to use this method and declare each of your class members in XYZWrapper:

struct XYZWrapper
{
    XYZWrapper(void* ptr)
    {
        if (x == 1)
        {
            extra = nullptr; // doesn't exist in x == 1
            test = reinterpret_cast<XY*>(ptr)->test;
        }
        else if (x == 2)
        {
            extra = reinterpret_cast<XYZ*>(ptr)->extra;
            test = reinterpret_cast<XYZ*>(ptr)->test;
        }
    }
    XYZWrapper* operator->() { return this; }
    short* extra;
    int* test;
}

*XYZWrapper(object)->test = 1;
void* addressTo = XYZWrapper(object)->test;

void SomeFunction(XYZWrapper object)
{
    *object->test = 1;
    if (x == 2) *object->extra = 4;
}

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 vptr_t= std::variant<XY*, XYZ*>;
void foo(vptr_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:

vptr_t get_vptr_from_void(int x, void*p){
  switch(x){
    case 1: return reinterpret_cast<XY*>(p);
    case 2: return reinterpret_cast<XYZ*>(p);
  }
}

You can do this once ideally.

You can generate a function table at compile-time, then select the corresponding operation through the index at runtime:

#include <array>

struct XY { int test; };

struct XYZ {
  short extra;
  int test;
};

template<class... Objects>
constexpr std::array table = {
  +[](void* object) { reinterpret_cast<Objects*>(object)->test = 1; }...
};

void Function(void* object, int index) {
  // Register your class
  const auto& dispatches = table<XY, XYZ>;
  dispatches[index](object);
}

Demo.

This is a use case for type erasure. You basically want to make a section of the code not care about the actual type. (But still make sure that there is validation of the type elsewhere.)

#include <memory>

struct Car
{
    std::string model;
    int numWheels;
};

struct Truck
{
    int weight;
    int numWheels;
    std::string model;
};

struct BaseVehicleWrapper
{
    virtual void setNumberWheels(int value) = 0;
    virtual void setModel(const std::string& model) = 0;
};

template <typename T>
struct VehicleWrapper : public BaseVehicleWrapper
{
    T& m_obj;
    VehicleWrapper(T& obj) : m_obj(obj) { }
    void setNumberWheels(int value) override { m_obj.numWheels = value; }
    void setModel(const std::string& model) override { m_obj.model = model; }
};

template <typename T>
std::unique_ptr<BaseVehicleWrapper> CreateVehicleWrapper(T& obj)
{
    return std::make_unique<VehicleWrapper<T>>(obj);
}

Now, usage. You don't need to worry about the nitty-gritty. Just call CreateVehicleWrapper where you need it.

In its most natural form, it does encourage you decrease the amount of code that is in branches, which some people consider makes for cleaner code.

Car car;
Truck truck;

bool selectCar = true;

auto selectedVehicle = selectCar ? CreateVehicleWrapper(car) : CreateVehicleWrapper(truck);

selectedVehicle->setNumberWheels(4);
selectedVehicle->setModel("Beetle");

I would also go on further to say that

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

should just be a member of the wrapper itself, with the object and type information "baked into" the wrapper, rather than having switching logic everywhere. Which means you are shifting the switching logic up the call stack.

void Function() override
{
    m_obj.test = 1;
}

This answer doesn't fulfill the last requirement of the question: "... I am unable to change the structures' layout in any way or form.". However I will leave it here as reference, since this is canonical way to implement this using inheritance.


It seems that x in some sense is keeping the information about the runtime type of the object. Worst of all, it is global variable it seems.

This is what inheritance for polymorphism is supposed to solve.

Without getting into the confusing topic of virtual inheritance (of member variables) you could design the classes differently:

class XY_base{
public:
   virtual int& Test() = 0;
};

class XY : public XY_base
{
    int test;
public:
    int& Test() override{return test;}
};

class XYZ : public XY_base
{
    short extra;
    int test;
public:
    int& Test() override{return test;}
};

Now the classes are larger (size in bytes) but you don't need to keep a separate tag variable x to recover the type, it is all handled by the virtual table.

This means that now you don't need the conditional, Function looks like this:

void Function(XY_base* object)
{
    object->Test() = 1;  // no IF ELSE
}

(You can even omit XY_base and let XY be the true base, although in that case the memory layout will be different.)

This is the textbook application of runtime polymorphism.

In C++14, you can use a generic lambda:

#include <cstdlib>

template <typename F>
inline static auto dispatch(int x, void *ptr, F perform) -> auto
{
    if (x == 1)
        return perform(reinterpret_cast<XY *>(ptr));
    if (x == 2)
        return perform(reinterpret_cast<XYZ *>(ptr));
    std::abort();
}

void Function(void* object)
{
    dispatch(x, object, [] (auto ptr) {
        ptr->test = 1;
    });
}

A future revision of the C++ standard may allow you to do this (syntax as of P1858R2 §3.8.1):

#include <ranges>

template <int i>
struct types {};

template <>
struct types<1>
{ typedef XY type; };

template <>
struct types<2>
{ typedef XYZ type; };

void Function(void* object)
{
    template for (constexpr int i : std::views::iota(1, 3)) {
        if (x == i) {
            reinterpret_cast<typename types<i>::type *>(object)->test = 1;
        }
    }
}
Related