Stepan Loginov
Stepan Loginov

Reputation: 1767

Any "for each" like hint

I have two classes for example

struct point{
dint data
};

class data{
 ...
public:
 point left;
 point right;
 ..... //more that 50 members of point
 point some_other_point;

}example;

Is it possible use something like "for each point in example" in this situation? Because now I need to modify many functions if I add one more point to data. Or maybe, there is any other idea about it.

Upvotes: 0

Views: 79

Answers (5)

Matthieu M.
Matthieu M.

Reputation: 299760

Yes, and there are two ways to do so:

  • an iterator class, for external iteration
  • a visitation method, for internal iteration

Then the iteration logic is encapsulated in either of those classes and all the code just uses them.


Using the iterator class.

Pros:

  • can easily be combined with existing STL algorithms, as well as for and while loops
  • can suspend (and resume) iteration

Cons:

  • requires polymorphism of attributes iterated over

Example:

class DataIterator;

class Data {
public:
    friend class DataIterator;

    Data(Point a, Point b, Point c): _one(a), _two(b), _three(c) {}

    DataIterator begin();
    DataIterator end();

private:
    Point _one; 
    Point _two;
    Point _three;
}; // class Data

class DataIterator:
    public std::iterator<std::forward_iterator_tag, Point>
{
public:
    struct BeginTag{};
    struct EndTag{};

    DataIterator(): _data(0), _member(0) {}

    DataIterator(Data& data, BeginTag): _data(&data), _member(0) {}
    DataIterator(Data& data, EndTag): _data(&data), _member(N) {}

    reference operator*() const {
        this->ensure_valid();
        MemberPtr const ptr = Pointers[_member];
        return _data->*ptr;
    }

    pointer operator->() const { return std::addressof(*(*this)); }

    DataIterator& operator++() { this->ensure_valid(); ++_member; return *this; }
    DataIterator operator++(int) { DataIterator tmp(*this); ++*this; return tmp; }

    friend bool operator==(DataIterator const& left, DataIterator const& right) {
        return left._data == right._data and left._member == right._member;
    }

    friend bool operator!=(DataIterator const& left, DataIterator const& right) {
        return not (left == right);
    }

private:
    typedef Point Data::*MemberPtr;

    static size_t const N = 3;
    static MemberPtr const Pointers[N];

    void ensure_valid() const { assert(_data and _member < N); }

    Data* _data;
    size_t _member;
}; // class DataIterator

//
// Implementation
//
DataIterator Data::begin() { return DataIterator(*this, DataIterator::BeginTag{}); }
DataIterator Data::end() { return DataIterator(*this, DataIterator::EndTag{}); }

size_t const DataIterator::N;

DataIterator::MemberPtr const DataIterator::Pointers[DataIterator::N] = {
    &Data::_one, &Data::_two, &Data::_three
};

And in case you wonder: yes, it really works.


Using the visitation method, though, is easier.

Pros:

  • can easily accommodate variance in the attributes types iterated over

Cons:

  • cannot be combined with existing STL algorithms or existing loops
  • pretty difficult to suspend the iteration part-way

Example:

class Data {
public:
    Data(Point a, Point b, Point c): _one(a), _two(b), _three(c) {}

    template <typename F>
    void apply(F&& f) {
        f(_one);
        f(_two);
        f(_three);
    }

private:
    Point _one; 
    Point _two;
    Point _three;
}; // class Data

And of course, it works too.

Upvotes: 2

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275330

You can write a for each function without modifying your example like this:

template<class Func>
void for_each_point(example& e, Func&& f){
  f(e.pt1);
  f(e.pt2);
  f(e.blah);

....

  f(e.last_pt);
}

and then call it like:

for_each_point(exam, [&](point & pt){
  std::cout<<pt.data<<"\n";
});

or do whatever in the body.

This function could also be a member variable, if you prefer.

Changing tye point storage to an array or std::array amd exposing begin and end or the array also works.

Finally, you could write a custom iterator that walks the points, but that is probably unwise.

Upvotes: 1

marcinj
marcinj

Reputation: 49976

You can convert your current fields into:

private:
 point left;
 // ..

public:
 point& left() { return points[LEFT]; }
 // ..

where points might be an array of points (as in other answers), and LEFT is a constant index into array. This should allow for a relatively quick and painless transition, you will only have to add (), and compiler will output errors where to apply fixes.

Then you can convert your code to iterate your point values.

Upvotes: 1

UldisK
UldisK

Reputation: 1639

Do it like this:

class data{
    public:

    enum POINTS {LEFT=0,RIGHT,SOME_OTHER_POINT};
    std::array<point,50> points; // or just point points[50];
 }example;

And use it like this:

example.points[data::LEFT]=point{};

Then you can iterate over points array with standard techniques.

Upvotes: 2

Lightness Races in Orbit
Lightness Races in Orbit

Reputation: 385104

No, you cannot enumerate members of a type, because C++ does not have the concept of reflection.

This is a common use case for an array, a vector or a map.

Upvotes: 4

Related Questions