Silmor . de
Site Links:
Impressum / Publisher

Range Based For and QDom

C++11 introduces range based for loops which make some typical loops much easier to write and read. What used to be written (thousands of times per project) like this:

QList<MyStruct> mylist=getListFromSomewhere();
for(int i=0;i<mylist.size();i++)
  doSomething(mylist[i]);

Can now be written much more concisely:

for(MyStruct elem:getListFromSomewhere())
  doSomething(elem);

Or in its optimized form:

for(const auto&elem:getListFromSomewhere())
  doSomething(elem);

This works with normal arrays as well as most Qt container classes (QList, QMap, ...) and all standard library containers. All that it needs is an iterator implementation for the container. Unfortunately not all containers have iterators, but it is quite easy to provide them.

For example Qt's DOM container QDomNodeList does not provide iterators. I'll use it as an example to show how to implement them.

To be compatible with range based for loops the container class itself must have a begin() and an end() method or a global begin(const Container&) and end(const Container&) functions. The latter can be applied retroactively. In both cases they return a kind of structure ("iterator") that fulfils the following properties:

In the simplest case these properties are fulfilled by pointers (can be compared, incremented and resolved), in more complex cases by classes that simulate this behavior. Let's create one for QDomNodeList:

class QDomNodeIterator
{
        int pos;
        const QDomNodeList&container;
public:
        //constructors and assignments
        QDomNodeIterator(const QDomNodeList&l,int p):pos(p),container(l){}
        QDomNodeIterator(const QDomNodeIterator&)=default;
        QDomNodeIterator(QDomNodeIterator&&)=default;
        QDomNodeIterator& operator=(const QDomNodeIterator&)=default;
        QDomNodeIterator& operator=(QDomNodeIterator&&)=default;
        
        //increment
        QDomNodeIterator& operator++(){pos++;return *this;}
        
        //comparison
        bool operator==(const QDomNodeIterator&o){return pos==o.pos && container==o.container;}
        bool operator!=(const QDomNodeIterator&o){return pos!=o.pos || container!=o.container;}
        
        //indirection
        QDomNode operator*(){return container.at(pos);}
};

//begin and end
inline QDomNodeIterator begin(const QDomNodeList&l){return QDomNodeIterator(l,0);}
inline QDomNodeIterator end(const QDomNodeList&l){return QDomNodeIterator(l,l.size());}

The iterator class is rather simple: it has a reference to the container that it enhances and remembers its position inside this container. This of course assumes that the container does not change during operation - which is true for QDomNodeList, which is read-only outside QtDom. Its first constructor is used to actually create it, the other constructors and assignment operators use the default implementation.

The increment operator looks pretty standard as well: it just increments the position. Same for the comparison operators: they compare the position and check whether they point to the same container. If the latter is not quite possible for your container and you are sure that the iterator will not be abused you can also just compare the position and it will still work for range based for loops (not necessarily for other scenarios). Finally the indirection operator executes the actual retrieval of an element from the container.

The begin functions then only needs to create an iterator for the first (always 0) element and the end function one that points behind the last element (size()) of the container.

With that we can simplify any use of QDomNodeList:

QDomDocument mydoc;
//...
for(auto node:mydoc.elementsByTagName("HelloKitty"))
        qDebug()<<"Found a kitten at line"<<node.lineNumber()
          <<"column"<<node.columnNumber();

Voilá! We just enhanced a non-standard-conforming container class with the ability to iterate over it with range based for.


Webmaster: webmaster AT silmor DOT de