Iterating over a QMap with for
Asked Answered
R

11

77

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?

Rosamondrosamund answered 15/12, 2011 at 9:31 Comment(0)
R
78

Qt 6.4 and later

Use QMap::asKeyValueRange as follows:

for (auto [key, value] : map.asKeyValueRange()) {
    qDebug() << key << value;
}

From Qt 5.10 to Qt 6.3

Use QMap::keyValueBegin and QMap::keyValueEnd as follows:

for (auto it = map.keyValueBegin(); it != map.keyValueEnd(); ++it) {
    qDebug() << it->first << it->second;
}

The QMap::asKeyValueRange function can be backported from Qt 6.4 with just a few lines of code, allowing to use key-value ranges in Qt 5.10 to Qt 6.3 as well (C++17 required):

for (auto [key, value] : asKeyValueRange(map)) {
    qDebug() << key << value;
}

From Qt 4 to Qt 5.9

Use QMap::begin and QMap::end as follows:

for (auto it = map.begin(); it != map.end(); ++it) {
    qDebug() << it.key() << it.value();
}

The loop can be wrapped into a range with some lines of additional code, allowing to use range-based for loops in Qt 4 to Qt 5.9 as well (C++11 required):

for (auto it : toRange(map)) {
    qDebug() << it.key() << it.value();
}

Other options

(This is the original answer.)

Use QMap::toStdMap to convert the map into a std::map and then iterate over that. Note that the performance will be poor, since this will copy the entire map.

for (auto it : map.toStdMap()) {
    qDebug() << it.first << it.second;
}

Iterate over the QMap::keys and look up each associated QMap::value. Note that the performance will be extremely poor, since this will copy all keys and then separately search for each value.

for (auto key : map.keys()) {
    qDebug() << key << map.value(key);
}
Riboflavin answered 16/12, 2011 at 2:18 Comment(7)
toStdMap() and keys() methods will copy entire contents of map or it's keys what is quite resourceful. I wonder why it is chosen as best answer and got so many votesForetopsail
@FPGAwarrior on the contrary, it seems that toStdMap() is linear in the size of the QMap, so it should make a difference.Lynellelynett
Also prefer the now available for(auto e : qAsConst(extensions.keys())), as this will avoid a deep copy of the container.Smug
@FPGAwarrior Who says QMap stores the keys in a QList internally? I bet this list is constructed from the map's internal structure when requested.Hindgut
@Hindgut My mistake. I was wrong about the implementation. The internal representation of QMap is a black-red tree called QMapNodeBase, and keys() method really does iterate in linear time. I'll remove my previous wrong comment.Paynim
@AdrienLeravat Update: qAsConst is being replaced with standard std::as_const these days.Peeler
What about a const QMap, in this case keys() will already be const? So the same as qAsConst(extensions.keys())? And then I can write for(auto e : extensions.keys())?Dichromatic
C
60

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.

Corney answered 16/12, 2011 at 2:51 Comment(0)
G
36

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

Godhead answered 5/7, 2015 at 1:34 Comment(5)
this is the best answer and deserves to be selected. everybody can test easily on their system that you are telling the truth. I got very similar values to yours, I wonder why stl iterator is almost twice as fast. Qt documentation states misleadingly "[Java style iterators] They are more convenient to use than the STL-style iterators, at the price of being slightly less efficient.". 100% is more than slightly imoMarceau
Thanks you for your kind commentary =)Godhead
@Fezvez i have added const iterators to your code, surprisingly the run time of the const iterators increased vs non cost iterators which calls detach for every iterator access gist.github.com/ejahandar/3b5db3a7f3d1e746103671ef003d4fe5Hidebound
That's indeed very surprising and I have no idea why it would be soGodhead
Best answer, however I prefer the following syntax: for (auto it = map.constBegin(), end = map.constEnd(); it != end; ++it)Ectoparasite
P
22

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());
}
Polk answered 15/12, 2011 at 9:57 Comment(0)
D
13

In "old" C++, using Qt, you would do it like this:

QMap< QString, whatever > extensions;
//...
foreach( QString key, extensions.keys() )
{
    fout << key << "," << extensions.value( key ) << '\n';
}

I don't have a C++11 compiler here but maybe the following will work:

for( auto key: extensions.keys() )
{
    fout << key << "," << extensions.value( key ) << '\n';
}

You can also use iterators instead, check out hmuelners link if you prefer using them

Duiker answered 15/12, 2011 at 11:55 Comment(5)
Thank you for the reference to doing this in "old" C++. Some of us are stuck in the kennel with the old dawgs ;-)Slosberg
I doubt this is the most efficient "old" way of doing it. More likely: for(QMap<QString, whatever>::iterator it = map.begin(); it != map.end(); it++) {... }Incommodious
Your way is probably slightly more efficient, though mine is both easier to write and easier to read. Also, in larger programs, optimizing file, UI and database operations is usually a lot more efficient than optimizing for loops..Duiker
'foreach' is not old C++ but old QT.Totalizer
@CemPolat true, but range-based for loops were not possible in older C++ standardsDuiker
L
4

Since Qt 5.10 you can use a simple wrapper class to use a range based for loop, but still be able to access both the key and value of the map entries.

Put the following code somewhere at the top of your source file or in a header that you include:

