Make a "for each" loop on all class members?

Hi all,

I want to introduce store to file/read from file functionality to my class hierarchy.

What I currently do is of the form:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Lattice
{
public:
  /*...*/
  void WriteToFile(std::fstream& output);
  bool ReadFromFile(std::fstream& input);
};

class Polynomial
{
public:
  /*...*/
  void WriteToFile(std::fstream& output);
  bool ReadFromFile(std::fstream& input);
};

class QuasiPolynomial
{
public:
  Lattice AmbientLattice;
  Polynomial value; 
  void WriteToFile(std::fstream& output)
  { this->AmbientLattice.WriteToFile(output);
    this->value.WriteToFile(output);
  }
  bool ReadFromFile(std::fstream& input)
  { if (!this->AmbientLattice.ReadFromFile(input))
      return false;
    if (!this->value.ReadFromFile(input))
      return false;
    return true;
  }
};


What I would like to do is in lines 22-32 change to something like
1
2
3
4
  
  bool ReadFromFile(std::fstream& input)
  { //for each member of this class, execute member.ReadFromFile(input); //how should I do this?
  }

and similarly for the WriteToFile function.

The "for each" concept is key here, because sometimes I extend my objects by adding new class members, and because I have an already existing class inheritence tree, and that could save me lots of trivial and error-prone typing.

In the best case scenario, I would like that the compiler handles the for-each loop automatically, and informs me if any of the member of the class don't have the WriteToFile method.

I want the whole thing to be similar to the way the compiler automatically synthesizes operator= and copy constructors.

Can this be done at all, or somehow imitated?
Cheers,
tition
Last edited on
It cannot be done exactly the way you want it. The best would be something like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
//If you are doing COM, this interface is already defined.
class IPersistStream
{
public:
    virtual void Read(std::istream &is) = 0;
    virtual void Write(std::ostream &os) = 0;
};

class IPersistStreamCollection : public IPersistStream
{
public:
    typedef std::vector<IPersistStream*> CollType;

protected:
    //A collection of pointers to to other persistable objects.
    CollType _objects;

public:
    IPersistStreamCollection() : _objects()
    { }

    virtual void Read(std::istream &is)
    {
        for (CollType::const_iterator it = _objects.begin(); it < _objects.end(); it++)
        {
            (*it)->Read(is);
        }
    }
    virtual void Write(std::ostream &os)
    {
        for (CollType::const_iterator it = _objects.begin(); it < _objects.end(); it++)
        {
            (*it)->Write(os);
        }
    }
};

//Each one of the serializable classes that do not contain other serializable objects inherit IPersistStream:
class Polynomial : public IPersistStream
{
...
};

//Each one of the serializable classes that contain other serializable objects inherit from IPersistStreamCollection:
class QuasiPolynomial : public IPersistStreamCollection
{
private:
    //If you add a member here........
    Lattice AmbientLattice;
    Polynomial value;

public:
    QuasiPolynomial()
    {
        //........ also add the corresponding line here.
        _objects.push_back(&AmbientLattice);
        _objects.push_back(&value);
    }
};


And that is how you do it. If the QuasiPolynomial class also needs to add data to the stream or read data from the stream (data that is not part of any other member variable), you can override the Read() and Write() methods to account for this.
Thanks for your advice!

You suggestion is one notch less conceptual than what I would have liked: you are substituting what I think should be the compiler's task with the programmer's run-time duty.

Also, to implement your suggestion, I would have to make all of my larger objects inherit the IPersistStreamCollection class, and I have heard warnings against multiple inheritance.
If you think it should be done by the compiler, you could possible find or create an addon/plugin/whatever your IDE calls it that would do it for you like that.
Yeah,... that could actually work quite nicely. Instead of plugins/complicated stuff, I could do something dead simple as:


//macro_should_start_here
Lattice AmbientLattice;
Polynomial value;
//macro_should_end_here


and then write a simple function (with std::stringstream's), that, on seeing the string "//macro_should_start_here" would generate the needed methods (and write them to a separate .cpp file).

I will need to do some parsing work, however.
[Edit:] The immediate things I see that need to be done:
1. I will need to know inside which class do the members reside. I can simply count opening { and closing }, and look for the class keyword
2. To parse the class members I can simply do: odd words are types, even words are member names.
3. I have to be extra careful with templates. Example: List<Matrix<Rational> > theMatrices; (I actually do have such code!) in fact consists of three words, not two.

The part that feels evil to me is that I could include such a function as a part of the code on which it acts >:)
Last edited on
Take a look at Boost.Preprocessor (*) first. You can do really cool things with it...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <boost/preprocessor/seq/for_each.hpp>
#include <boost/preprocessor/tuple/elem.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <iostream>

#define DECL_MEMBER(r, data, elem) \
    BOOST_PP_TUPLE_ELEM(3, 0, elem) BOOST_PP_TUPLE_ELEM(3, 1, elem);

#define INIT_MEMBER(r, data, elem) \
    BOOST_PP_TUPLE_ELEM(3, 1, elem) = BOOST_PP_TUPLE_ELEM(3, 2, elem);

struct MyClass
{
    #define MEMBER_LIST       \
                              \
        ((int,    mem1,   1)) \
        ((int,    mem2,   2)) \
        ((double, mem3, 5.5)) \
        ((char,   mem4, 'c'))

    BOOST_PP_SEQ_FOR_EACH(DECL_MEMBER, ~, MEMBER_LIST)

    MyClass()
    {
        BOOST_PP_SEQ_FOR_EACH(INIT_MEMBER, ~, MEMBER_LIST)
    }

    void Print() const
    {
        #define PRINT_MEMBER(r, data, elem) \
            std::cout \
                << BOOST_PP_STRINGIZE(BOOST_PP_TUPLE_ELEM(3, 1, elem)) " = " \
                << BOOST_PP_TUPLE_ELEM(3, 1, elem) << std::endl;

        BOOST_PP_SEQ_FOR_EACH(PRINT_MEMBER, ~, MEMBER_LIST)

        #undef PRINT_MEMBER
    }

    #undef MEMBER_LIST
};

int main()
{
    MyClass my_object;

    my_object.Print();

    return 0;
}
mem1 = 1
mem2 = 2
mem3 = 5.5
mem4 = c

(*) http://www.boost.org/doc/libs/1_46_1/libs/preprocessor/doc/index.html
Last edited on
Topic archived. No new replies allowed.