From 0deff80eabd577f3514d255e32f74502c914dfe1 Mon Sep 17 00:00:00 2001 From: Giuseppe D'Angelo Date: Tue, 1 Mar 2022 04:24:38 +0100 Subject: Associative containers: add a way to obtain a key/value range Our associative containers' iterator's value_type isn't a destructurable type (yielding key/value). This means that something like for (auto [k, v] : map) doesn't even compile -- one can only "directly" iterate on the values. For quite some time we've had QKeyValueIterator to allow key/value iteration, but then one had to resort to a "traditional" for loop: for (auto i = map.keyValueBegin(), e = keyValueEnd(); i!=e; ++i) This can be easily packaged in an adaptor class, which is what this commmit does, thereby offering a C++17-compatible way to obtain key/value iteration over associative containers. Something possibly peculiar is the fact that the range so obtained is a range of pairs of references -- not a range of references to pairs. But that's easily explained by the fact that we have no pairs to build references to; hence, for (auto &[k, v] : map.asKeyValueRange()) doesn't compile (lvalue reference doesn't bind to prvalue pair). Instead, both of these compile: for (auto [k, v] : map.asKeyValueRange()) for (auto &&[k, v] : map.asKeyValueRange()) and in *both* cases one gets references to the keys/values in the map. If the map is non-const, the reference to the value is mutable. Last but not least, implement pinning for rvalue containers. [ChangeLog][QtCore][QMap] Added asKeyValueRange(). [ChangeLog][QtCore][QMultiMap] Added asKeyValueRange(). [ChangeLog][QtCore][QHash] Added asKeyValueRange(). [ChangeLog][QtCore][QMultiHash] Added asKeyValueRange(). Task-number: QTBUG-4615 Change-Id: Ic8506bff38b2f753494b21ab76f52e05c06ffc8b Reviewed-by: Edward Welbourne --- .../doc/snippets/code/src_corelib_tools_qhash.cpp | 26 ++++ .../doc/snippets/code/src_corelib_tools_qmap.cpp | 13 ++ .../snippets/code/src_corelib_tools_qmultimap.cpp | 13 ++ src/corelib/tools/qhash.cpp | 37 ++++++ src/corelib/tools/qhash.h | 8 ++ src/corelib/tools/qiterator.h | 41 +++++++ src/corelib/tools/qmap.h | 8 ++ src/corelib/tools/qmap.qdoc | 19 +++ src/corelib/tools/qmultimap.qdoc | 19 +++ .../tst_containerapisymmetry.cpp | 131 +++++++++++++++++++++ 10 files changed, 315 insertions(+) diff --git a/src/corelib/doc/snippets/code/src_corelib_tools_qhash.cpp b/src/corelib/doc/snippets/code/src_corelib_tools_qhash.cpp index ee9f88fc65..844c5f1b7c 100644 --- a/src/corelib/doc/snippets/code/src_corelib_tools_qhash.cpp +++ b/src/corelib/doc/snippets/code/src_corelib_tools_qhash.cpp @@ -360,3 +360,29 @@ template <> struct hash }; } //! [33] + +//! [34] +QHash hash; +hash.insert("January", 1); +hash.insert("February", 2); +// ... +hash.insert("December", 12); + +for (auto [key, value] : hash.asKeyValueRange()) { + cout << key << ": " << value << Qt::endl; + --value; // convert to JS month indexing +} +//! [34] + +//! [35] +QMultiHash hash; +hash.insert("January", 1); +hash.insert("February", 2); +// ... +hash.insert("December", 12); + +for (auto [key, value] : hash.asKeyValueRange()) { + cout << key << ": " << value << Qt::endl; + --value; // convert to JS month indexing +} +//! [35] diff --git a/src/corelib/doc/snippets/code/src_corelib_tools_qmap.cpp b/src/corelib/doc/snippets/code/src_corelib_tools_qmap.cpp index 145cd10d84..a0e280e759 100644 --- a/src/corelib/doc/snippets/code/src_corelib_tools_qmap.cpp +++ b/src/corelib/doc/snippets/code/src_corelib_tools_qmap.cpp @@ -340,3 +340,16 @@ qDeleteAll(map2.keys()); int numPrimes = std::count_if(map.keyBegin(), map.keyEnd(), isPrimeNumber); qDeleteAll(map2.keyBegin(), map2.keyEnd()); //! [keyiterator2] + +//! [28] +QMap map; +map.insert("January", 1); +map.insert("February", 2); +// ... +map.insert("December", 12); + +for (auto [key, value] : map.asKeyValueRange()) { + cout << key << ": " << value << Qt::endl; + --value; // convert to JS month indexing +} +//! [28] diff --git a/src/corelib/doc/snippets/code/src_corelib_tools_qmultimap.cpp b/src/corelib/doc/snippets/code/src_corelib_tools_qmultimap.cpp index 9c2a01834b..bd188c3fb4 100644 --- a/src/corelib/doc/snippets/code/src_corelib_tools_qmultimap.cpp +++ b/src/corelib/doc/snippets/code/src_corelib_tools_qmultimap.cpp @@ -322,3 +322,16 @@ qDeleteAll(multimap2.keys()); int numPrimes = std::count_if(multimap.keyBegin(), multimap.keyEnd(), isPrimeNumber); qDeleteAll(multimap2.keyBegin(), multimap2.keyEnd()); //! [keyiterator2] + +//! [26] +QMultiMap map; +map.insert("January", 1); +map.insert("February", 2); +// ... +map.insert("December", 12); + +for (auto [key, value] : map.asKeyValueRange()) { + cout << key << ": " << value << Qt::endl; + --value; // convert to JS month indexing +} +//! [26] diff --git a/src/corelib/tools/qhash.cpp b/src/corelib/tools/qhash.cpp index e3106745da..b5aaaeee5b 100644 --- a/src/corelib/tools/qhash.cpp +++ b/src/corelib/tools/qhash.cpp @@ -2270,6 +2270,25 @@ size_t qHash(long double key, size_t seed) noexcept \sa constKeyValueBegin() */ +/*! \fn template auto QHash::asKeyValueRange() & + \fn template auto QHash::asKeyValueRange() const & + \fn template auto QHash::asKeyValueRange() && + \fn template auto QHash::asKeyValueRange() const && + \since 6.4 + + Returns a range object that allows iteration over this hash as + key/value pairs. For instance, this range object can be used in a + range-based for loop, in combination with a structured binding declaration: + + \snippet code/src_corelib_tools_qhash.cpp 34 + + Note that both the key and the value obtained this way are + references to the ones in the hash. Specifically, mutating the value + will modify the hash itself. + + \sa QKeyValueIterator +*/ + /*! \fn template QHash::iterator QHash::erase(const_iterator pos) \since 5.7 @@ -3411,6 +3430,24 @@ size_t qHash(long double key, size_t seed) noexcept \sa constKeyValueBegin() */ +/*! \fn template auto QMultiHash::asKeyValueRange() & + \fn template auto QMultiHash::asKeyValueRange() const & + \fn template auto QMultiHash::asKeyValueRange() && + \fn template auto QMultiHash::asKeyValueRange() const && + \since 6.4 + + Returns a range object that allows iteration over this hash as + key/value pairs. For instance, this range object can be used in a + range-based for loop, in combination with a structured binding declaration: + + \snippet code/src_corelib_tools_qhash.cpp 35 + + Note that both the key and the value obtained this way are + references to the ones in the hash. Specifically, mutating the value + will modify the hash itself. + + \sa QKeyValueIterator +*/ /*! \class QMultiHash::iterator \inmodule QtCore diff --git a/src/corelib/tools/qhash.h b/src/corelib/tools/qhash.h index 0eb91e7604..73a0225106 100644 --- a/src/corelib/tools/qhash.h +++ b/src/corelib/tools/qhash.h @@ -1228,6 +1228,10 @@ public: inline const_key_value_iterator constKeyValueBegin() const noexcept { return const_key_value_iterator(begin()); } inline const_key_value_iterator keyValueEnd() const noexcept { return const_key_value_iterator(end()); } inline const_key_value_iterator constKeyValueEnd() const noexcept { return const_key_value_iterator(end()); } + auto asKeyValueRange() & { return QtPrivate::QKeyValueRange(*this); } + auto asKeyValueRange() const & { return QtPrivate::QKeyValueRange(*this); } + auto asKeyValueRange() && { return QtPrivate::QKeyValueRange(std::move(*this)); } + auto asKeyValueRange() const && { return QtPrivate::QKeyValueRange(std::move(*this)); } iterator erase(const_iterator it) { @@ -1843,6 +1847,10 @@ public: inline const_key_value_iterator constKeyValueBegin() const noexcept { return const_key_value_iterator(begin()); } inline const_key_value_iterator keyValueEnd() const noexcept { return const_key_value_iterator(end()); } inline const_key_value_iterator constKeyValueEnd() const noexcept { return const_key_value_iterator(end()); } + auto asKeyValueRange() & { return QtPrivate::QKeyValueRange(*this); } + auto asKeyValueRange() const & { return QtPrivate::QKeyValueRange(*this); } + auto asKeyValueRange() && { return QtPrivate::QKeyValueRange(std::move(*this)); } + auto asKeyValueRange() const && { return QtPrivate::QKeyValueRange(std::move(*this)); } iterator detach(const_iterator it) { diff --git a/src/corelib/tools/qiterator.h b/src/corelib/tools/qiterator.h index 8922f34758..559023d493 100644 --- a/src/corelib/tools/qiterator.h +++ b/src/corelib/tools/qiterator.h @@ -301,6 +301,47 @@ private: Iterator i; }; +namespace QtPrivate { + +template +class QKeyValueRangeStorage +{ +protected: + Map m_map; +public: + explicit QKeyValueRangeStorage(const Map &map) : m_map(map) {} + explicit QKeyValueRangeStorage(Map &&map) : m_map(std::move(map)) {} +}; + +template +class QKeyValueRangeStorage +{ +protected: + Map &m_map; +public: + explicit QKeyValueRangeStorage(Map &map) : m_map(map) {} +}; + +template +class QKeyValueRange : public QKeyValueRangeStorage +{ +public: + using QKeyValueRangeStorage::QKeyValueRangeStorage; + auto begin() { return this->m_map.keyValueBegin(); } + auto begin() const { return this->m_map.keyValueBegin(); } + auto end() { return this->m_map.keyValueEnd(); } + auto end() const { return this->m_map.keyValueEnd(); } +}; + +template +QKeyValueRange(Map &) -> QKeyValueRange; + +template , bool> = false> +QKeyValueRange(Map &&) -> QKeyValueRange>; + +} // namespace QtPrivate + + QT_END_NAMESPACE #endif // QITERATOR_H diff --git a/src/corelib/tools/qmap.h b/src/corelib/tools/qmap.h index 5daf24189b..672d01791a 100644 --- a/src/corelib/tools/qmap.h +++ b/src/corelib/tools/qmap.h @@ -646,6 +646,10 @@ public: const_key_value_iterator constKeyValueBegin() const { return const_key_value_iterator(begin()); } const_key_value_iterator keyValueEnd() const { return const_key_value_iterator(end()); } const_key_value_iterator constKeyValueEnd() const { return const_key_value_iterator(end()); } + auto asKeyValueRange() & { return QtPrivate::QKeyValueRange(*this); } + auto asKeyValueRange() const & { return QtPrivate::QKeyValueRange(*this); } + auto asKeyValueRange() && { return QtPrivate::QKeyValueRange(std::move(*this)); } + auto asKeyValueRange() const && { return QtPrivate::QKeyValueRange(std::move(*this)); } iterator erase(const_iterator it) { @@ -1341,6 +1345,10 @@ public: const_key_value_iterator constKeyValueBegin() const { return const_key_value_iterator(begin()); } const_key_value_iterator keyValueEnd() const { return const_key_value_iterator(end()); } const_key_value_iterator constKeyValueEnd() const { return const_key_value_iterator(end()); } + auto asKeyValueRange() & { return QtPrivate::QKeyValueRange(*this); } + auto asKeyValueRange() const & { return QtPrivate::QKeyValueRange(*this); } + auto asKeyValueRange() && { return QtPrivate::QKeyValueRange(std::move(*this)); } + auto asKeyValueRange() const && { return QtPrivate::QKeyValueRange(std::move(*this)); } iterator erase(const_iterator it) { diff --git a/src/corelib/tools/qmap.qdoc b/src/corelib/tools/qmap.qdoc index 55c2bcb5d9..eeb6b0f939 100644 --- a/src/corelib/tools/qmap.qdoc +++ b/src/corelib/tools/qmap.qdoc @@ -663,6 +663,25 @@ \sa constKeyValueBegin() */ +/*! \fn template auto QMap::asKeyValueRange() & + \fn template auto QMap::asKeyValueRange() const & + \fn template auto QMap::asKeyValueRange() && + \fn template auto QMap::asKeyValueRange() const && + \since 6.4 + + Returns a range object that allows iteration over this map as + key/value pairs. For instance, this range object can be used in a + range-based for loop, in combination with a structured binding declaration: + + \snippet code/src_corelib_tools_qmap.cpp 28 + + Note that both the key and the value obtained this way are + references to the ones in the map. Specifically, mutating the value + will modify the map itself. + + \sa QKeyValueIterator +*/ + /*! \fn template QMap::iterator QMap::erase(const_iterator pos) Removes the (key, value) pair pointed to by the iterator \a pos diff --git a/src/corelib/tools/qmultimap.qdoc b/src/corelib/tools/qmultimap.qdoc index 6bfc1515e6..fb61b540bf 100644 --- a/src/corelib/tools/qmultimap.qdoc +++ b/src/corelib/tools/qmultimap.qdoc @@ -693,6 +693,25 @@ \sa constKeyValueBegin() */ +/*! \fn template auto QMultiMap::asKeyValueRange() & + \fn template auto QMultiMap::asKeyValueRange() const & + \fn template auto QMultiMap::asKeyValueRange() && + \fn template auto QMultiMap::asKeyValueRange() const && + \since 6.4 + + Returns a range object that allows iteration over this multi map as + key/value pairs. For instance, this range object can be used in a + range-based for loop, in combination with a structured binding declaration: + + \snippet code/src_corelib_tools_qmultimap.cpp 26 + + Note that both the key and the value obtained this way are + references to the ones in the multi map. Specifically, mutating the value + will modify the map itself. + + \sa QKeyValueIterator +*/ + /*! \fn template QMultiMap::iterator QMultiMap::erase(const_iterator pos) Removes the (key, value) pair pointed to by the iterator \a pos diff --git a/tests/auto/corelib/tools/containerapisymmetry/tst_containerapisymmetry.cpp b/tests/auto/corelib/tools/containerapisymmetry/tst_containerapisymmetry.cpp index 5e2d7eab04..33994dfccc 100644 --- a/tests/auto/corelib/tools/containerapisymmetry/tst_containerapisymmetry.cpp +++ b/tests/auto/corelib/tools/containerapisymmetry/tst_containerapisymmetry.cpp @@ -391,6 +391,16 @@ private Q_SLOTS: void erase_if_QMultiMap() {erase_if_associative_impl>(); } void erase_if_QHash() { erase_if_associative_impl>(); } void erase_if_QMultiHash() { erase_if_associative_impl>(); } + +private: + template + void keyValueRange_impl() const; + +private Q_SLOTS: + void keyValueRange_QMap() { keyValueRange_impl>(); } + void keyValueRange_QMultiMap() { keyValueRange_impl>(); } + void keyValueRange_QHash() { keyValueRange_impl>(); } + void keyValueRange_QMultiHash() { keyValueRange_impl>(); } }; void tst_ContainerApiSymmetry::init() @@ -851,5 +861,126 @@ void tst_ContainerApiSymmetry::erase_if_associative_impl() const QCOMPARE(c.size(), S(0)); } +template +void tst_ContainerApiSymmetry::keyValueRange_impl() const +{ + constexpr int COUNT = 20; + + using K = typename Container::key_type; + using V = typename Container::mapped_type; + QVector keys; + keys.reserve(COUNT); + QVector values; + values.reserve(COUNT); + + auto c = makeAssociative(COUNT); + auto returnC = [&](){ return c; }; + + const auto verify = [](QVector v, int count, int offset = 0) -> bool { + if (v.size() != count) + return false; + std::sort(v.begin(), v.end()); + for (int i = 0; i < count; ++i) { + // vector is indexed from 0, but makeAssociative starts from 1 + if (v[i] != i + 1 + offset) + return false; + } + return true; + }; + + // Check that the range has the right size + auto range = c.asKeyValueRange(); + QCOMPARE(std::distance(range.begin(), range.end()), COUNT); + + auto constRange = std::as_const(c).asKeyValueRange(); + QCOMPARE(std::distance(constRange.begin(), constRange.end()), COUNT); + + auto rvalueRange = returnC().asKeyValueRange(); + QCOMPARE(std::distance(rvalueRange.begin(), rvalueRange.end()), COUNT); + + // auto, mutating + keys.clear(); values.clear(); + for (auto [key, value] : c.asKeyValueRange()) { + keys << key; + values << value; + QCOMPARE(key, value); + QCOMPARE(c.value(key), value); + ++value; + QCOMPARE(key, value - 1); + QCOMPARE(c.value(key), value); + } + QVERIFY(verify(keys, COUNT)); + QVERIFY(verify(values, COUNT)); + + // auto, non-mutating + keys.clear(); values.clear(); + for (auto [key, value] : c.asKeyValueRange()) { + keys << key; + values << value; + QCOMPARE(key, value - 1); + QCOMPARE(c.value(key), value); + } + QVERIFY(verify(keys, COUNT)); + QVERIFY(verify(values, COUNT, 1)); + + // auto &&, mutating + keys.clear(); values.clear(); + for (auto &&[key, value] : c.asKeyValueRange()) { + keys << key; + values << value; + QCOMPARE(key, value - 1); + QCOMPARE(c.value(key), value); + ++value; + QCOMPARE(key, value - 2); + QCOMPARE(c.value(key), value); + } + QVERIFY(verify(keys, COUNT)); + QVERIFY(verify(values, COUNT, 1)); + + // auto, non-mutating (const map) + keys.clear(); values.clear(); + for (auto [key, value] : std::as_const(c).asKeyValueRange()) { + keys << key; + values << value; + QCOMPARE(key, value - 2); + QCOMPARE(c.value(key), value); + } + QVERIFY(verify(keys, COUNT)); + QVERIFY(verify(values, COUNT, 2)); + + // auto &&, non-mutating (const map) + keys.clear(); values.clear(); + for (auto &&[key, value] : std::as_const(c).asKeyValueRange()) { + keys << key; + values << value; + QCOMPARE(key, value - 2); + QCOMPARE(c.value(key), value); + } + QVERIFY(verify(keys, COUNT)); + QVERIFY(verify(values, COUNT, 2)); + + // auto, non-mutating (rvalue map) + keys.clear(); values.clear(); + for (auto [key, value] : returnC().asKeyValueRange()) { + keys << key; + values << value; + QCOMPARE(key, value - 2); + QCOMPARE(c.value(key), value); + } + QVERIFY(verify(keys, COUNT)); + QVERIFY(verify(values, COUNT, 2)); + + // auto &&, non-mutating (rvalue map) + keys.clear(); values.clear(); + for (auto &&[key, value] : returnC().asKeyValueRange()) { + keys << key; + values << value; + QCOMPARE(key, value - 2); + QCOMPARE(c.value(key), value); + } + QVERIFY(verify(keys, COUNT)); + QVERIFY(verify(values, COUNT, 2)); +} + QTEST_APPLESS_MAIN(tst_ContainerApiSymmetry) #include "tst_containerapisymmetry.moc" -- cgit v1.2.3