4

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.

Morgan Bengtsson
  • 1,341
  • 1
  • 14
  • 23

4 Answers4

10

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()) { }
jhoffman0x
  • 795
  • 5
  • 9
4

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.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • 1
    +1 but, what if he wants to iterate over the mesh indices too? This needs a bit of more effort – Manu343726 Aug 03 '14 at 14:39
  • As @Manu343726 said, I want to iterate over the indices in the same way. I will clarify that in the question. – Morgan Bengtsson Aug 03 '14 at 14:41
  • @MorganBengtsson: 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. – Nawaz Aug 03 '14 at 14:45
  • Or to write a zip iterator. I don't know why there is no zip iterator on the standard library – Manu343726 Aug 03 '14 at 14:48
1

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())
{
  //....
}
JarkkoL
  • 1,898
  • 11
  • 17
0

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.

galop1n
  • 8,573
  • 22
  • 36