1

I am trying to serialize an object of std::unordered_map<QString, QString> and place in QSettings, which I hope to read back later on. Reading the docs, I need to do 2 things: declare it as a meta type and register stream operators, which I did. Here is a complete example reproducing the error:

#include <QCoreApplication>

#include <QDataStream>
#include <QDebug>
#include <QSettings>
#include <QString>
#include <unordered_map>

namespace std {
template <>
struct hash<QString> {
  std::size_t operator()(const QString &s) const noexcept {
    return static_cast<std::size_t>(qHash(s));
  }
};
}  // namespace std

using StrMap = std::unordered_map<QString, QString>;

inline QDataStream &operator<<(QDataStream &out, const StrMap &map) {
  for (auto &&elm : map) {
    out << elm.first << elm.second;
  }
  return out;
}

inline QDataStream &operator>>(QDataStream &in, StrMap &map) {
  QString key;
  QString val;
  while (not in.atEnd()) {
    in >> key >> val;
    map.insert_or_assign(std::move(key), std::move(val));
  }
  return in;
}

Q_DECLARE_METATYPE(StrMap)

int main(int argc, char *argv[]) {
  QCoreApplication a(argc, argv);
  qRegisterMetaTypeStreamOperators<StrMap>("StrMap");

  {
    StrMap map{
        {"1", "1"},
        {"2", "2"},
        {"3", "3"},
        {"4", "4"},
    };

    QSettings settings{"TestOrg", "TestApp"};
    QVariant var;
    var.setValue(map);
    settings.setValue("StrMap", std::move(var));
  }

  QSettings settings{"TestOrg", "TestApp"};
  StrMap map{settings.value("StrMap").value<StrMap>()};
  for (auto &&pair : map) {
    qDebug() << pair.first << " - " << pair.second << '\n';
  }

  return a.exec();
}

Here when I read back the map object which I serialized, I get an additional element with empty key and empty value. Output:

""  -  ""

"1"  -  "1"

"2"  -  "2"

"4"  -  "4"

"3"  -  "3"

What am I doing wrong? I never placed that empty element. Some people are going to point me to "Why is iostream::eof inside a loop condition (i.e. while (!stream.eof())) considered wrong?" post, but the main differences here are, I know exactly how many elements I have placed there, so I won't read after the end, and there is apparently no way to convert QDataStream objects into bool implicitly, so I can't do while (in >> key >> val){...}.

Aykhan Hagverdili
  • 28,141
  • 6
  • 41
  • 93
  • 4
    Looks like the problem of [Why is iostream::eof inside a loop condition (i.e. `while (!stream.eof())`) considered wrong?](https://stackoverflow.com/questions/5605125/why-is-iostreameof-inside-a-loop-condition-i-e-while-stream-eof-cons), just translated to QT. – Yksisarvinen Aug 09 '19 at 11:48
  • @Yksisarvinen I did mention this post in the question. Can you be more precise on how the problem may still arise considering my note at the end? – Aykhan Hagverdili Aug 09 '19 at 11:49
  • My guess is that [`atEnd()`](https://doc.qt.io/qt-5/qdatastream.html#atEnd) doesn't return "true" until after you actually *passed* the end. That means you're reading once to many from the file. Use the "usual" `while (in >> key >> val) { ... }` pattern instead. – Some programmer dude Aug 09 '19 at 11:49
  • @Someprogrammerdude I can't do `while (in >> key >> val) { ... }` because `QDataStream ` objects do not convert to `bool`. I mentioned that at the end of the question – Aykhan Hagverdili Aug 09 '19 at 11:52
  • Then a `do { ... } while (...)` loop instead? – Some programmer dude Aug 09 '19 at 11:57
  • I'm afraid I never used QT, so I cannot provide a good answer, but if `QDataStream` doesn't support any way of checking stream state, you could just reorganize your loop? `in >> key >> val; while (not in.atEnd()) { map.insert_or_assign(std::move(key), std::move(val)); in >> key >> val; }` – Yksisarvinen Aug 09 '19 at 11:58
  • Or remove last key from map? – Yksisarvinen Aug 09 '19 at 12:05
  • @Yksisarvinen that too would solve the problem but the accepted answer seems to be the right way – Aykhan Hagverdili Aug 09 '19 at 13:04

1 Answers1

3

You need to test the stream after trying to read from it.

while ((in >> key >> val).status() == QDataStream::Ok) {
    map.insert_or_assign(std::move(key), std::move(val));
}
Caleth
  • 52,200
  • 2
  • 44
  • 75