1

I have a class that contains the vector of elements of the specific class. The main idea is to generate periodic sequence of the elements, based on the one period of the sequence (elems_) and the number of the periods (nperiod_) so I do not need to store all elements, but just one period.

class PeriodicContainer
{
private:
  std::vector<Class> elems_; // elements
  size_t nperiod_; // period of repetition of elems_
public:
  PeriodicContainer();
  PeriodicContainer(const std::vector<Class>& elems, size_t nperiod);
  /*...*/
}

Is it possible to implement custom iterator for the PeriodicContainer so that I can do things like (semi-pseudo-code):

PeriodicContainer container({Class(1), Class(2)}, 4);
for (auto it : container)
  std::cout << it << '\n';

and the output will be

Class(1)
Class(2)
Class(1)
Class(2)
Class(1)
Class(2)
Class(1)
Class(2)
  • 1
    You could adapt any *RandomAccessIterator* to behave like this. Just check in the `++` operator you reached the end and go back to the start in that case. – Jan Schultke Aug 18 '20 at 20:10
  • 1
    Does this answer your question? [Is there a standard cyclic iterator in C++](https://stackoverflow.com/questions/2616643/is-there-a-standard-cyclic-iterator-in-c) – Jan Schultke Aug 18 '20 at 20:13

2 Answers2

2

If you can use range-v3, you can do:

namespace rv = ranges::views;    

std::vector<Class> Container { Class(1), Class(2) };

for (auto it : rv::repeat_n(Container, 4) | rv::join)
    std::cout << it;

and not have to write any additional code yourself. This will also work for any contiguous container, not just std::vector.

Here's a demo.

cigien
  • 57,834
  • 11
  • 73
  • 112
0

If your underlying container is simply a std::vector, then you know that it's a contiguous container -- which actually makes this quite easy.

You can form an iterator from the following:

  • A pointer (or reference) to the container being iterated, and
  • The current iteration count (note: not 'index'). This will be used as "index" into the underlying container's operator[] after wrapping around the container's size().

The behavior of this iterator would be simply:

  • Each increment just increments the current count
  • Each dereference returns (*elems_)[current_ % elems_->size()], which will account for the loop-around for the "period".
  • The begin() would simply return an iterator with a 0 count, and
  • The end() would return an iterator with a count of elems_.size() * nperiod_

An example of what this could look like as a LegacyForwardIterator is the following:


template <typename T>
class PeriodicContainerIterator
{
public:
    using value_type = T;
    using reference = T&;
    using pointer = T*;
    using difference_type = std::ptrdiff_t;
    using iterator_category = std::forward_iterator_tag;
 
    PeriodicContainerIterator(std::vector<T>* elems, std::size_t current)
        : elems_{elems},
          current_{current}
    {}

    reference operator*() {
        return (*elems_)[current_ % elems_->size()]
    }
    pointer operator->() {
        return &(*elems_)[current_ % elems_->size()];
    }
    PeriodicContainerIterator& operator++() const {
        ++current_;
        return (*this);
    }
    PeriodicContainerIterator operator++(int) const {
        auto copy = (*this);
        ++current_;
        return copy;
    }
 
    bool operator==(const PeriodicContainerIterator& other) const {
        return current_ == other.current_;
    }
    bool operator!=(const PeriodicContainerIterator& other) const {
        return current_ != other.current_;
    }
    
private:
    std::vector<T>* elems_;
    std::size_t current_;
};

The container would then define begin() and end() as:


PeriodicContainerIterator<Class> begin() {
    return PeriodicContainerIterator<Class>{&elems_, 0};
}
PeriodicContainerIterator<Class> end() {
    return PeriodicContainerIterator<Class>{&elems_, elems_->size() * nperiod_};
}

You could easily make this all the way up to a LegacyRandomAccessIterator, but this requires a lot of extra functions which will bulk this answer.


If you don't specifically need this as an iterator but just want a simple way to visit each element in the periodic sequence, it might be easier to read / understand if you were to make this into a for_each-like call that expects a callback instead. For example:

template <typename Fn>
void forEach(Fn&& fn)
{
    for (auto i = 0; i < nperiod_; ++i) {
        for (auto& e : elems_) {
            fn(e);
        }
    }
}

Which allows for use like:

container.forEach([&](auto& e){
    // 'e' is each visited element
});
Human-Compiler
  • 11,022
  • 1
  • 32
  • 59
  • How would you implement this for a custom container though instead of specifically for `std::vector`? OP specifically put *custom container* in the title. It would make more sense basing the implementation off of a *RandomAccessIterator* which would then work for any container that provides one. On a side note, you should take a `std::vector&` in your constructor and then turn that into a pointer. You don't want to accept nullable parameters, so why not use a reference? – Jan Schultke Aug 18 '20 at 20:29
  • This _is_ an iterator for the custom container -- it's written specifically based around the implementation of the `PeriodicContainer`, which is what is asked. Iterators that work over their underlying containers are often tightly coupled. As for the reference vs parameter point: that's a matter of opinion. The pointer is not meant to imply nullability, it's meant to be more readable at the call-site that this is capturing the non-owning lifetime of the object (e.g. `&elems_` instead of `elems_`, which isn't as easily readable for lifetime capturing) – Human-Compiler Aug 18 '20 at 20:32
  • Now I understand your approach. It seems counter-intuitive to implement a concept as generic as a periodic iterator in such a specific way that it only works for a single class. But I guess that keeps this example simple. As for the pointers vs reference conventions, a pointer is technically always nullable which always leads to the question whether you can pass `nullptr`. Other than that this argument really is opinion-based and not worth discussing. – Jan Schultke Aug 18 '20 at 20:41
  • Many-many thanx! I will definetely use it in my code. Actually, I wanted to do something like this but decided ask here because of lack of experience. –  Aug 18 '20 at 20:47