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 <edward.welbourne@qt.io>
This commit is contained in:
Giuseppe D'Angelo 2022-03-01 04:24:38 +01:00
parent dd5fc20e90
commit 0deff80eab
10 changed files with 315 additions and 0 deletions

View File

@ -360,3 +360,29 @@ template <> struct hash<K>
};
}
//! [33]
//! [34]
QHash<QString, int> 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<QString, int> 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]

View File

@ -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<QString, int> 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]

View File

@ -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<QString, int> 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]

View File

@ -2270,6 +2270,25 @@ size_t qHash(long double key, size_t seed) noexcept
\sa constKeyValueBegin()
*/
/*! \fn template <class Key, class T> auto QHash<Key, T>::asKeyValueRange() &
\fn template <class Key, class T> auto QHash<Key, T>::asKeyValueRange() const &
\fn template <class Key, class T> auto QHash<Key, T>::asKeyValueRange() &&
\fn template <class Key, class T> auto QHash<Key, T>::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 <class Key, class T> QHash<Key, T>::iterator QHash<Key, T>::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 <class Key, class T> auto QMultiHash<Key, T>::asKeyValueRange() &
\fn template <class Key, class T> auto QMultiHash<Key, T>::asKeyValueRange() const &
\fn template <class Key, class T> auto QMultiHash<Key, T>::asKeyValueRange() &&
\fn template <class Key, class T> auto QMultiHash<Key, T>::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

View File

@ -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)
{

View File

@ -301,6 +301,47 @@ private:
Iterator i;
};
namespace QtPrivate {
template <typename Map>
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 <typename Map>
class QKeyValueRangeStorage<Map &>
{
protected:
Map &m_map;
public:
explicit QKeyValueRangeStorage(Map &map) : m_map(map) {}
};
template <typename Map>
class QKeyValueRange : public QKeyValueRangeStorage<Map>
{
public:
using QKeyValueRangeStorage<Map>::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 <typename Map>
QKeyValueRange(Map &) -> QKeyValueRange<Map &>;
template <typename Map, std::enable_if_t<!std::is_reference_v<Map>, bool> = false>
QKeyValueRange(Map &&) -> QKeyValueRange<std::remove_const_t<Map>>;
} // namespace QtPrivate
QT_END_NAMESPACE
#endif // QITERATOR_H

View File

@ -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)
{

View File

@ -663,6 +663,25 @@
\sa constKeyValueBegin()
*/
/*! \fn template <class Key, class T> auto QMap<Key, T>::asKeyValueRange() &
\fn template <class Key, class T> auto QMap<Key, T>::asKeyValueRange() const &
\fn template <class Key, class T> auto QMap<Key, T>::asKeyValueRange() &&
\fn template <class Key, class T> auto QMap<Key, T>::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 <class Key, class T> QMap<Key, T>::iterator QMap<Key, T>::erase(const_iterator pos)
Removes the (key, value) pair pointed to by the iterator \a pos

View File

@ -693,6 +693,25 @@
\sa constKeyValueBegin()
*/
/*! \fn template <class Key, class T> auto QMultiMap<Key, T>::asKeyValueRange() &
\fn template <class Key, class T> auto QMultiMap<Key, T>::asKeyValueRange() const &
\fn template <class Key, class T> auto QMultiMap<Key, T>::asKeyValueRange() &&
\fn template <class Key, class T> auto QMultiMap<Key, T>::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 <class Key, class T> QMultiMap<Key, T>::iterator QMultiMap<Key, T>::erase(const_iterator pos)
Removes the (key, value) pair pointed to by the iterator \a pos

View File

@ -391,6 +391,16 @@ private Q_SLOTS:
void erase_if_QMultiMap() {erase_if_associative_impl<QMultiMap<int, int>>(); }
void erase_if_QHash() { erase_if_associative_impl<QHash<int, int>>(); }
void erase_if_QMultiHash() { erase_if_associative_impl<QMultiHash<int, int>>(); }
private:
template <typename Container>
void keyValueRange_impl() const;
private Q_SLOTS:
void keyValueRange_QMap() { keyValueRange_impl<QMap<int, int>>(); }
void keyValueRange_QMultiMap() { keyValueRange_impl<QMultiMap<int, int>>(); }
void keyValueRange_QHash() { keyValueRange_impl<QHash<int, int>>(); }
void keyValueRange_QMultiHash() { keyValueRange_impl<QMultiHash<int, int>>(); }
};
void tst_ContainerApiSymmetry::init()
@ -851,5 +861,126 @@ void tst_ContainerApiSymmetry::erase_if_associative_impl() const
QCOMPARE(c.size(), S(0));
}
template <typename Container>
void tst_ContainerApiSymmetry::keyValueRange_impl() const
{
constexpr int COUNT = 20;
using K = typename Container::key_type;
using V = typename Container::mapped_type;
QVector<K> keys;
keys.reserve(COUNT);
QVector<V> values;
values.reserve(COUNT);
auto c = makeAssociative<Container>(COUNT);
auto returnC = [&](){ return c; };
const auto verify = [](QVector<K> 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"