template<class K,class V>
struct QMapWrapper {
    const QMap<K,V> map;
    QMapWrapper(const QMap<K,V>& map) : map(map) {}
    auto begin() { return map.keyValueBegin(); }
    auto end()   { return map.keyValueEnd();   }
};

To iterate over all entries you can simply write:

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

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

The type of e will be std::pair<const QString&, const QString&> as is partially specified in the QKeyValueIterator documentation.

The member variable map is an implicitly shared copy of the map, to avoid a segmentation fault in case this is used with temporary values. Hence as long as you do not modify the map within the loop, this only has a small constant overhead.


The above example uses class template argument deduction, which was introduced in C++17. If you're using an older standard, the template parameters for QMapWrapper must be specified when calling the constructor. In this case a factory method might be useful:

template<class K,class V>
QMapWrapper<K,V> wrapQMap(const QMap<K,V>& map) {
    return QMapWrapper<K,V>(map);
}
    

This is used as:

for(auto e : wrapQMap(extensions))
{
  fout << e.first << "," << e.second << '\n';
}
Lonnielonny answered 20/2, 2020 at 23:18 Comment(2)
This is nice as soon as the map is not a temporary value, in which case it generates a bad segfaultHuan
@kiruahxh good point. I changed the member variable into an implicitly shared copy to avoid this issue.Lonnielonny
V
4

Ivan Čukić of KDAB has a blog post that explains how to iterate over a QMap with C++17 structured bindings without copying the container:

template <typename T>
class asKeyValueRange
{
public:
    asKeyValueRange(T& data) : m_data{data} {}

    auto begin() { return m_data.keyValueBegin(); }

    auto end() { return m_data.keyValueEnd(); }

private:
    T& m_data;
};

...

QMap<QString, QString> extensions;

for (auto&& [key, value]: asKeyValueRange(extensions))
{
    fout << key << ',' << value << '\n';
} 
Voluble answered 24/11, 2020 at 11:42 Comment(1)
This is nice as soon as the map is not a temporary value, in which case it generates a bad segfaultHuan
A
4

Since Qt 6.4 you can use the method asKeyValueRange() as follows:

for(auto pair : extensions.asKeyValueRange()) {
    pair.first;    // key access
    pair.second;   // value access
}

This even works for the fancy structured bindings.

for(auto& [key, value] : extensions.asKeyValueRange()) {
    fout << key << "," << value << '\n';
}
Airdrome answered 11/7, 2023 at 11:23 Comment(0)
O
2

Another convenient method, from the QMap Docs. It allows explicit access to key and value (Java-Style iterator):

QMap<QString, QString> extensions;
// ... fill extensions
QMapIterator<QString, QString> i(extensions);
while (i.hasNext()) {
    i.next();
    qDebug() << i.key() << ": " << i.value();
}

In case you want to be able to overwrite, use QMutableMapIterator instead.

There's another convenient Qt method, if you're only interested in reading the values, without the keys (using Qts foreach and c++11):

QMap<QString, QString> extensions;
// ... fill extensions
foreach (const auto& value, extensions)
{
    // to stuff with value
}
Oversubtlety answered 15/3, 2017 at 9:24 Comment(1)
we also need keys!Elmiraelmo
Y
2

Since Qt 5.10, you can wrap QMap::keyValueBegin and QMap::keyValueEnd into a range and then use a range-based for loop to iterate that range. This works without copying the map data and supports both const and rvalue objects. C++17 compiler needed.

template<typename T> class KeyValueRange {
private:
    T iterable; // This is either a reference or a moved-in value. The map data isn't copied.
public:
    KeyValueRange(T &iterable) : iterable(iterable) { }
    KeyValueRange(std::remove_reference_t<T> &&iterable) noexcept : iterable(std::move(iterable)) { }
    auto begin() const { return iterable.keyValueBegin(); }
    auto end() const { return iterable.keyValueEnd(); }
};

template <typename T> auto asKeyValueRange(T &iterable) { return KeyValueRange<T &>(iterable); }
template <typename T> auto asKeyValueRange(const T &iterable) { return KeyValueRange<const T &>(iterable); }
template <typename T> auto asKeyValueRange(T &&iterable) noexcept { return KeyValueRange<T>(std::move(iterable)); }

Use it like this:

for (auto [key, value] : asKeyValueRange(map)) {
    qDebug() << key << value;
}

Note: If you only need read access to the value while iterating a non-const map, then you should use asKeyValueRange(std::as_const(map)) instead. This will make sure that the iteration doesn't trigger an unneeded copy-on-write operation.

Note: This exact code actually also works for QHash, QMultiHash and QMultiMap as well.

Yeh answered 14/2 at 12:39 Comment(0)
S
1

I used something like this, to achieve my own result. Just in case someone needed the keys and values separately.

{
   QMap<int,string> map; 

   map.insert(1,"One");
   map.insert(2,"Two");
   map.insert(3,"Three");
   map.insert(4,"Four");   

   fout<<"Values in QMap 'map' are:"<<endl;
   foreach(string str,map)
   {
     cout<<str<<endl;
   };


   fout<<"Keys in QMap 'map' are:"<<endl;
   foreach(int key,map.keys())
   {
     cout<<key<<endl;
   }; 
}  
Spreader answered 25/8, 2016 at 22:13 Comment(1)
The thing is that you want to retrieve the value of a certain key within the for(each) loop...Pierrette

© 2022 - 2024 — McMap. All rights reserved.