1

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_)}; }
1
  • You can safely derive from nearly any class, provided that the inheritance is private. You will need using declarations to re-export members you want to be accessible. Commented Feb 4, 2016 at 17:19

2 Answers 2

3

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.

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

Comments

3

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.

4 Comments

No not that typedef, you need pointer, value_type and reference
@DieterLücking - aren't the ones inherited from the base class sufficient?
No, the value_type is shared_ptr<T>, but the iterator returns T
@DieterLücking - yes, of course. operator* is overloaded. Although the typedef for iterator_type is also needed.

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.