Daniel
Daniel

Reputation: 8431

Deriving from standard container iterators

I have a class which has a private collection of std::shared_ptrs, like:

class Foo
{
private:
     using Bars = std::vector<std::shared_ptr<Bar>>;
     Bars items_;
}

Given an instance of Foo, I want to be able to iterate over the Bar objects in items_ directly - hiding that the collection actually contains pointers. I believe the only thing that needs to change from Bars::const_iterator is operator*, is it ok to just derive from it and implement operator*? i.e.

class Iterator : public Bars::const_iterator
{
public:
    Iterator(Bars::const_iterator it) : Bars::const_iterator {it} {}

    const string& operator*() const
    {
        return *Bars::const_iterator::operator*();
    }
};

And then provide begin and end methods for Foo:

Foo::Iterator Foo::begin() const noexcept { return Iterator {std::cbegin(items_)}; }
Foo::Iterator Foo::end()   const noexcept { return Iterator {std::cend(items_)}; }

Upvotes: 1

Views: 88

Answers (2)

Pete Becker
Pete Becker

Reputation: 76235

While most types in the standard library are not designed to be derived from, in this case it should be okay. The two dangers of inheritance are slicing and non-virtual destruction; for iterators these just aren't going to happen. No slicing because iterators are passed as template arguments, so the exact type is always used, and no non-virtual destruction because nobody in their right mind will create copies of iterators on the free store and delete them through a pointer to the base type (assuming they can figure out what it is).

EDIT: as Dieter Lücking points out, you'll also need to provide a typedef for iterator_type that matches your type:

typedef Iterator iterator_type;

EDIT: as Dieter Lücking points out, this one alone is not sufficient. You're providing an operator*, and need to provide all the typedefs that refer to the return type of that operator.

Upvotes: 3

user2249683
user2249683

Reputation:

For flexibility you may just write an adapter:

#include <type_traits>

template <typename Iterator>
class random_access_pointer_iterator
{
    // Types
    // =====

    public:
    typedef Iterator iterator_type;
    typedef std::random_access_iterator_tag iterator_category;
    using difference_type = typename iterator_type::difference_type;
    using pointer = decltype(&**std::declval<iterator_type>());
    using value_type = typename std::remove_pointer<pointer>::type;
    typedef value_type& reference;

    // Construction
    // ============

    public:
    explicit random_access_pointer_iterator(iterator_type iterator)
    :   m_iterator(iterator)
    {}

    // Element Access
    // ==============

    public:
    const iterator_type& base() const { return m_iterator; }
    iterator_type& base() { return m_iterator; }
    operator iterator_type () const { return m_iterator; }

    // Iterator
    // ========

    public:
    reference operator * () const { return **m_iterator; }
    pointer operator -> () const { return &(**m_iterator); }

    random_access_pointer_iterator& operator ++ () {
        ++m_iterator;
        return *this;
    }
    random_access_pointer_iterator operator ++ (int) {
        random_access_pointer_iterator tmp(*this);
        ++m_iterator;
        return tmp;

    }
    random_access_pointer_iterator& operator += (difference_type n) {
        m_iterator += n;
        return *this;
    }

    random_access_pointer_iterator& operator -- () {
        --m_iterator;
        return *this;
    }
    random_access_pointer_iterator operator -- (int) {
        random_access_pointer_iterator tmp(*this);
        --m_iterator;
        return tmp;
    }

    random_access_pointer_iterator& operator -= (difference_type n) {
        m_iterator -= n;
        return *this;
    }

    private:
    iterator_type m_iterator;
};

template <typename Iterator>
inline random_access_pointer_iterator<Iterator> operator + (
    random_access_pointer_iterator<Iterator> i,
    typename random_access_pointer_iterator<Iterator>::difference_type n) {
    return i += n;
}

template <typename Iterator>
inline random_access_pointer_iterator<Iterator> operator - (
    random_access_pointer_iterator<Iterator> i,
    typename random_access_pointer_iterator<Iterator>::difference_type n) {
    return i -= n;
}

template <typename Iterator>
inline typename random_access_pointer_iterator<Iterator>::difference_type
operator - (
    const random_access_pointer_iterator<Iterator>& a,
    const random_access_pointer_iterator<Iterator>& b) {
    return a.base() - b.base();
}

template <typename Iterator>
inline bool operator == (
    const random_access_pointer_iterator<Iterator>& a,
    const random_access_pointer_iterator<Iterator>& b) {
    return a.base() == b.base();
}

template <typename Iterator>
inline bool operator != (
    const random_access_pointer_iterator<Iterator>& a,
    const random_access_pointer_iterator<Iterator>& b) {
    return a.base() != b.base();
}

template <typename Iterator>
inline bool operator <  (
    const random_access_pointer_iterator<Iterator>& a,
    const random_access_pointer_iterator<Iterator>& b) {
    return a.base() <  b.base();
}

template <typename Iterator>
inline bool operator <= (
    const random_access_pointer_iterator<Iterator>& a,
    const random_access_pointer_iterator<Iterator>& b) {
    return a.base() <= b.base();
}

template <typename Iterator>
inline bool operator >  (
    const random_access_pointer_iterator<Iterator>& a,
    const random_access_pointer_iterator<Iterator>& b) {
    return a.base() >  b.base();
}

template <typename Iterator>
inline bool operator >= (
    const random_access_pointer_iterator<Iterator>& a,
    const random_access_pointer_iterator<Iterator>& b) {
    return a.base() >= b.base();
}


#include <cassert>
#include <memory>
#include <vector>

int main() {
    using vector = std::vector<std::shared_ptr<int>>;
    auto p = std::make_shared<int>(0);
    vector v = { p };

    using iterator = random_access_pointer_iterator<vector::iterator>;
    iterator a(v.begin());
    iterator b(v.end());

    assert(*a == 0);
    assert(a.operator -> () == &*p);
    ++a;
    assert(a == b);
    --a;
    assert(a != b);
    assert(a++ != b);
    assert(a-- == b);
    assert(a + 1 == b);
    assert(a == b - 1);
    assert(b - a == 1);
    assert(a <  b);
    assert(a <= b);
    assert(b >  a);
    assert(b >= a);
}

Having this, you can use any random access iterator (vector, deque, ... ) and use any pointer type (raw pointer, shared_ptr, ...)

Note: In your case - when you derive from the iterator of the vector you will have to adjust the type definitions, too.

Note: I do not like 'random_access_pointer_iterator', but I have nothing better on my mind.

Upvotes: 3

Related Questions