1
\$\begingroup\$

I'm implementing an iterator which takes a single value and which you can advance for n times with a dereferencing yielding the single value - but before or after that range of n 'positions' you can't dereference. So it's as though you filled a length-n std::vector with a single value and were taking a const iterator over it.

Imlementation is intentionally not thread-safe.

#include <stdexcept>
#include <iterator>
#include <cstddef>

template <typename E>
struct iterate_n_wrapper : public std::iterator<std::input_iterator_tag, E, size_t> {
    iterate_n_wrapper() = delete; // ... so it doesn't meet the forward iterator requirements
    iterate_n_wrapper(const iterate_n_wrapper<E>& other) = default;
    iterate_n_wrapper(const E& datum_, size_t times_)
        : datum(datum_), times(times_), pos(0) { };

    const E & operator[](off_t offset) const {
        auto offset_pos = pos + offset;
        if (offset_pos >= 0 && offset_pos < times) { return datum; }
        throw std::out_of_range("iteration range exceeded");
    }

    const E & operator *() const { return operator[](0); }
    const E * operator ->() const { return &(operator *()); }

    iterate_n_wrapper& operator ++() { pos++; return *this; }
    iterate_n_wrapper& operator --() { pos--; return *this; }

    iterate_n_wrapper operator ++(int) { auto ret = *this; operator++(); return ret; }
    iterate_n_wrapper operator --(int) { auto ret = *this; operator--(); return ret; }

    bool operator ==(const iterate_n_wrapper &other) const {
        return
            datum == other.datum &&
            times == other.times &&
            pos   == other.pos;
    }
    bool operator !=(const iterate_n_wrapper &other) const { return !operator==(other); }

protected:
    const E   datum;
    size_t    times;
    ptrdiff_t pos;
};

A simple example of use:

int main() {
    int a = 123;
    iterate_n_wrapper<int> iwa(a, 6);
    for(int i = 0; i < 4; i++) { 
         std::cout << "iwa[" << i << "] = " << iwa[i] << "\n"; 
    }
    for(int i = 0; i < 100; i++) {
        std::cout << "i = " << i << ", got " << *(iwa++) << "\n"; 
    }
    return 0;
}

produces:

iwa[0] = 123
iwa[1] = 123
iwa[2] = 123
iwa[3] = 123
i = 0, got 123
i = 1, got 123
i = 2, got 123
i = 3, got 123
i = 4, got 123
i = 5, got 123
terminate called after throwing an instance of 'std::out_of_range'
  what():  iteration range exceeded
Aborted (core dumped)

so, the motivation is that this is a stand-in for an array/vector iterator when you can get the length from someplace else. Although I guess the lack of some kind of corresponding end() is a shortcoming.

\$\endgroup\$
  • \$\begingroup\$ Could you give an example of typical use? As is I have a hard time seeing how you can use it (easily) with the standard algorithms as you usually need some kind of end() iterator. \$\endgroup\$ – user786653 Oct 15 '16 at 17:24
  • \$\begingroup\$ @user786653: Well, I added a use snippet but it doesn't really answer your question, except for the last sentence perhaps. \$\endgroup\$ – einpoklum Oct 15 '16 at 17:40
1
\$\begingroup\$

As I alluded to in my question in the comments I think you should consider having a past-the-end iterator that would allow your iterator to be used with standard algorithms. Example:

std::copy(iterate_n_wrapper<int>(42, 4), iterate_n_wrapper<int>(), std::ostream_iterator<int>(std::cout, " "));

See std::istream_iterator for an example from the standard library of this concept in action.

cppreference.com has a nice example of how to achieve this.

It doesn't seem like an iterator is actually required to implement "past-the-endness" (note: I haven't dug into the standard to check), but it's something I, and I think most other C++ users, would expect. And the text in the link strongly hints that it's expected.

Normally you'd only supply operator* for an iterator and not operator[], but that's up to you.

Also I don't see a reason for using size_t as the difference_type (third template parameter to std::iterator) - why not stick with the default of std::ptrdiff_t?

You also need to implement Swapability for it to be satisfy the iterator concept.

Except for the missing past-the-end iterator, these are all minor quibbles. Building on std::iterator is was until C++17 (see @Olzhas comment) the right choice and I don't see anything wrong with the rest of your implementation.

\$\endgroup\$
  • \$\begingroup\$ For a past-the-end check you need a sentinel, not a full blown iterator, I guess. But IIRC that's a fleshed-out notion in C++. Anyway, thanks for the links. \$\endgroup\$ – einpoklum Oct 15 '16 at 18:37
  • \$\begingroup\$ Also, for the past-the-end iterator, perhaps I should think about a function yielding a pair of structures, in which the sentinel could peek into the regular iterator's pos counter. \$\endgroup\$ – einpoklum Oct 15 '16 at 18:40
  • \$\begingroup\$ Since past-the-end iterators aren't dereferencable you can choose any representation that's convenient for you. See the notes concerning InputIterators for some ideas. (You could e.g. have any iterators where pos==times compare equal I think) \$\endgroup\$ – user786653 Oct 15 '16 at 18:57
  • 1
    \$\begingroup\$ @einpoklum: why do you need a sentinel for a past the end check. When pos >= times you are passed the end. To make the iterator as efficient as possible there is usually no check for going past the end. In the most common use case scenario your code adds extra checks for a situation that should never exist. You definitely should look at generating an end version that will match against past the end concept as this is how all other iterators work. \$\endgroup\$ – Martin York Oct 15 '16 at 21:49
  • \$\begingroup\$ @user786653, std::iterator is deprecated in C++17 . Please remove the part mentioning it. \$\endgroup\$ – Incomputable Oct 16 '16 at 4:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.