Range based for loops and multiple iterators

3.7k Views Asked by At

I have the following code, representing a mesh in a 3D application (some code ommited for clarity):

class Mesh {
public:
    typedef std::vector<Vertex> Vertices;
    typedef std::vector<int> Elements;

    template<class VerticesIt, class ElementsIt>
    Mesh(const VerticesIt verticesBegin,
         const VerticesIt verticesEnd,
         const ElementsIt elementsBegin,
         const ElementsIt elementsEnd) :
    vertices_(verticesBegin, verticesEnd),
    elements_(elementsBegin, elementsEnd) {
    }        
    virtual ~Mesh();

    typename Vertices::const_iterator verticesBegin() const {
        return vertices_.begin();
    };

    typename Vertices::const_iterator verticesEnd() const {
        return vertices_.end();
    };

    typename Elements::const_iterator elementsBegin() const {
        return elements_.begin();
    };

    typename Elements::const_iterator elementsEnd() const {
        return elements_.end();
    };

private:       
    Vertices vertices_;
    Elements elements_;

};

It works quite nicely, providing a clear interface to the internal data. No implementation details are exposed for the containers.

I have one little hiccup regarding this though. I can not use range based for loops, iterators has to be used:

for (auto it = mesh.verticesBegin(); it != mesh.verticesEnd(); ++it) {
// Do stuff
}

for (auto it = mesh.elementsBegin(); it != mesh.elementsEnd(); ++it) {
// Do stuff
}

A bit verbose for my taste. My preferred client code would instead look like this:

for(auto & vert : mesh.vertices) {
// Do stuff.
}

for(auto & element : mesh.elements) {
// Do stuff.
}

Is it possible to achieve this without exposing implementation details of the containers? Also, I would not like to wrap the containers into custom classes, since I want full access to the chosen container (std::vector) inside the Mesh class.

4

There are 4 best solutions below

0
On BEST ANSWER

You could use some sort of proxy, such as this:

template<typename Container>
class ConstIteratorProxy
{
public:
    ConstIteratorProxy(const Container& container) : container_(container) { }
    typename Container::const_iterator begin() const {
        return container_.begin();
    };
    typename Container::const_iterator end() const {
        return container_.end();
    };
private:
    const Container& container_;
};

And in Mesh:

ConstIteratorProxy<Vertices> vertices() const {
    return ConstIteratorProxy<Vertices>(vertices_);
}
ConstIteratorProxy<Elements> elements() const {
    return ConstIteratorProxy<Elements>(elements_);
}

Then to use it:

Mesh m;
for (auto& v : m.vertices()) { }
for (auto& e : m.elements()) { }
4
On

Rename your functions verticesBegin and verticesEnd to begin and end respectively. Then you would be able to write this:

for(auto & vert : mesh) 
{
// Do stuff.
}

Note that the range based for loop expects your containter to have begin and end as member functions — Or free functions begin and end which takes your container as argument.

Also note that the range-based for loop cannot iterate over two things — mesh can behave like just one single container, not two. So if you want to iterate over indices too, then you've to expose it, or abstract it and then expose vertices instead.

Or you could write a zip iterator which will iterate over both container simultenously in a single for loop.

0
On

It is quite easy with a proxy storing the two iterators with begin and end method implemented :

#include <iostream>
#include <vector>

template <typename T>
struct PairIt {
    PairIt(T&& b, T&& e) : b_{std::forward<T>(b)}, e_{std::forward<T>(e)} {}
    T begin() const { return b_; }
    T end() const { return e_; }
private:
    T b_,e_;
};

std::vector<int> badglobal { 1, 2, 3 };

PairIt<std::vector<int>::iterator> ForRangeProxy() {
    return { begin(badglobal), end(badglobal) };
};

int main() {
    for( auto v : ForRangeProxy() )
        std::cout << v << std::endl;
}

You can implement several ForRangeProxy for each collection your object want to give the access.

0
On

You could return an iterator wrapper from vertices() and elements() functions which you can pass to the for loop, e.g.

template<typename T>
class iterator_wrapper
{
public:
  iterator_wrapper(const typename std::vector<T>::const_iterator &begin_,
                   const typename std::vector<T>::const_iterator &end_)
    :m_begin(begin_), m_end(end_) {}
  typename std::vector<T>::const_iterator begin() const {return m_begin;}
  typename std::vector<T>::const_iterator end() const {return m_end;}

private:
  typename std::vector<T>::const_iterator m_begin, m_end;
};

Define vertices() and elements() functions in your mesh class:

iterator_wrapper<Vertex> vertices() {return iterator_wrapper<Vertex>(vertices_.begin(), vertices_.end());}
iterator_wrapper<int> elements()    {return iterator_wrapper<int>(elements_.begin(), elements_.end());}

Then call it like:

for(auto &vert:mesh.vertices())
{
  //....
}

for(auto &element:mesh.elements())
{
  //....
}