3

I am trying to design a data stuctures, which would enhance/supplement an existing one by storing some additional data about it's members.

Let's say we have:

class A {
 int x;
 string y;
};

And we want to have a GUI component associated with it, so the data members have corresponding GUI elements. I'd like to map the members to their respective components. Something like

class GuiA {
 int x;
 string y;
 map<MemberHandle, GuiElement*> guiHandles;
}

I don't have any restrictions, but I'd like the result to be easily convertible to the original type.

I am aware, that I could introduce a template e.g. GuiElementMember holding original data plus the GuiElement pointer, and swap class member for their decorated counterparts, so it would look like:

class GuiA {
 GuiElementMember<int> x;
 GuiElementMember<string> y;
}

but I'd like to avoid it, as it completely changes access patterns to data members and bloats it. I.e. it results with data members interleaved with pointers, that are not easy to strip out.

Ideally it would be possible to write GuiA as a derived class of A, or as a composition of A and something additional.

I was thinking about something like a template that class could produce the map. I could yield to write a custom class per component, but I don't think there is an easy way to map data members, so on the clients side it would look like getGuiMember(GuiA::x). The pointer to data member contains the member original type. I don't think it is possible to have something like "type-erased pointer to member" that could serve as a MemberHandle type.

The only thing that comes to my mind is a custom enum per component which would enumerate data members and serve as key type for a map (or a vector in this case), but it seems as an awful lot of information duplication and maintenance.

Is there some technique that allows mapping data members?

I don't really care about the implementational complexity as long as the interface is easy. I welcome boost or template magic. I also don't care about the performance of additional data access, it's extra stuff, but the plain class usage should not be impacted, so introduction of indirection that cannot be optimized is less welcomed.

EDIT: Please don't hinge on GUI thing it's an example. I am only concerned about storing some additional data per member without composing it with the member.

luk32
  • 15,812
  • 38
  • 62
  • 2
    This is why people complain about lack of reflection :) See Herb Sutter's metaclasses to get your mind blown. AFAIK the only automagic solution right now is macros, which we all love don't we? – Passer By Jun 22 '18 at 13:30
  • 1
    I like your `GuiElementMember` approach. With suitably overloaded `GuiElementMember::operator*()` and `GuiElementMember::operator->()` operators, the access pattern looks just like a pointer. I don't see why you are averse to interleaving data with pointers; if you have so many GUI elements that memory fragmentation becomes a problem, the GUI is unusable anyway :) – Thomas Jun 22 '18 at 13:30
  • @Thomas `y.size()` or `y.find_last_of()` won't compile out the box – Passer By Jun 22 '18 at 13:31
  • @PasserBy Good point, my second thought turned out to be better, edited. – Thomas Jun 22 '18 at 13:32
  • Last time I needed something like that, I ended by defining a specific *meta language*: a config file to declare the elements, and a Python script to generate the bunch of boiler plate C++ source. The multi purpose tool is with not doubt the macro system but IMHO is pretty ugly. Java has reflexion and the annotation concept, Python has reflexion, annotations and meta-classes. C++ has macro, and SFINAE, and no other provision for mid-level framework implementation. So different implementers have rolled their own, for example Qt... – Serge Ballesta Jun 22 '18 at 13:50
  • ... As an other example, MSVC offers an open framework development tool with its concept of wizard but is highly proprietary (one single compiler and one single platform). If you want to keep in the standard, the closer I can imagine is a combination of macros and traits classes. Good luck with it... – Serge Ballesta Jun 22 '18 at 13:53
  • Here's a question: It looks an awful lot like you are trying to build an Editor to live manipulate values. Are you building some sort of GUI editor? Do you need undo/redo support? There might be other approaches to your problem if this is the case. – James Poag Jun 22 '18 at 14:22
  • @JamesPoag No. I just have some sourced data, e.g. GUI, the data needs to be validated, so I need to be able to back propagate errors to sources, but for further processing I'd like to have plain data. It might be an XY problem. But I'd really like to have a technique to store some data for data members but separated from them. I.e. not composed via template. I came up with 2 or 3 ideas but I like none of them so far. – luk32 Jun 22 '18 at 14:29
  • @Thomas My motivation is that I'd like to initially process the data, and then easily forget the GUI payload for computation heavy processing. I can transcribe the `GuiElementMember` classes onto non-decorated types, but that would result in hand-duplicating all data types. I will probably defer to a template with `T value; GuiMember* guiMember;` and live with it... or do handweaving and wait for reflection, so one day I can swap it and keep the interface. – luk32 Jun 22 '18 at 14:37
  • There are probably other concerns coming your way for building a GUI. The rendering would presumably happen in a different thread, so have you thought about how to avoid race conditions on your members? Also, would the GUI read out all variables in every frame or would it only react when a member variable is changed? How would it be notified? What if there are a lot of updates in quick succession? – Max Langhof Jun 22 '18 at 15:20
  • @MaxLanghof The gui is just an example. I am concerned about storing additional data per member. I've edited question to avoid confusion. – luk32 Jun 22 '18 at 15:54

3 Answers3

2

You can use BOOST_FUSION_DEFINE_STRUCT to define your structures that can be iterated over with a for_each loop:

#include <boost/fusion/include/define_struct.hpp>
#include <boost/fusion/include/for_each.hpp>
#include <unordered_map>
#include <string>
#include <cstdint>

BOOST_FUSION_DEFINE_STRUCT(
    (demo), employee,
    (std::string, name)
    (int, age)
    )

struct GuiElement;
GuiElement* createGuiElement(char const* name);

using Mapping = std::unordered_map<size_t, GuiElement*>;

template<class T>
Mapping create_mapping(T&& t) {
    Mapping mapping;
    boost::fusion::for_each(t, [&](auto& member) {
        auto offset = reinterpret_cast<uintptr_t>(&member) - reinterpret_cast<uintptr_t>(&t);
        mapping[offset];
    });
    return mapping;
}

template<class T, class M>
GuiElement*& get_mapping_element(Mapping& mapping, T const& t, M const& member) {
    auto offset = reinterpret_cast<uintptr_t>(&member) - reinterpret_cast<uintptr_t>(&t);
    auto found = mapping.find(offset);
    if(found == mapping.end())
        std::abort();
    return found->second;
}

int main() {
    auto employee_mapping = create_mapping(demo::employee{});

    demo::employee e1;
    get_mapping_element(employee_mapping, e1, e1.name) = createGuiElement("name");
    get_mapping_element(employee_mapping, e1, e1.age) = createGuiElement("age");
}

In the code there is a Mapping, one per class. Each member is identified by its offset from the beginning of its enclosing class.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
0

In general, you use macros for such purposes. They can generate any kind of code/wrappers that you'd like, letting you have the usual access to your data, but also adding stuff you want/need. It ain't pretty, but it works.

There are some template libraries that can help here, like Boost.Fusion or Boost.Hana, but, you can also roll your own here if you don't have a use for their advanced features (which come with the long compilation price tag).

Also, if you can focus on a particular GUI framework, they have some support for such things. For example, Qt has its own "meta object" compiler.

srdjan.veljkovic
  • 2,468
  • 16
  • 24
0

You could try a template for this? e.g.

template <typename T>
class GuiItem : public T {
    map<MemberHandle, GuiElement*> guiHandles;
}

GuiItem<A> guiA;
guiA.x = 123;
guiA.y = "y";
guiA.guiHandles[handle] = element;

I'm not sure I understand the other requirements so this way may not work for you.

Hitobat
  • 2,847
  • 1
  • 16
  • 12