Files
2026-05-07 16:41:01 +02:00

1579 lines
47 KiB
C++

// Copyright (C) 2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Giuseppe D'Angelo <giuseppe.dangelo@kdab.com>
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef QMAP_H
#define QMAP_H
#include <QtCore/qiterator.h>
#include <QtCore/qlist.h>
#include <QtCore/qrefcount.h>
#include <QtCore/qpair.h>
#include <QtCore/qshareddata.h>
#include <QtCore/qshareddata_impl.h>
#include <functional>
#include <initializer_list>
#include <map>
#include <algorithm>
QT_BEGIN_NAMESPACE
// common code shared between QMap and QMultimap
template <typename AMap>
class QMapData : public QSharedData
{
public:
using Map = AMap;
using Key = typename Map::key_type;
using T = typename Map::mapped_type;
using value_type = typename Map::value_type;
using size_type = typename Map::size_type;
using iterator = typename Map::iterator;
using const_iterator = typename Map::const_iterator;
static_assert(std::is_nothrow_destructible_v<Key>, "Types with throwing destructors are not supported in Qt containers.");
static_assert(std::is_nothrow_destructible_v<T>, "Types with throwing destructors are not supported in Qt containers.");
Map m;
QMapData() = default;
explicit QMapData(const Map &other)
: m(other)
{}
explicit QMapData(Map &&other)
: m(std::move(other))
{}
// used in remove(); copies from source all the values not matching key.
// returns how many were NOT copied (removed).
size_type copyIfNotEquivalentTo(const Map &source, const Key &key)
{
Q_ASSERT(m.empty());
size_type result = 0;
const auto &keyCompare = source.key_comp();
const auto filter = [&result, &key, &keyCompare](const auto &v)
{
if (!keyCompare(key, v.first) && !keyCompare(v.first, key)) {
// keys are equivalent (neither a<b nor b<a) => found it
++result;
return true;
}
return false;
};
std::remove_copy_if(source.cbegin(), source.cend(),
std::inserter(m, m.end()),
filter);
return result;
}
// used in key(T), count(Key, T), find(key, T), etc; returns a
// comparator object suitable for algorithms with std::(multi)map
// iterators.
static auto valueIsEqualTo(const T &value)
{
return [&value](const auto &v) { return v.second == value; };
}
Key key(const T &value, const Key &defaultKey) const
{
auto i = std::find_if(m.cbegin(),
m.cend(),
valueIsEqualTo(value));
if (i != m.cend())
return i->first;
return defaultKey;
}
QList<Key> keys() const
{
QList<Key> result;
result.reserve(m.size());
const auto extractKey = [](const auto &v) { return v.first; };
std::transform(m.cbegin(),
m.cend(),
std::back_inserter(result),
extractKey);
return result;
}
QList<Key> keys(const T &value) const
{
QList<Key> result;
result.reserve(m.size());
// no std::transform_if...
for (const auto &v : m) {
if (v.second == value)
result.append(v.first);
}
result.shrink_to_fit();
return result;
}
QList<T> values() const
{
QList<T> result;
result.reserve(m.size());
const auto extractValue = [](const auto &v) { return v.second; };
std::transform(m.cbegin(),
m.cend(),
std::back_inserter(result),
extractValue);
return result;
}
size_type count(const Key &key) const
{
return m.count(key);
}
// Used in erase. Allocates a new QMapData and copies, from this->m,
// the elements not in the [first, last) range. The return contains
// the new QMapData and an iterator in its map pointing at the first
// element after the erase.
struct EraseResult {
QMapData *data;
iterator it;
};
EraseResult erase(const_iterator first, const_iterator last) const
{
EraseResult result;
result.data = new QMapData;
result.it = result.data->m.end();
const auto newDataEnd = result.it;
auto i = m.begin();
const auto e = m.end();
// copy over all the elements before first
while (i != first) {
result.it = result.data->m.insert(newDataEnd, *i);
++i;
}
// skip until last
while (i != last)
++i;
// copy from last to the end
while (i != e) {
result.data->m.insert(newDataEnd, *i);
++i;
}
if (result.it != newDataEnd)
++result.it;
return result;
}
};
//
// QMap
//
template <class Key, class T>
class QMap
{
using Map = std::map<Key, T>;
using MapData = QMapData<Map>;
QtPrivate::QExplicitlySharedDataPointerV2<MapData> d;
friend class QMultiMap<Key, T>;
public:
using key_type = Key;
using mapped_type = T;
using difference_type = qptrdiff;
using size_type = qsizetype;
QMap() = default;
// implicitly generated special member functions are OK!
void swap(QMap<Key, T> &other) noexcept
{
d.swap(other.d);
}
QMap(std::initializer_list<std::pair<Key, T>> list)
{
for (auto &p : list)
insert(p.first, p.second);
}
explicit QMap(const std::map<Key, T> &other)
: d(other.empty() ? nullptr : new MapData(other))
{
}
explicit QMap(std::map<Key, T> &&other)
: d(other.empty() ? nullptr : new MapData(std::move(other)))
{
}
std::map<Key, T> toStdMap() const &
{
if (d)
return d->m;
return {};
}
std::map<Key, T> toStdMap() &&
{
if (d) {
if (d.isShared())
return d->m;
else
return std::move(d->m);
}
return {};
}
#ifndef Q_QDOC
template <typename AKey = Key, typename AT = T> friend
QTypeTraits::compare_eq_result_container<QMap, AKey, AT> operator==(const QMap &lhs, const QMap &rhs)
{
if (lhs.d == rhs.d)
return true;
if (!lhs.d)
return rhs == lhs;
Q_ASSERT(lhs.d);
return rhs.d ? (lhs.d->m == rhs.d->m) : lhs.d->m.empty();
}
template <typename AKey = Key, typename AT = T> friend
QTypeTraits::compare_eq_result_container<QMap, AKey, AT> operator!=(const QMap &lhs, const QMap &rhs)
{
return !(lhs == rhs);
}
// TODO: add the other comparison operators; std::map has them.
#else
friend bool operator==(const QMap &lhs, const QMap &rhs);
friend bool operator!=(const QMap &lhs, const QMap &rhs);
#endif // Q_QDOC
size_type size() const { return d ? size_type(d->m.size()) : size_type(0); }
bool isEmpty() const { return d ? d->m.empty() : true; }
void detach()
{
if (d)
d.detach();
else
d.reset(new MapData);
}
bool isDetached() const noexcept
{
return d ? !d.isShared() : false; // false makes little sense, but that's shared_null's behavior...
}
bool isSharedWith(const QMap<Key, T> &other) const noexcept
{
return d == other.d; // also this makes little sense?
}
void clear()
{
if (!d)
return;
if (!d.isShared())
d->m.clear();
else
d.reset();
}
size_type remove(const Key &key)
{
if (!d)
return 0;
if (!d.isShared())
return size_type(d->m.erase(key));
MapData *newData = new MapData;
size_type result = newData->copyIfNotEquivalentTo(d->m, key);
d.reset(newData);
return result;
}
template <typename Predicate>
size_type removeIf(Predicate pred)
{
return QtPrivate::associative_erase_if(*this, pred);
}
T take(const Key &key)
{
if (!d)
return T();
const auto copy = d.isShared() ? *this : QMap(); // keep `key` alive across the detach
// TODO: improve. There is no need of copying all the
// elements (the one to be removed can be skipped).
detach();
auto i = d->m.find(key);
if (i != d->m.end()) {
T result(std::move(i->second));
d->m.erase(i);
return result;
}
return T();
}
bool contains(const Key &key) const
{
if (!d)
return false;
auto i = d->m.find(key);
return i != d->m.end();
}
Key key(const T &value, const Key &defaultKey = Key()) const
{
if (!d)
return defaultKey;
return d->key(value, defaultKey);
}
T value(const Key &key, const T &defaultValue = T()) const
{
if (!d)
return defaultValue;
const auto i = d->m.find(key);
if (i != d->m.cend())
return i->second;
return defaultValue;
}
T &operator[](const Key &key)
{
const auto copy = d.isShared() ? *this : QMap(); // keep `key` alive across the detach
detach();
auto i = d->m.find(key);
if (i == d->m.end())
i = d->m.insert({key, T()}).first;
return i->second;
}
// CHANGE: return T, not const T!
T operator[](const Key &key) const
{
return value(key);
}
QList<Key> keys() const
{
if (!d)
return {};
return d->keys();
}
QList<Key> keys(const T &value) const
{
if (!d)
return {};
return d->keys(value);
}
QList<T> values() const
{
if (!d)
return {};
return d->values();
}
size_type count(const Key &key) const
{
if (!d)
return 0;
return d->count(key);
}
size_type count() const
{
return size();
}
inline const Key &firstKey() const { Q_ASSERT(!isEmpty()); return constBegin().key(); }
inline const Key &lastKey() const { Q_ASSERT(!isEmpty()); return (--constEnd()).key(); }
inline T &first() { Q_ASSERT(!isEmpty()); return *begin(); }
inline const T &first() const { Q_ASSERT(!isEmpty()); return *constBegin(); }
inline T &last() { Q_ASSERT(!isEmpty()); return *(--end()); }
inline const T &last() const { Q_ASSERT(!isEmpty()); return *(--constEnd()); }
class const_iterator;
class iterator
{
friend class QMap<Key, T>;
friend class const_iterator;
typename Map::iterator i;
explicit iterator(typename Map::iterator it) : i(it) {}
public:
using iterator_category = std::bidirectional_iterator_tag;
using difference_type = qptrdiff;
using value_type = T;
using pointer = T *;
using reference = T &;
iterator() = default;
const Key &key() const { return i->first; }
T &value() const { return i->second; }
T &operator*() const { return i->second; }
T *operator->() const { return &i->second; }
friend bool operator==(const iterator &lhs, const iterator &rhs) { return lhs.i == rhs.i; }
friend bool operator!=(const iterator &lhs, const iterator &rhs) { return lhs.i != rhs.i; }
iterator &operator++()
{
++i;
return *this;
}
iterator operator++(int)
{
iterator r = *this;
++i;
return r;
}
iterator &operator--()
{
--i;
return *this;
}
iterator operator--(int)
{
iterator r = *this;
--i;
return r;
}
#if QT_DEPRECATED_SINCE(6, 0)
QT_DEPRECATED_VERSION_X_6_0("Use std::next; QMap iterators are not random access")
//! [qmap-op-it-plus-step]
friend iterator operator+(iterator it, difference_type j) { return std::next(it, j); }
QT_DEPRECATED_VERSION_X_6_0("Use std::prev; QMap iterators are not random access")
//! [qmap-op-it-minus-step]
friend iterator operator-(iterator it, difference_type j) { return std::prev(it, j); }
QT_DEPRECATED_VERSION_X_6_0("Use std::next or std::advance; QMap iterators are not random access")
iterator &operator+=(difference_type j) { std::advance(*this, j); return *this; }
QT_DEPRECATED_VERSION_X_6_0("Use std::prev or std::advance; QMap iterators are not random access")
iterator &operator-=(difference_type j) { std::advance(*this, -j); return *this; }
QT_DEPRECATED_VERSION_X_6_0("Use std::next; QMap iterators are not random access")
//! [qmap-op-step-plus-it]
friend iterator operator+(difference_type j, iterator it) { return std::next(it, j); }
QT_DEPRECATED_VERSION_X_6_0("Use std::prev; QMap iterators are not random access")
//! [qmap-op-step-minus-it]
friend iterator operator-(difference_type j, iterator it) { return std::prev(it, j); }
#endif
};
class const_iterator
{
friend class QMap<Key, T>;
typename Map::const_iterator i;
explicit const_iterator(typename Map::const_iterator it) : i(it) {}
public:
using iterator_category = std::bidirectional_iterator_tag;
using difference_type = qptrdiff;
using value_type = T;
using pointer = const T *;
using reference = const T &;
const_iterator() = default;
Q_IMPLICIT const_iterator(const iterator &o) : i(o.i) {}
const Key &key() const { return i->first; }
const T &value() const { return i->second; }
const T &operator*() const { return i->second; }
const T *operator->() const { return &i->second; }
friend bool operator==(const const_iterator &lhs, const const_iterator &rhs) { return lhs.i == rhs.i; }
friend bool operator!=(const const_iterator &lhs, const const_iterator &rhs) { return lhs.i != rhs.i; }
const_iterator &operator++()
{
++i;
return *this;
}
const_iterator operator++(int)
{
const_iterator r = *this;
++i;
return r;
}
const_iterator &operator--()
{
--i;
return *this;
}
const_iterator operator--(int)
{
const_iterator r = *this;
--i;
return r;
}
#if QT_DEPRECATED_SINCE(6, 0)
QT_DEPRECATED_VERSION_X_6_0("Use std::next; QMap iterators are not random access")
//! [qmap-op-it-plus-step-const]
friend const_iterator operator+(const_iterator it, difference_type j) { return std::next(it, j); }
QT_DEPRECATED_VERSION_X_6_0("Use std::prev; QMap iterators are not random access")
//! [qmap-op-it-minus-step-const]
friend const_iterator operator-(const_iterator it, difference_type j) { return std::prev(it, j); }
QT_DEPRECATED_VERSION_X_6_0("Use std::next or std::advance; QMap iterators are not random access")
const_iterator &operator+=(difference_type j) { std::advance(*this, j); return *this; }
QT_DEPRECATED_VERSION_X_6_0("Use std::prev or std::advance; QMap iterators are not random access")
const_iterator &operator-=(difference_type j) { std::advance(*this, -j); return *this; }
QT_DEPRECATED_VERSION_X_6_0("Use std::next; QMap iterators are not random access")
//! [qmap-op-step-plus-it-const]
friend const_iterator operator+(difference_type j, const_iterator it) { return std::next(it, j); }
QT_DEPRECATED_VERSION_X_6_0("Use std::prev; QMap iterators are not random access")
//! [qmap-op-step-minus-it-const]
friend const_iterator operator-(difference_type j, const_iterator it) { return std::prev(it, j); }
#endif
};
class key_iterator
{
const_iterator i;
public:
typedef typename const_iterator::iterator_category iterator_category;
typedef typename const_iterator::difference_type difference_type;
typedef Key value_type;
typedef const Key *pointer;
typedef const Key &reference;
key_iterator() = default;
explicit key_iterator(const_iterator o) : i(o) { }
const Key &operator*() const { return i.key(); }
const Key *operator->() const { return &i.key(); }
bool operator==(key_iterator o) const { return i == o.i; }
bool operator!=(key_iterator o) const { return i != o.i; }
inline key_iterator &operator++() { ++i; return *this; }
inline key_iterator operator++(int) { return key_iterator(i++);}
inline key_iterator &operator--() { --i; return *this; }
inline key_iterator operator--(int) { return key_iterator(i--); }
const_iterator base() const { return i; }
};
typedef QKeyValueIterator<const Key&, const T&, const_iterator> const_key_value_iterator;
typedef QKeyValueIterator<const Key&, T&, iterator> key_value_iterator;
// STL style
iterator begin() { detach(); return iterator(d->m.begin()); }
const_iterator begin() const { if (!d) return const_iterator(); return const_iterator(d->m.cbegin()); }
const_iterator constBegin() const { return begin(); }
const_iterator cbegin() const { return begin(); }
iterator end() { detach(); return iterator(d->m.end()); }
const_iterator end() const { if (!d) return const_iterator(); return const_iterator(d->m.end()); }
const_iterator constEnd() const { return end(); }
const_iterator cend() const { return end(); }
key_iterator keyBegin() const { return key_iterator(begin()); }
key_iterator keyEnd() const { return key_iterator(end()); }
key_value_iterator keyValueBegin() { return key_value_iterator(begin()); }
key_value_iterator keyValueEnd() { return key_value_iterator(end()); }
const_key_value_iterator keyValueBegin() const { return const_key_value_iterator(begin()); }
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)
{
return erase(it, std::next(it));
}
iterator erase(const_iterator afirst, const_iterator alast)
{
if (!d)
return iterator();
if (!d.isShared())
return iterator(d->m.erase(afirst.i, alast.i));
auto result = d->erase(afirst.i, alast.i);
d.reset(result.data);
return iterator(result.it);
}
// more Qt
typedef iterator Iterator;
typedef const_iterator ConstIterator;
iterator find(const Key &key)
{
const auto copy = d.isShared() ? *this : QMap(); // keep `key` alive across the detach
detach();
return iterator(d->m.find(key));
}
const_iterator find(const Key &key) const
{
if (!d)
return const_iterator();
return const_iterator(d->m.find(key));
}
const_iterator constFind(const Key &key) const
{
return find(key);
}
iterator lowerBound(const Key &key)
{
const auto copy = d.isShared() ? *this : QMap(); // keep `key` alive across the detach
detach();
return iterator(d->m.lower_bound(key));
}
const_iterator lowerBound(const Key &key) const
{
if (!d)
return const_iterator();
return const_iterator(d->m.lower_bound(key));
}
iterator upperBound(const Key &key)
{
const auto copy = d.isShared() ? *this : QMap(); // keep `key` alive across the detach
detach();
return iterator(d->m.upper_bound(key));
}
const_iterator upperBound(const Key &key) const
{
if (!d)
return const_iterator();
return const_iterator(d->m.upper_bound(key));
}
iterator insert(const Key &key, const T &value)
{
const auto copy = d.isShared() ? *this : QMap(); // keep `key` alive across the detach
// TODO: improve. In case of assignment, why copying first?
detach();
return iterator(d->m.insert_or_assign(key, value).first);
}
iterator insert(const_iterator pos, const Key &key, const T &value)
{
// TODO: improve. In case of assignment, why copying first?
typename Map::const_iterator dpos;
const auto copy = d.isShared() ? *this : QMap(); // keep `key`/`value` alive across the detach
if (!d || d.isShared()) {
auto posDistance = d ? std::distance(d->m.cbegin(), pos.i) : 0;
detach();
dpos = std::next(d->m.cbegin(), posDistance);
} else {
dpos = pos.i;
}
return iterator(d->m.insert_or_assign(dpos, key, value));
}
void insert(const QMap<Key, T> &map)
{
// TODO: improve. In case of assignment, why copying first?
if (map.isEmpty())
return;
detach();
#ifdef __cpp_lib_node_extract
auto copy = map.d->m;
copy.merge(std::move(d->m));
d->m = std::move(copy);
#else
// this is a std::copy, but we can't use std::inserter (need insert_or_assign...).
// copy in reverse order, trying to make effective use of insertionHint.
auto insertionHint = d->m.end();
auto mapIt = map.d->m.crbegin();
auto end = map.d->m.crend();
for (; mapIt != end; ++mapIt)
insertionHint = d->m.insert_or_assign(insertionHint, mapIt->first, mapIt->second);
#endif
}
void insert(QMap<Key, T> &&map)
{
if (!map.d || map.d->m.empty())
return;
if (map.d.isShared()) {
// fall back to a regular copy
insert(map);
return;
}
detach();
#ifdef __cpp_lib_node_extract
map.d->m.merge(std::move(d->m));
*this = std::move(map);
#else
// same as above
auto insertionHint = d->m.end();
auto mapIt = map.d->m.crbegin();
auto end = map.d->m.crend();
for (; mapIt != end; ++mapIt)
insertionHint = d->m.insert_or_assign(insertionHint, std::move(mapIt->first), std::move(mapIt->second));
#endif
}
// STL compatibility
inline bool empty() const
{
return isEmpty();
}
QPair<iterator, iterator> equal_range(const Key &akey)
{
const auto copy = d.isShared() ? *this : QMap(); // keep `key` alive across the detach
detach();
auto result = d->m.equal_range(akey);
return {iterator(result.first), iterator(result.second)};
}
QPair<const_iterator, const_iterator> equal_range(const Key &akey) const
{
if (!d)
return {};
auto result = d->m.equal_range(akey);
return {const_iterator(result.first), const_iterator(result.second)};
}
};
Q_DECLARE_ASSOCIATIVE_ITERATOR(Map)
Q_DECLARE_MUTABLE_ASSOCIATIVE_ITERATOR(Map)
template <typename Key, typename T, typename Predicate>
qsizetype erase_if(QMap<Key, T> &map, Predicate pred)
{
return QtPrivate::associative_erase_if(map, pred);
}
//
// QMultiMap
//
template <class Key, class T>
class QMultiMap
{
using Map = std::multimap<Key, T>;
using MapData = QMapData<Map>;
QtPrivate::QExplicitlySharedDataPointerV2<MapData> d;
public:
using key_type = Key;
using mapped_type = T;
using difference_type = qptrdiff;
using size_type = qsizetype;
QMultiMap() = default;
// implicitly generated special member functions are OK!
QMultiMap(std::initializer_list<std::pair<Key,T>> list)
{
for (auto &p : list)
insert(p.first, p.second);
}
void swap(QMultiMap<Key, T> &other) noexcept
{
d.swap(other.d);
}
explicit QMultiMap(const QMap<Key, T> &other)
: d(other.isEmpty() ? nullptr : new MapData)
{
if (d) {
Q_ASSERT(other.d);
d->m.insert(other.d->m.begin(),
other.d->m.end());
}
}
explicit QMultiMap(QMap<Key, T> &&other)
: d(other.isEmpty() ? nullptr : new MapData)
{
if (d) {
Q_ASSERT(other.d);
if (other.d.isShared()) {
d->m.insert(other.d->m.begin(),
other.d->m.end());
} else {
#ifdef __cpp_lib_node_extract
d->m.merge(std::move(other.d->m));
#else
d->m.insert(std::make_move_iterator(other.d->m.begin()),
std::make_move_iterator(other.d->m.end()));
#endif
}
}
}
explicit QMultiMap(const std::multimap<Key, T> &other)
: d(other.empty() ? nullptr : new MapData(other))
{
}
explicit QMultiMap(std::multimap<Key, T> &&other)
: d(other.empty() ? nullptr : new MapData(std::move(other)))
{
}
// CHANGE: return type
Q_DECL_DEPRECATED_X("Use toStdMultiMap instead")
std::multimap<Key, T> toStdMap() const
{
return toStdMultiMap();
}
std::multimap<Key, T> toStdMultiMap() const &
{
if (d)
return d->m;
return {};
}
std::multimap<Key, T> toStdMultiMap() &&
{
if (d) {
if (d.isShared())
return d->m;
else
return std::move(d->m);
}
return {};
}
#ifndef Q_QDOC
template <typename AKey = Key, typename AT = T> friend
QTypeTraits::compare_eq_result_container<QMultiMap, AKey, AT> operator==(const QMultiMap &lhs, const QMultiMap &rhs)
{
if (lhs.d == rhs.d)
return true;
if (!lhs.d)
return rhs == lhs;
Q_ASSERT(lhs.d);
return rhs.d ? (lhs.d->m == rhs.d->m) : lhs.d->m.empty();
}
template <typename AKey = Key, typename AT = T> friend
QTypeTraits::compare_eq_result_container<QMultiMap, AKey, AT> operator!=(const QMultiMap &lhs, const QMultiMap &rhs)
{
return !(lhs == rhs);
}
// TODO: add the other comparison operators; std::multimap has them.
#else
friend bool operator==(const QMultiMap &lhs, const QMultiMap &rhs);
friend bool operator!=(const QMultiMap &lhs, const QMultiMap &rhs);
#endif // Q_QDOC
size_type size() const { return d ? size_type(d->m.size()) : size_type(0); }
bool isEmpty() const { return d ? d->m.empty() : true; }
void detach()
{
if (d)
d.detach();
else
d.reset(new MapData);
}
bool isDetached() const noexcept
{
return d ? !d.isShared() : false; // false makes little sense, but that's shared_null's behavior...
}
bool isSharedWith(const QMultiMap<Key, T> &other) const noexcept
{
return d == other.d; // also this makes little sense?
}
void clear()
{
if (!d)
return;
if (!d.isShared())
d->m.clear();
else
d.reset();
}
size_type remove(const Key &key)
{
if (!d)
return 0;
if (!d.isShared())
return size_type(d->m.erase(key));
MapData *newData = new MapData;
size_type result = newData->copyIfNotEquivalentTo(d->m, key);
d.reset(newData);
return result;
}
size_type remove(const Key &key, const T &value)
{
if (!d)
return 0;
// key and value may belong to this map. As such, we need to copy
// them to ensure they stay valid throughout the iteration below
// (which may destroy them)
const Key keyCopy = key;
const T valueCopy = value;
// TODO: improve. Copy over only the elements not to be removed.
detach();
size_type result = 0;
const auto &keyCompare = d->m.key_comp();
auto i = d->m.find(keyCopy);
const auto e = d->m.end();
while (i != e && !keyCompare(keyCopy, i->first)) {
if (i->second == valueCopy) {
i = d->m.erase(i);
++result;
} else {
++i;
}
}
return result;
}
template <typename Predicate>
size_type removeIf(Predicate pred)
{
return QtPrivate::associative_erase_if(*this, pred);
}
T take(const Key &key)
{
if (!d)
return T();
const auto copy = d.isShared() ? *this : QMultiMap(); // keep `key` alive across the detach
// TODO: improve. There is no need of copying all the
// elements (the one to be removed can be skipped).
detach();
auto i = d->m.find(key);
if (i != d->m.end()) {
T result(std::move(i->second));
d->m.erase(i);
return result;
}
return T();
}
bool contains(const Key &key) const
{
if (!d)
return false;
auto i = d->m.find(key);
return i != d->m.end();
}
bool contains(const Key &key, const T &value) const
{
return find(key, value) != end();
}
Key key(const T &value, const Key &defaultKey = Key()) const
{
if (!d)
return defaultKey;
return d->key(value, defaultKey);
}
T value(const Key &key, const T &defaultValue = T()) const
{
if (!d)
return defaultValue;
const auto i = d->m.find(key);
if (i != d->m.cend())
return i->second;
return defaultValue;
}
QList<Key> keys() const
{
if (!d)
return {};
return d->keys();
}
QList<Key> keys(const T &value) const
{
if (!d)
return {};
return d->keys(value);
}
QList<Key> uniqueKeys() const
{
QList<Key> result;
if (!d)
return result;
result.reserve(size());
std::unique_copy(keyBegin(), keyEnd(),
std::back_inserter(result));
result.shrink_to_fit();
return result;
}
QList<T> values() const
{
if (!d)
return {};
return d->values();
}
QList<T> values(const Key &key) const
{
QList<T> result;
const auto range = equal_range(key);
result.reserve(std::distance(range.first, range.second));
std::copy(range.first, range.second, std::back_inserter(result));
return result;
}
size_type count(const Key &key) const
{
if (!d)
return 0;
return d->count(key);
}
size_type count(const Key &key, const T &value) const
{
if (!d)
return 0;
// TODO: improve; no need of scanning the equal_range twice.
auto range = d->m.equal_range(key);
return size_type(std::count_if(range.first,
range.second,
MapData::valueIsEqualTo(value)));
}
inline const Key &firstKey() const { Q_ASSERT(!isEmpty()); return constBegin().key(); }
inline const Key &lastKey() const { Q_ASSERT(!isEmpty()); return std::next(constEnd(), -1).key(); }
inline T &first() { Q_ASSERT(!isEmpty()); return *begin(); }
inline const T &first() const { Q_ASSERT(!isEmpty()); return *constBegin(); }
inline T &last() { Q_ASSERT(!isEmpty()); return *std::next(end(), -1); }
inline const T &last() const { Q_ASSERT(!isEmpty()); return *std::next(constEnd(), -1); }
class const_iterator;
class iterator
{
friend class QMultiMap<Key, T>;
friend class const_iterator;
typename Map::iterator i;
explicit iterator(typename Map::iterator it) : i(it) {}
public:
using iterator_category = std::bidirectional_iterator_tag;
using difference_type = qptrdiff;
using value_type = T;
using pointer = T *;
using reference = T &;
iterator() = default;
const Key &key() const { return i->first; }
T &value() const { return i->second; }
T &operator*() const { return i->second; }
T *operator->() const { return &i->second; }
friend bool operator==(const iterator &lhs, const iterator &rhs) { return lhs.i == rhs.i; }
friend bool operator!=(const iterator &lhs, const iterator &rhs) { return lhs.i != rhs.i; }
iterator &operator++()
{
++i;
return *this;
}
iterator operator++(int)
{
iterator r = *this;
++i;
return r;
}
iterator &operator--()
{
--i;
return *this;
}
iterator operator--(int)
{
iterator r = *this;
--i;
return r;
}
#if QT_DEPRECATED_SINCE(6, 0)
QT_DEPRECATED_VERSION_X_6_0("Use std::next; QMultiMap iterators are not random access")
//! [qmultimap-op-it-plus-step]
friend iterator operator+(iterator it, difference_type j) { return std::next(it, j); }
QT_DEPRECATED_VERSION_X_6_0("Use std::prev; QMultiMap iterators are not random access")
//! [qmultimap-op-it-minus-step]
friend iterator operator-(iterator it, difference_type j) { return std::prev(it, j); }
QT_DEPRECATED_VERSION_X_6_0("Use std::next or std::advance; QMultiMap iterators are not random access")
iterator &operator+=(difference_type j) { std::advance(*this, j); return *this; }
QT_DEPRECATED_VERSION_X_6_0("Use std::prev or std::advance; QMultiMap iterators are not random access")
iterator &operator-=(difference_type j) { std::advance(*this, -j); return *this; }
QT_DEPRECATED_VERSION_X_6_0("Use std::next; QMultiMap iterators are not random access")
//! [qmultimap-op-step-plus-it]
friend iterator operator+(difference_type j, iterator it) { return std::next(it, j); }
QT_DEPRECATED_VERSION_X_6_0("Use std::prev; QMultiMap iterators are not random access")
//! [qmultimap-op-step-minus-it]
friend iterator operator-(difference_type j, iterator it) { return std::prev(it, j); }
#endif
};
class const_iterator
{
friend class QMultiMap<Key, T>;
typename Map::const_iterator i;
explicit const_iterator(typename Map::const_iterator it) : i(it) {}
public:
using iterator_category = std::bidirectional_iterator_tag;
using difference_type = qptrdiff;
using value_type = T;
using pointer = const T *;
using reference = const T &;
const_iterator() = default;
Q_IMPLICIT const_iterator(const iterator &o) : i(o.i) {}
const Key &key() const { return i->first; }
const T &value() const { return i->second; }
const T &operator*() const { return i->second; }
const T *operator->() const { return &i->second; }
friend bool operator==(const const_iterator &lhs, const const_iterator &rhs) { return lhs.i == rhs.i; }
friend bool operator!=(const const_iterator &lhs, const const_iterator &rhs) { return lhs.i != rhs.i; }
const_iterator &operator++()
{
++i;
return *this;
}
const_iterator operator++(int)
{
const_iterator r = *this;
++i;
return r;
}
const_iterator &operator--()
{
--i;
return *this;
}
const_iterator operator--(int)
{
const_iterator r = *this;
--i;
return r;
}
#if QT_DEPRECATED_SINCE(6, 0)
QT_DEPRECATED_VERSION_X_6_0("Use std::next; QMultiMap iterators are not random access")
//! [qmultimap-op-it-plus-step-const]
friend const_iterator operator+(const_iterator it, difference_type j) { return std::next(it, j); }
QT_DEPRECATED_VERSION_X_6_0("Use std::prev; QMultiMap iterators are not random access")
//! [qmultimap-op-it-minus-step-const]
friend const_iterator operator-(const_iterator it, difference_type j) { return std::prev(it, j); }
QT_DEPRECATED_VERSION_X_6_0("Use std::next or std::advance; QMultiMap iterators are not random access")
const_iterator &operator+=(difference_type j) { std::advance(*this, j); return *this; }
QT_DEPRECATED_VERSION_X_6_0("Use std::prev or std::advance; QMultiMap iterators are not random access")
const_iterator &operator-=(difference_type j) { std::advance(*this, -j); return *this; }
QT_DEPRECATED_VERSION_X_6_0("Use std::next; QMultiMap iterators are not random access")
//! [qmultimap-op-step-plus-it-const]
friend const_iterator operator+(difference_type j, const_iterator it) { return std::next(it, j); }
QT_DEPRECATED_VERSION_X_6_0("Use std::prev; QMultiMap iterators are not random access")
//! [qmultimap-op-step-minus-it-const]
friend const_iterator operator-(difference_type j, const_iterator it) { return std::prev(it, j); }
#endif
};
class key_iterator
{
const_iterator i;
public:
typedef typename const_iterator::iterator_category iterator_category;
typedef typename const_iterator::difference_type difference_type;
typedef Key value_type;
typedef const Key *pointer;
typedef const Key &reference;
key_iterator() = default;
explicit key_iterator(const_iterator o) : i(o) { }
const Key &operator*() const { return i.key(); }
const Key *operator->() const { return &i.key(); }
bool operator==(key_iterator o) const { return i == o.i; }
bool operator!=(key_iterator o) const { return i != o.i; }
inline key_iterator &operator++() { ++i; return *this; }
inline key_iterator operator++(int) { return key_iterator(i++);}
inline key_iterator &operator--() { --i; return *this; }
inline key_iterator operator--(int) { return key_iterator(i--); }
const_iterator base() const { return i; }
};
typedef QKeyValueIterator<const Key&, const T&, const_iterator> const_key_value_iterator;
typedef QKeyValueIterator<const Key&, T&, iterator> key_value_iterator;
// STL style
iterator begin() { detach(); return iterator(d->m.begin()); }
const_iterator begin() const { if (!d) return const_iterator(); return const_iterator(d->m.cbegin()); }
const_iterator constBegin() const { return begin(); }
const_iterator cbegin() const { return begin(); }
iterator end() { detach(); return iterator(d->m.end()); }
const_iterator end() const { if (!d) return const_iterator(); return const_iterator(d->m.end()); }
const_iterator constEnd() const { return end(); }
const_iterator cend() const { return end(); }
key_iterator keyBegin() const { return key_iterator(begin()); }
key_iterator keyEnd() const { return key_iterator(end()); }
key_value_iterator keyValueBegin() { return key_value_iterator(begin()); }
key_value_iterator keyValueEnd() { return key_value_iterator(end()); }
const_key_value_iterator keyValueBegin() const { return const_key_value_iterator(begin()); }
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)
{
return erase(it, std::next(it));
}
iterator erase(const_iterator afirst, const_iterator alast)
{
if (!d)
return iterator();
if (!d.isShared())
return iterator(d->m.erase(afirst.i, alast.i));
auto result = d->erase(afirst.i, alast.i);
d.reset(result.data);
return iterator(result.it);
}
// more Qt
typedef iterator Iterator;
typedef const_iterator ConstIterator;
size_type count() const
{
return size();
}
iterator find(const Key &key)
{
const auto copy = d.isShared() ? *this : QMultiMap(); // keep `key` alive across the detach
detach();
return iterator(d->m.find(key));
}
const_iterator find(const Key &key) const
{
if (!d)
return const_iterator();
return const_iterator(d->m.find(key));
}
const_iterator constFind(const Key &key) const
{
return find(key);
}
iterator find(const Key &key, const T &value)
{
const auto copy = d.isShared() ? *this : QMultiMap(); // keep `key`/`value` alive across the detach
detach();
auto range = d->m.equal_range(key);
auto i = std::find_if(range.first, range.second,
MapData::valueIsEqualTo(value));
if (i != range.second)
return iterator(i);
return iterator(d->m.end());
}
const_iterator find(const Key &key, const T &value) const
{
if (!d)
return const_iterator();
auto range = d->m.equal_range(key);
auto i = std::find_if(range.first, range.second,
MapData::valueIsEqualTo(value));
if (i != range.second)
return const_iterator(i);
return const_iterator(d->m.end());
}
const_iterator constFind(const Key &key, const T &value) const
{
return find(key, value);
}
iterator lowerBound(const Key &key)
{
const auto copy = d.isShared() ? *this : QMultiMap(); // keep `key` alive across the detach
detach();
return iterator(d->m.lower_bound(key));
}
const_iterator lowerBound(const Key &key) const
{
if (!d)
return const_iterator();
return const_iterator(d->m.lower_bound(key));
}
iterator upperBound(const Key &key)
{
const auto copy = d.isShared() ? *this : QMultiMap(); // keep `key` alive across the detach
detach();
return iterator(d->m.upper_bound(key));
}
const_iterator upperBound(const Key &key) const
{
if (!d)
return const_iterator();
return const_iterator(d->m.upper_bound(key));
}
iterator insert(const Key &key, const T &value)
{
const auto copy = d.isShared() ? *this : QMultiMap(); // keep `key`/`value` alive across the detach
detach();
// note that std::multimap inserts at the end of an equal_range for a key,
// QMultiMap at the beginning.
auto i = d->m.lower_bound(key);
return iterator(d->m.insert(i, {key, value}));
}
iterator insert(const_iterator pos, const Key &key, const T &value)
{
const auto copy = d.isShared() ? *this : QMultiMap(); // keep `key`/`value` alive across the detach
typename Map::const_iterator dpos;
if (!d || d.isShared()) {
auto posDistance = d ? std::distance(d->m.cbegin(), pos.i) : 0;
detach();
dpos = std::next(d->m.cbegin(), posDistance);
} else {
dpos = pos.i;
}
return iterator(d->m.insert(dpos, {key, value}));
}
#if QT_DEPRECATED_SINCE(6, 0)
QT_DEPRECATED_VERSION_X_6_0("Use insert() instead")
iterator insertMulti(const Key &key, const T &value)
{
return insert(key, value);
}
QT_DEPRECATED_VERSION_X_6_0("Use insert() instead")
iterator insertMulti(const_iterator pos, const Key &key, const T &value)
{
return insert(pos, key, value);
}
QT_DEPRECATED_VERSION_X_6_0("Use unite() instead")
void insert(const QMultiMap<Key, T> &map)
{
unite(map);
}
QT_DEPRECATED_VERSION_X_6_0("Use unite() instead")
void insert(QMultiMap<Key, T> &&map)
{
unite(std::move(map));
}
#endif
iterator replace(const Key &key, const T &value)
{
const auto copy = d.isShared() ? *this : QMultiMap(); // keep `key`/`value` alive across the detach
// TODO: improve. No need of copying and then overwriting.
detach();
// Similarly, improve here (e.g. lower_bound and hinted insert);
// there's no insert_or_assign on multimaps
auto i = d->m.find(key);
if (i != d->m.end())
i->second = value;
else
i = d->m.insert({key, value});
return iterator(i);
}
// STL compatibility
inline bool empty() const { return isEmpty(); }
QPair<iterator, iterator> equal_range(const Key &akey)
{
const auto copy = d.isShared() ? *this : QMultiMap(); // keep `key` alive across the detach
detach();
auto result = d->m.equal_range(akey);
return {iterator(result.first), iterator(result.second)};
}
QPair<const_iterator, const_iterator> equal_range(const Key &akey) const
{
if (!d)
return {};
auto result = d->m.equal_range(akey);
return {const_iterator(result.first), const_iterator(result.second)};
}
QMultiMap &unite(const QMultiMap &other)
{
if (other.isEmpty())
return *this;
detach();
auto copy = other.d->m;
#ifdef __cpp_lib_node_extract
copy.merge(std::move(d->m));
#else
copy.insert(std::make_move_iterator(d->m.begin()),
std::make_move_iterator(d->m.end()));
#endif
d->m = std::move(copy);
return *this;
}
QMultiMap &unite(QMultiMap<Key, T> &&other)
{
if (!other.d || other.d->m.empty())
return *this;
if (other.d.isShared()) {
// fall back to a regular copy
unite(other);
return *this;
}
detach();
#ifdef __cpp_lib_node_extract
other.d->m.merge(std::move(d->m));
#else
other.d->m.insert(std::make_move_iterator(d->m.begin()),
std::make_move_iterator(d->m.end()));
#endif
*this = std::move(other);
return *this;
}
};
Q_DECLARE_ASSOCIATIVE_ITERATOR(MultiMap)
Q_DECLARE_MUTABLE_ASSOCIATIVE_ITERATOR(MultiMap)
template <typename Key, typename T>
QMultiMap<Key, T> operator+(const QMultiMap<Key, T> &lhs, const QMultiMap<Key, T> &rhs)
{
auto result = lhs;
result += rhs;
return result;
}
template <typename Key, typename T>
QMultiMap<Key, T> operator+=(QMultiMap<Key, T> &lhs, const QMultiMap<Key, T> &rhs)
{
return lhs.unite(rhs);
}
template <typename Key, typename T, typename Predicate>
qsizetype erase_if(QMultiMap<Key, T> &map, Predicate pred)
{
return QtPrivate::associative_erase_if(map, pred);
}
QT_END_NAMESPACE
#endif // QMAP_H