Brandon Lyons
Brandon Lyons

Reputation: 463

Reading in each data member of an unspecified struct from XML

I have some structs with varying types and amounts of data members:

struct Foo
{
    int a;
    float b;
    char c;
}

struct Bar
{
    std::string a;
    int b;
}

struct Baz
{
    char a;
}

I want to fill these data members with values contained in an external file. Is there a general solution such that if I later add a new struct with different data members, I won't have to alter my code that's reading in the data? I'd prefer to keep any functionality independent of these structs, but my best idea at the moment is a polymorphic solution where each struct has a static method responsible for reading in its data.

Upvotes: 1

Views: 98

Answers (1)

Johannes Matokic
Johannes Matokic

Reputation: 942

C++ as of now doesn't have a way to reflect the content of a struct. This means that there is no truly generic way to read into any given struct (neither by name nor by index). However you might create some kind of meta-description which might result in less overhead than to rewrite the XML deserialization for each structure.

Such a meta-description could look like (requires C++17):

enum FieldType { String, Int, Float, Char };

struct AbstractField
{
    FieldType const type;
    const char * const name;
    AbstractField(FieldType type, const char *name) : type(type), name(name) {}
    virtual ~AbstractField() {}
    virtual bool setValue(std::any & dest, const std::any & value) const = 0;
}

template <typename Struct, typename Type>
struct Field : public AbstractField
{
    Type Struct::*const fieldPtr;
    Field(FieldType type, const char *name, Type Struct::*fieldPtr)
        : AbstractField(type, name), fieldPtr(fieldPtr)
    {}
    bool setValue(std::any & dest, const std::any & value) const override
    {
        Struct *realDest = std::any_cast<Struct>(& dest);
        const Type *realValue = std::any_cast<Type>(& value);
        if (realDest != nullptr && realValue != nullptr) {
            realDest->*fieldPtr = *realValue;
            return true;
        }
        return false;
    }
}

// The following has to be redone for each structure
static const std::vector<const AbstractField *> metaFoo = {
    new Field<Foo, int>(Int, "a", & Foo::a)),
    new Field<Foo, float>(Float, "b", & Foo::b)),
    new Field<Foo, char>(Char, "c", & Foo:c))
};

There could be added some extra logic to get this even fancier (type_id instead of the enum, templated functions that only take the name and the member pointer to create the Field instance, abstract factories for the individual structs, etc).

The reader could then set a value in the structure like this

std::any parseValue(const std::string & value, FieldType type);

bool setField(std::any &dest, const std::vector<const AbstractField*> &meta,
              const std::string & fieldName, const std::string & value)
{
    for (auto field : meta) {
        if (field->name == fieldName) {
            std::any value = parseValue(value, field->type);
            return field->setValue(dest, value);
        }
    }
    return false;
}

When combined with abstract factories for the structs (similar to what I've done for the fields) this allows to instanciate a whole tree of structures just from the meta-description.

Upvotes: 2

Related Questions