3

I'd like an iterator in C++ that can only iterate over elements of a specific type. In the following example, I want to iterate only on elements that are SubType instances.

vector<Type*> the_vector;
the_vector.push_back(new Type(1));
the_vector.push_back(new SubType(2)); //SubType derives from Type
the_vector.push_back(new Type(3));
the_vector.push_back(new SubType(4)); 

vector<Type*>::iterator the_iterator; //***This line needs to change***

the_iterator = the_vector.begin();
while( the_iterator != the_vector.end() ) {
    SubType* item = (SubType*)*the_iterator;
    //only SubType(2) and SubType(4) should be in this loop.
    ++the_iterator;
}

How would I create this iterator in C++?

Alex B
  • 24,678
  • 14
  • 64
  • 87
  • 1
    Isn't it possible to make your own iterator (or subclass) that will skip anything but SubType objects? – Scott M. Apr 21 '09 at 19:49

5 Answers5

12

boost filter iterator?

Sanjaya R
  • 6,246
  • 2
  • 17
  • 19
  • 1
    good answer, wanted to go for it too :) Would be even better with a small example. I would propose BOOST_FOREACH(Type *t, make_pair(make_filter_iterator(ll_dynamic_cast(_1), v.begin(), v.end()), make_filter_iterator(ll_dynamic_cast(_1), v.end(), v.end())) { ... } (using boost.iterators, boost.lambda and boost.foreach) – Johannes Schaub - litb Apr 21 '09 at 20:18
  • Great! I felt like there is should be ready to use solution. Thanks. Never know what else has boost. – Mykola Golubyev Apr 21 '09 at 20:36
9

You must use a dynamic cast.

the_iterator = the_vector.begin();
while( the_iterator != the_vector.end() ) {
    SubType* item = dynamic_cast<SubType*>(*the_iterator);
    if( item != 0 )
       ... 

    //only SubType(2) and SubType(4) should be in this loop.
    ++the_iterator;
}
Jem
  • 2,255
  • 18
  • 25
  • 1
    This is a solution to the 'check the proper subtype' part of the question, but it has a flaw in that after reaching the last SubType element it will keep incrementing the iterator beyond the end of the container. – David Rodríguez - dribeas Apr 21 '09 at 20:25
  • dribeas, i don't see how that could ever happen. he's got the right condition at the loop's head. – Johannes Schaub - litb Apr 21 '09 at 20:35
  • 1
    Original question did not even mention dynamic cast, so I supposed that the poster did not know dynamic casts, and was looking in the wrong direction for the solution (change something related to the iterator). Before suggesting a solution (build your own iterator) that is probably more-than-necessary complex, I prefered to first propose the simpliest way to solve the original problem. Then, as my elipsis and indentation imply, the iterator must not be in the if( item != 0 ) block, of course. – Jem Apr 21 '09 at 20:37
4

Solution without boost. But if you have an access to the boost library - use Filter Iterator as was proposed.

template <typename TCollection, typename T>
class Iterator
{
public:
    typedef typename TCollection::iterator iterator;
    typedef typename TCollection::value_type value_type;

    Iterator(const TCollection& collection,
             iterator it):
        collection_(collection),
        it_(it)
    {
        moveToNextAppropriatePosition(it_);
    }
    bool operator != ( const Iterator& rhs )
    {
        return rhs.it_ != it_;
    }
    Iterator& operator++()
    {
        ++it_;
        moveToNextAppropriatePosition(it_);
        return *this;
    }
    Iterator& operator++(int);
    Iterator& operator--();
    Iterator& operator--(int);
    value_type& operator*()
    {
        return *it_;
    }
    value_type* operator->()
    {
        return &it_;
    }
private:
    const TCollection& collection_;
    iterator it_;
    void moveToNextAppropriatePosition(iterator& it)
    {
        while ( dynamic_cast<T*>(*it) == NULL && it != collection_.end() ) 
            ++it;
    }
};

class A
{
public:
    A(){}
    virtual ~A(){}
    virtual void action()
    {
        std::cout << "A";
    }
};
class B: public A
{
public:
    virtual void action()
    {
        std::cout << "B";
    }
};
int main()
{
    typedef std::vector< A* > Collection;
    Collection c;
    c.push_back( new A );
    c.push_back( new B );
    c.push_back( new A );

    typedef Iterator<Collection, B> CollectionIterator;
    CollectionIterator begin(c, c.begin());
    CollectionIterator end(c, c.end());

    std::for_each( begin, end, std::mem_fun(&A::action) );
}
Mykola Golubyev
  • 57,943
  • 15
  • 89
  • 102
2

As paintballbob said in a comment, you should create your own iterator class, perhaps inheriting from vector<Type*>::iterator. In particular, you will need to implement or override operator++() and operator++(int) to ensure that you skip non-SubType objects (you can use dynamic_cast<SubType*>() to check each item). There is a nice overview of implementing your own container and iterator in this O'Reilly Net article.

Rick Copeland
  • 11,672
  • 5
  • 39
  • 38
  • You must be careful not to increment the iterator beyond the end of the container. For that you will need your wrapper to keep a copy of the 'end' iterator so that you will have a stop condition. That or just use boost::filter_iterator as Sanjaya suggests in another answer. – David Rodríguez - dribeas Apr 21 '09 at 20:21
2

Just another way how to do it using boost iterators. This time, using std::remove_copy_if:

std::remove_copy_if(v.begin(), v.end(), 
    boost::make_function_output_iterator(boost::bind(&someFunction, _1)),
    !boost::lambda::ll_dynamic_cast<SubType*>(boost::lambda::_1));

It will call a function (In this example someFunction. But it can be anything boost::bind can construct - also a member-function) for each pointer that's pointing to a SubType.

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212