Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I easily override an (STL) iterator's category?

Tags:

c++

iterator

stl

Right now, I have a class that can satisfy an API requirement with a random-access iterator. However, I can envision a situation where the implementation will change and only a forward iterator can be provided.

Therefore, I would like to restrict callers from using the random-access functionality. I know I can write my own implementation (e.g. restricted_bar_iterator), but was wondering if there is anything simpler (i.e. requiring less coding).

class BAR { ... };

class FOO {
public:
    // Bad...clients may expect 'bar_iterator' to be random access...
    typedef std::vector<BAR>::iterator bar_iterator;

    bar_iterator begin_bar() const;
    bar_iterator end_bar() const;

    // Possible solution here!
    class restricted_bar_iterator :
        public std::iterator< std::forward_iterator_tag, BAR > { ... };
};


void baz()
{
    FOO foo;
    bar_iterator it = foo.begin_bar() + 5; // want a compile time error here!
}
like image 897
MarkB Avatar asked Mar 23 '23 05:03

MarkB


2 Answers

Here's an example using Boost Iterator Adaptor. I used int instead of BAR.

#include <boost/iterator/iterator_adaptor.hpp>
#include <vector>

struct iterator :
    public boost::iterator_adaptor<
        iterator,                    // the name of our class, see docs for details
        std::vector<int>::iterator,  // underlying base iterator
        boost::use_default,          // for value type
        boost::forward_traversal_tag // all the boilerplate for this!
    >
{
     // need this to convert from vector::iterator to ours
     explicit iterator(std::vector<int>::iterator i)
      : iterator::iterator_adaptor_(i) {}
};

int main()
{
    std::vector<int> v;
    iterator it(v.begin());
    ++it;    // OK
    it += 1; // ERROR
}

This effectively uses a std::vector<T>::iterator as a base class, but only allows operations that are defined for forward iterators. The downside are the error messages - they aren't very pretty.

like image 56
jrok Avatar answered Apr 02 '23 07:04

jrok


You definitely have to do some coding, but you might be able to inherit from the underlying type to get most of the functionality, just overriding the operations you don't want to work, either by defining them as deleted in C++11 or making them private and unimplemented in C++03:

class FOO {
    // Bad...clients may expect 'bar_iterator' to be random access...
    typedef std::vector<BAR>::iterator bar_iterator_impl;

public:
    // Possible solution here!
    struct bar_iterator : bar_iterator_impl {
      bar_iterator& operator++() {
        ++static_cast<bar_iterator_impl&>(*this);
        return *this;
      }
      bar_iterator operator++(int) {
        bar_iterator copy(*this);
        ++*this;
        return copy;
      }

      typedef std::forward_iterator_tag iterator_category;    
      typedef std::iterator_traits<bar_iterator_impl>::value_type value_type;
      typedef std::iterator_traits<bar_iterator_impl>::difference_type difference_type;
      typedef std::iterator_traits<bar_iterator_impl>::pointer    pointer;
      typedef std::iterator_traits<bar_iterator_impl>::reference  reference;

    private:
      friend void operator+(bar_iterator const&, long);
      friend void operator+(long, bar_iterator const&);
      friend void operator-(bar_iterator const&, long);
      friend void operator-(long, bar_iterator const&);
    };

    bar_iterator begin_bar() const;
    bar_iterator end_bar() const;
};

However this only works if std::vector<BAR>::iterator is a class type, and it could be a pointer, in which case it cannot be derived from. To be portable, you'd need to define the whole iterator API yourself.

like image 31
Jonathan Wakely Avatar answered Apr 02 '23 07:04

Jonathan Wakely