1

In my project I have some custom classes, with roughly the structure below.

Point, which represents a 2D int coordinate

struct Point {
  int x;
  int y;
}

Line, which represents a line drawn between two Points

class Line {
    Point start;
    Point end;
}

Polygon, which represents the polygon that is defined by number of Points.

class Polygon {
  private:
    std::vector<Point> _vertices;
}

I'm trying to implement a custom iterator for Polygon, the goal being a syntax something like:

Polygon aPolygon;
Point somePoint;
for( auto line_it = aPolygon.begin(); line_it != aPolygon.end(); line_it++ ){
    if( line_it->includes(somePoint) ){
        // Do something
    }
}

When iterating over Polygon the n:th element would be
Line( _vertices[n], _vertices[n+1] ),
and the last would be
Line( _vertices[_vertices.size() - 1], _vertices[0] )

How do I implement such an iterator, or otherwise acheive similar syntax with acceptable performance?

I'm looking through similar questions, but I've yet to find one similar enough for a comprehensive answer. If possible I would prefer a STL solution that uses c++20 standard.


I realize the question was unecessarily vague, more specifically I'm asking how I can implement the *,-> and ++ operators for my iterator.

class Polygon {
  public:
    /* ... */

    struct PolygonIterator {
        using iterator_category = std::input_iterator_tag;
        using difference_type = std::ptrdiff_t;
        using value_type = Line;
        using pointer = value_type*;
        using reference = value_type&;
        
        explicit PolygonIterator( Point* ptr ) : m_ptr( ptr ) {}
        reference operator*();
        // ^ Should return Line reference, but I have Point in container?
        PolygonIterator& operator++();
        pointer operator->();
        bool operator==(const PolygonIterator& other);
      private:
        Point* m_ptr;
    };
    PolygonIterator begin() { return PolygonIterator( &_vertices.front() ); }
    PolygonIterator end() { return PolygonIterator( &_vertices.back() ); }
2
  • Step 1: Pick an iterator category. You can start with a 'forward iterator'. Step 2: Make a class. Read through the requirements for your category, and implement all of them for the class. Commented Jul 4, 2021 at 17:53
  • @HolyBlackCat Could you elaborate? I've been reading cppreference and other similar questions here on stackoverflow but still don't understand what the implementations of the requirements are supposed to look like. Commented Jul 4, 2021 at 19:40

2 Answers 2

1

Looking at your code again, satisfying forward iterator requirements would be tricky, because you essentially generate the lines on the fly. Hence I suggest making an input iterator.

operator++ should just increment m_ptr, nothing unusual. But you might want to store an std::vector iterator instead of a pointer (then, if you enable iterator debuggning for standard containers, it will extend to your iterators).

Then you have two options:

  1. Store the current Line inside of the iterator. Then * and -> return a reference and a pointer to it respectively. ++ will need to update this Line after incrementing the pointer/iterator.

  2. Return the Line from operator* by value, and change using reference to Line to match the return type. This is unusal, but allowed for input iterators.

    Then operator-> becomes tricky. It can't return a pointer (because there's no Line to point to), so it has to return a helper class by value, which in turn would store a Line (again by value), and overload -> to return a pointer to it. You probably should also change using pointer to match the type of this helper class.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you! Option 2 was exactly what I was looking for, and even seems to perform slightly better than what I was using before :) The operator-> isn't the most beautiful, but I rarely use it anyway, and it seems to work intuitively. After reading through the articles linked by @Serge Ballesta in another answer I think this functionality could be acheived with the new ranges/views but I haven't been able to wrap my head around that yet.
1

Welcome into the world of containers which are not containers!

Since the beginning, standard library containers are expected to actually directly contain their objects. I could find 2 reference articles about that:

Long story made short your pseudo container is only a proxy container: objects exists elsewhere, and the container can only build proxies to them. It is not so uncommon and vector<bool> is such a container, and its iterators are only proxy iterators. Simply if you look into the sources of an implementation of the standard library, you will find every now and then special processing for vector<bool> in algorithm because vector<bool> iterators pretend being random access iterators when they cannot even meet the requirements of a forward container.

That means that your iterators will only be proxy iterators and their reference type will be an object (the proxy) instead of a true reference. Because of that they will be no more than simple input iterators. It is is acceptable for you, that will be enough, else you could have a look to the range library of C++20, which should start to provide some support to proxy containers and iterators.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.