Home > other >  Automatic generation of member based operations?
Automatic generation of member based operations?

Time:09-17

Are there any built-in C mechanics that provide an ability to automatically generate code to call a function or operator on every member of a class?

For example, if we have hundreds of types that need to employ a specific set of functions, or operator. And within each one, each type needs to call that same function or operator for all of its members (and that's it).

For some specific examples, we could say the types have functions Save() and Load(), or stream operators << and >>. How can we automatically execute A.Save(s) B.Save(s) C.Save(s) when TYPE::Save(stream s) is called? If the code simply passes execution to all members identically, is there any way to automate the process?

Here is an example of some code that would be automatically generated (just the Save() function). EDIT NOTE: This example shows all 3 members as the same type, but the solution should work on members of any combination of types, as long as they all share the related function/operator

class TYPE
{
protected:
    XTYPE A, B, C;
public:
    void Save(stream s)
    {
        A.Save( s );
        B.Save( s );
        C.Save( s );
    }
};

One of my older engines used crazy looping macros to pull this off. It makes it much easier to build types using the macros (when there are hundreds of classes that use them), and the resulting classes are not difficult to understand, but the macros themselves are messy and overly complicated. I was hoping for something more.. built into the language.

CodePudding user response:

As P Kramer mentioned, there might be some reflection tricks you could use. Another option would be to try to reduce the amount of code you have to write. One trick is to create a lambda that takes a parameter pack, and applies some operation to every item in the pack using a fold expression, like so:

void Save(stream s)
{
    [&](auto&&... members) {
        (members.Save(s), ...);
    } (A, B, C);
}

The above works even if A, B and C have different types, as long as they all have a Save() member function.

You could also do this with regular template functions instead of lambdas, but the above is the shortest way to write it. It still requires you to list all the names of the member variables you want to apply the function on, you could consider putting that in a macro:

#define MEMBERS A, B, C

class TYPE
{
protected:
    XTYPE MEMBERS;
public:
    void Save(stream s)
    {
        [&](auto&&... members) {
            (members.Save(s), ...);
        } (MEMBERS);
    }
};

Inspired by Vincent's answer, you can also add a function that returns a std::tuple of references to member variables, which can then be iterated over using std::apply and the above lambda:

class TYPE
{
protected:
    XTYPE A, B, C;

    auto Members()
    {
        return std::forward_as_tuple(A, B, C);
    }
public:
    void Save(stream s)
    {
        std::apply([&](auto&&... members) {
            (members.Save(s), ...);
        }, Members());
    }
};

Slightly less flexible, but resulting in even more compact code is to create a member function that takes a function as an argument, and applies that function to all members:

class TYPE
{
protected:
    XTYPE A, B, C;

    void ApplyToMembers(auto&& func)
    {
        [&](auto&&... members) {
            (func(members), ...);
        } (A, B, C);
    }
public:
    void Save(stream s)
    {
        ApplyToMembers([&](auto&& member){ member.Save(s); });
    }
};

CodePudding user response:

This is a really object-oriented approach. You could use a design pattern called the Iterator pattern. It basically gives TYPE an interface called "Iterator" which implements hasNext() and next(). Then you'd put XTYPE into a vector<XTYPE> and execute on each.

class Iterator {
  virtual boolean hasNext() const = 0;
  virtual XTYPE *next() = 0;
};

class TYPE: public Iterator
{
private:
    size_t pos;
protected:
    vector<XTYPE> XTYPES;

public:
    void Save(stream s)
    {
        for (auto &XT : XTYPES ) {
            XT.Save(s);
        }
    }
    virtual boolean hasNext() const {
        if (pos => XTYPES.size() ) {
          return false;
        }
    };
    virtual XTYPE *next() {
        return XTYPES[pos  ];
    }

};

Basically, you have to make sure in one way or another that the types you want to call have some way to the group that you want to call. You can also keep A, B, C intact and just make a vector of pointers to each of the XTYPE's in the iterator class. Like so:

class Iterator {
  virtual boolean hasNext() const = 0;
  virtual XTYPE *next() = 0;
protected:
  int pos;
  vector<XTYPE*> XTYPES_ITER;
};

class TYPE: public Iterator
{
protected:
    XTYPE A, B, C;
public:
    Type() {
        XTYPES_ITER = {&A, &B, &C};
    }
    void Save(stream s)
    {
        for (auto XT : XTYPES_ITER ) {
            XT->Save(s);
        }
    }
};
  • Related