Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Iterating over a QMap with for

Tags:

c++

c++11

qt

qmap

I've a QMap object and I am trying to write its content to a file.

QMap<QString, QString> extensions;
//.. 

for(auto e : extensions)
{
  fout << e.first << "," << e.second << '\n';
}  

Why do I get: error: 'class QString' has no member named 'first' nor 'second'

Is e not of type QPair?

like image 537
user336635 Avatar asked Dec 15 '11 09:12

user336635


4 Answers

If you want the STL style with first and second, do this:

for(auto e : extensions.toStdMap()) {   fout << e.first << "," << e.second << '\n'; } 

If you want to use what Qt offers, do this:

for(auto e : extensions.keys()) {   fout << e << "," << extensions.value(e) << '\n'; } 
like image 167
Arlen Avatar answered Sep 17 '22 20:09

Arlen


C++11 range-based-for uses the type of the dereferenced iterator as the automatically deduced "cursor" type. Here, it is the type of the expression *map.begin().
And since QMap::iterator::operator*() returns a reference to the value (of type QString &), the key isn't accessible using that method.

You should use one of the iterator methods described in the documentation but you should avoid using

  • keys() because it involves creating a list of keys and then searching the value for each key, or,
  • toStdMap() because it copies all the map elements to another one,

and that wouldn't be very optimal.


You could also use a wrapper to get QMap::iterator as the auto type:
template<class Map> struct RangeWrapper {     typedef typename Map::iterator MapIterator;     Map &map;      RangeWrapper(Map & map_) : map(map_) {}      struct iterator {         MapIterator mapIterator;         iterator(const MapIterator &mapIterator_): mapIterator(mapIterator_) {}         MapIterator operator*() {             return mapIterator;         }         iterator & operator++() {             ++mapIterator;             return *this;         }         bool operator!=(const iterator & other) {             return this->mapIterator != other.mapIterator;         }     };     iterator begin() {         return map.begin();     }     iterator end() {         return map.end();     } };  // Function to be able to use automatic template type deduction template<class Map> RangeWrapper<Map> toRange(Map & map) {     return RangeWrapper<Map>(map); }  // Usage code QMap<QString, QString> extensions; ... for(auto e : toRange(extensions)) {     fout << e.key() << "," << e.value() << '\n'; } 

There is another wrapper here.

like image 24
alexisdm Avatar answered Sep 16 '22 20:09

alexisdm


For people interested in optimizations, I have tried several approaches, did some micro benchmarks, and I can conclude that STL style approach is significantly faster.

I have tried adding integers with these methods :

  • QMap::values()
  • Java style iterator (as advised in the documentation)
  • STL style iterator (as advised in the documentation too)

And I have compared it with summing integers of a QList/QVector

Results :

Reference vector :   244  ms
Reference list :     1239  ms

QMap::values() :     6504  ms
Java style iterator :    6199  ms
STL style iterator :     2343  ms

Code for those interested :

#include <QDateTime>
#include <QMap>
#include <QVector>
#include <QList>
#include <QDebug>

void testQMap(){
    QMap<int, int> map;
    QVector<int> vec;
    QList<int> list;

    int nbIterations = 100;
    int size = 1000000;
    volatile int sum = 0;

    for(int i = 0; i<size; ++i){
        int randomInt = qrand()%128;
        map[i] = randomInt;
        vec.append(randomInt);
        list.append(randomInt);
    }


    // Rererence vector/list
    qint64 start = QDateTime::currentMSecsSinceEpoch();
    for(int i = 0; i<nbIterations; ++i){
        sum = 0;
        for(int j : vec){
            sum += j;
        }
    }
    qint64 end = QDateTime::currentMSecsSinceEpoch();
    qDebug() << "Reference vector : \t" << (end-start) << " ms";

    qint64 startList = QDateTime::currentMSecsSinceEpoch();
    for(int i = 0; i<nbIterations; ++i){
        sum = 0;
        for(int j : list){
            sum += j;
        }
    }
    qint64 endList = QDateTime::currentMSecsSinceEpoch();
    qDebug() << "Reference list : \t" << (endList-startList) << " ms";

    // QMap::values()
    qint64 start0 = QDateTime::currentMSecsSinceEpoch();
    for(int i = 0; i<nbIterations; ++i){
        sum = 0;
        QList<int> values = map.values();
        for(int k : values){
            sum += k;
        }
    }
    qint64 end0 = QDateTime::currentMSecsSinceEpoch();
    qDebug() << "QMap::values() : \t" << (end0-start0) << " ms";


    // Java style iterator
    qint64 start1 = QDateTime::currentMSecsSinceEpoch();
    for(int i = 0; i<nbIterations; ++i){
        sum = 0;
        QMapIterator<int, int> it(map);
        while (it.hasNext()) {
            it.next();
            sum += it.value();
        }
    }
    qint64 end1 = QDateTime::currentMSecsSinceEpoch();
    qDebug() << "Java style iterator : \t" << (end1-start1) << " ms";


    // STL style iterator
    qint64 start2 = QDateTime::currentMSecsSinceEpoch();
    for(int i = 0; i<nbIterations; ++i){
        sum = 0;
        QMap<int, int>::const_iterator it = map.constBegin();
        auto end = map.constEnd();
        while (it != end) {
            sum += it.value();
            ++it;
        }
    }
    qint64 end2 = QDateTime::currentMSecsSinceEpoch();
    qDebug() << "STL style iterator : \t" << (end2-start2) << " ms";


    qint64 start3 = QDateTime::currentMSecsSinceEpoch();
    for(int i = 0; i<nbIterations; ++i){
        sum = 0;
        auto end = map.cend();
        for (auto it = map.cbegin(); it != end; ++it)
        {
            sum += it.value();
        }
    }
    qint64 end3 = QDateTime::currentMSecsSinceEpoch();

    qDebug() << "STL style iterator v2 : \t" << (end3-start3) << " ms";
}

Edit July 2017 : I ran this code again on my new laptop (Qt 5.9, i7-7560U) and got some interesting changes

Reference vector :   155  ms 
Reference list :     157  ms
QMap::values():      1874  ms 
Java style iterator: 1156  ms 
STL style iterator:  1143  ms

STL style and Java style have very similar performances in this benchmark

like image 29
B. Decoster Avatar answered Sep 16 '22 20:09

B. Decoster


QMap::iterator uses key() and value() - which can be found easily in the documentation for Qt 4.8 or the documentation for Qt-5.

Edit:

A range-based for loop generates codes similar to this (see CPP reference):

{
    for (auto __begin = extensions.begin(), __end = extensions.end();
            __begin != __end; ++__begin) {
        auto e = *__begin; // <--- this is QMap::iterator::operator*()
        fout << e.first << "," << e.second << '\n';
    }
} 

QMap::iterator::iterator*() is equivalent to QMap::iterator::value(), and does not give a pair.

The best way to write this is without range-based for loop:

auto end = extensions.cend();
for (auto it = extensions.cbegin(); it != end; ++it)
{
    std::cout << qPrintable(it.key()) << "," << qPrintable(it.value());
}
like image 42
hmuelner Avatar answered Sep 20 '22 20:09

hmuelner