|
|
@@ -6,7 +6,7 @@
|
|
|
// _/_____/
|
|
|
//
|
|
|
// robin_hood::unordered_map for C++14
|
|
|
-// version 3.2.5
|
|
|
+// version 3.2.7
|
|
|
// https://github.com/martinus/robin-hood-hashing
|
|
|
//
|
|
|
// Licensed under the MIT License <http://opensource.org/licenses/MIT>.
|
|
|
@@ -37,7 +37,7 @@
|
|
|
// see https://semver.org/
|
|
|
#define ROBIN_HOOD_VERSION_MAJOR 3 // for incompatible API changes
|
|
|
#define ROBIN_HOOD_VERSION_MINOR 2 // for adding functionality in a backwards-compatible manner
|
|
|
-#define ROBIN_HOOD_VERSION_PATCH 4 // for backwards-compatible bug fixes
|
|
|
+#define ROBIN_HOOD_VERSION_PATCH 7 // for backwards-compatible bug fixes
|
|
|
|
|
|
#include <algorithm>
|
|
|
#include <cstdlib>
|
|
|
@@ -363,16 +363,6 @@ struct NodeAllocator<T, MinSize, MaxSize, true> {
|
|
|
template <typename T, size_t MinSize, size_t MaxSize>
|
|
|
struct NodeAllocator<T, MinSize, MaxSize, false> : public BulkPoolAllocator<T, MinSize, MaxSize> {};
|
|
|
|
|
|
-// All empty maps initial mInfo point to this infobyte. That way lookup in an empty map
|
|
|
-// always returns false, and this is a very hot byte.
|
|
|
-//
|
|
|
-// we have to use data >1byte (at least 2 bytes), because initially we set mShift to 63 (has to be
|
|
|
-// <63), so initial index will be 0 or 1.
|
|
|
-namespace DummyInfoByte {
|
|
|
-
|
|
|
-static uint64_t b = 0;
|
|
|
-
|
|
|
-} // namespace DummyInfoByte
|
|
|
} // namespace detail
|
|
|
|
|
|
struct is_transparent_tag {};
|
|
|
@@ -846,6 +836,8 @@ private:
|
|
|
|
|
|
// Iter ////////////////////////////////////////////////////////////
|
|
|
|
|
|
+ struct fast_forward_tag {};
|
|
|
+
|
|
|
// generic iterator for both const_iterator and iterator.
|
|
|
template <bool IsConst>
|
|
|
class Iter {
|
|
|
@@ -874,21 +866,18 @@ private:
|
|
|
: mKeyVals(valPtr)
|
|
|
, mInfo(infoPtr) {}
|
|
|
|
|
|
+ Iter(NodePtr valPtr, uint8_t const* infoPtr,
|
|
|
+ fast_forward_tag ROBIN_HOOD_UNUSED(tag) /*unused*/)
|
|
|
+ : mKeyVals(valPtr)
|
|
|
+ , mInfo(infoPtr) {
|
|
|
+ fastForward();
|
|
|
+ }
|
|
|
+
|
|
|
// prefix increment. Undefined behavior if we are at end()!
|
|
|
Iter& operator++() {
|
|
|
mInfo++;
|
|
|
mKeyVals++;
|
|
|
- int inc;
|
|
|
- do {
|
|
|
- auto const n = detail::unaligned_load<size_t>(mInfo);
|
|
|
-#if ROBIN_HOOD_LITTLE_ENDIAN
|
|
|
- inc = ROBIN_HOOD_COUNT_TRAILING_ZEROES(n) / 8;
|
|
|
-#else
|
|
|
- inc = ROBIN_HOOD_COUNT_LEADING_ZEROES(n) / 8;
|
|
|
-#endif
|
|
|
- mInfo += inc;
|
|
|
- mKeyVals += inc;
|
|
|
- } while (inc == sizeof(size_t));
|
|
|
+ fastForward();
|
|
|
return *this;
|
|
|
}
|
|
|
|
|
|
@@ -911,6 +900,21 @@ private:
|
|
|
}
|
|
|
|
|
|
private:
|
|
|
+ // fast forward to the next non-free info byte
|
|
|
+ void fastForward() {
|
|
|
+ int inc;
|
|
|
+ do {
|
|
|
+ auto const n = detail::unaligned_load<size_t>(mInfo);
|
|
|
+#if ROBIN_HOOD_LITTLE_ENDIAN
|
|
|
+ inc = ROBIN_HOOD_COUNT_TRAILING_ZEROES(n) / 8;
|
|
|
+#else
|
|
|
+ inc = ROBIN_HOOD_COUNT_LEADING_ZEROES(n) / 8;
|
|
|
+#endif
|
|
|
+ mInfo += inc;
|
|
|
+ mKeyVals += inc;
|
|
|
+ } while (inc == sizeof(size_t));
|
|
|
+ }
|
|
|
+
|
|
|
friend class unordered_map<IsFlatMap, MaxLoadFactor100, key_type, mapped_type, hasher,
|
|
|
key_equal>;
|
|
|
NodePtr mKeyVals;
|
|
|
@@ -1030,7 +1034,7 @@ private:
|
|
|
} while (info <= mInfo[idx]);
|
|
|
|
|
|
// nothing found!
|
|
|
- return mMask + 1;
|
|
|
+ return mMask == 0 ? 0 : mMask + 1;
|
|
|
}
|
|
|
|
|
|
void cloneData(const unordered_map& o) {
|
|
|
@@ -1116,22 +1120,8 @@ public:
|
|
|
unordered_map(unordered_map&& o)
|
|
|
: Hash(std::move(static_cast<Hash&>(o)))
|
|
|
, KeyEqual(std::move(static_cast<KeyEqual&>(o)))
|
|
|
- , DataPool(std::move(static_cast<DataPool&>(o)))
|
|
|
- , mKeyVals{std::move(o.mKeyVals)}
|
|
|
- , mInfo{std::move(o.mInfo)}
|
|
|
- , mNumElements{std::move(o.mNumElements)}
|
|
|
- , mMask{std::move(o.mMask)}
|
|
|
- , mMaxNumElementsAllowed{std::move(o.mMaxNumElementsAllowed)}
|
|
|
- , mInfoInc{std::move(o.mInfoInc)}
|
|
|
- , mInfoHashShift{std::move(o.mInfoHashShift)} {
|
|
|
- // set other's mask to 0 so its destructor won't do anything
|
|
|
- o.mMask = 0;
|
|
|
- }
|
|
|
-
|
|
|
- unordered_map& operator=(unordered_map&& o) {
|
|
|
- if (&o != this) {
|
|
|
- // different, move it
|
|
|
- destroy();
|
|
|
+ , DataPool(std::move(static_cast<DataPool&>(o))) {
|
|
|
+ if (o.mMask) {
|
|
|
mKeyVals = std::move(o.mKeyVals);
|
|
|
mInfo = std::move(o.mInfo);
|
|
|
mNumElements = std::move(o.mNumElements);
|
|
|
@@ -1139,12 +1129,33 @@ public:
|
|
|
mMaxNumElementsAllowed = std::move(o.mMaxNumElementsAllowed);
|
|
|
mInfoInc = std::move(o.mInfoInc);
|
|
|
mInfoHashShift = std::move(o.mInfoHashShift);
|
|
|
- Hash::operator=(std::move(static_cast<Hash&>(o)));
|
|
|
- KeyEqual::operator=(std::move(static_cast<KeyEqual&>(o)));
|
|
|
- DataPool::operator=(std::move(static_cast<DataPool&>(o)));
|
|
|
// set other's mask to 0 so its destructor won't do anything
|
|
|
o.mMask = 0;
|
|
|
}
|
|
|
+ }
|
|
|
+
|
|
|
+ unordered_map& operator=(unordered_map&& o) {
|
|
|
+ if (&o != this) {
|
|
|
+ if (o.mMask) {
|
|
|
+ // only move stuff if the other map actually has some data
|
|
|
+ destroy();
|
|
|
+ mKeyVals = std::move(o.mKeyVals);
|
|
|
+ mInfo = std::move(o.mInfo);
|
|
|
+ mNumElements = std::move(o.mNumElements);
|
|
|
+ mMask = std::move(o.mMask);
|
|
|
+ mMaxNumElementsAllowed = std::move(o.mMaxNumElementsAllowed);
|
|
|
+ mInfoInc = std::move(o.mInfoInc);
|
|
|
+ mInfoHashShift = std::move(o.mInfoHashShift);
|
|
|
+ Hash::operator=(std::move(static_cast<Hash&>(o)));
|
|
|
+ KeyEqual::operator=(std::move(static_cast<KeyEqual&>(o)));
|
|
|
+ DataPool::operator=(std::move(static_cast<DataPool&>(o)));
|
|
|
+ // set other's mask to 0 so its destructor won't do anything
|
|
|
+ o.mMask = 0;
|
|
|
+ } else {
|
|
|
+ // nothing in the other map => just clear us.
|
|
|
+ clear();
|
|
|
+ }
|
|
|
+ }
|
|
|
return *this;
|
|
|
}
|
|
|
|
|
|
@@ -1189,10 +1200,12 @@ public:
|
|
|
// clear also resets mInfo to 0, that's sometimes not necessary.
|
|
|
destroy();
|
|
|
|
|
|
- // we assign an invalid pointer, but this is ok because we never dereference it.
|
|
|
- using detail::DummyInfoByte::b;
|
|
|
- mKeyVals = reinterpret_cast<Node*>(&b) - 1; // lgtm [cpp/suspicious-pointer-scaling]
|
|
|
- mInfo = reinterpret_cast<uint8_t*>(&b);
|
|
|
+ // we assign invalid pointer, but this is ok because we never dereference it.
|
|
|
+ // The worst that can happen is that find() is called, which will return an iterator but
|
|
|
+ // it will be the end() iterator.
|
|
|
+ mKeyVals = reinterpret_cast<Node*>(&mMask);
|
|
|
+ // we need to point somewhere thats 0 as long as we're empty
|
|
|
+ mInfo = reinterpret_cast<uint8_t*>(mMask);
|
|
|
Hash::operator=(static_cast<const Hash&>(o));
|
|
|
KeyEqual::operator=(static_cast<const KeyEqual&>(o));
|
|
|
DataPool::operator=(static_cast<DataPool const&>(o));
|
|
|
@@ -1329,27 +1342,31 @@ public:
|
|
|
|
|
|
// Returns 1 if key is found, 0 otherwise.
|
|
|
size_t count(const key_type& key) const {
|
|
|
- return findIdx(key) == (mMask + 1) ? 0 : 1;
|
|
|
+ auto kv = mKeyVals + findIdx(key);
|
|
|
+ if (kv != reinterpret_cast<Node*>(mInfo)) {
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
}
|
|
|
|
|
|
// Returns a reference to the value found for key.
|
|
|
// Throws std::out_of_range if element cannot be found
|
|
|
mapped_type& at(key_type const& key) {
|
|
|
- auto idx = findIdx(key);
|
|
|
- if (idx == mMask + 1) {
|
|
|
+ auto kv = mKeyVals + findIdx(key);
|
|
|
+ if (kv == reinterpret_cast<Node*>(mInfo)) {
|
|
|
doThrow<std::out_of_range>("key not found");
|
|
|
}
|
|
|
- return mKeyVals[idx].getSecond();
|
|
|
+ return kv->getSecond();
|
|
|
}
|
|
|
|
|
|
// Returns a reference to the value found for key.
|
|
|
// Throws std::out_of_range if element cannot be found
|
|
|
mapped_type const& at(key_type const& key) const {
|
|
|
- auto idx = findIdx(key);
|
|
|
- if (idx == mMask + 1) {
|
|
|
+ auto kv = mKeyVals + findIdx(key);
|
|
|
+ if (kv == reinterpret_cast<Node*>(mInfo)) {
|
|
|
doThrow<std::out_of_range>("key not found");
|
|
|
}
|
|
|
- return mKeyVals[idx].getSecond();
|
|
|
+ return kv->getSecond();
|
|
|
}
|
|
|
|
|
|
const_iterator find(const key_type& key) const {
|
|
|
@@ -1378,7 +1395,7 @@ public:
|
|
|
if (empty()) {
|
|
|
return end();
|
|
|
}
|
|
|
- return ++iterator(mKeyVals - 1, mInfo - 1);
|
|
|
+ return iterator(mKeyVals, mInfo, fast_forward_tag{});
|
|
|
}
|
|
|
const_iterator begin() const {
|
|
|
return cbegin();
|
|
|
@@ -1387,7 +1404,7 @@ public:
|
|
|
if (empty()) {
|
|
|
return cend();
|
|
|
}
|
|
|
- return ++const_iterator(mKeyVals - 1, mInfo - 1);
|
|
|
+ return const_iterator(mKeyVals, mInfo, fast_forward_tag{});
|
|
|
}
|
|
|
|
|
|
iterator end() {
|
|
|
@@ -1701,7 +1718,7 @@ private:
|
|
|
|
|
|
void destroy() {
|
|
|
if (0 == mMask) {
|
|
|
- // don't deallocate! we are pointing to DummyInfoByte::b.
|
|
|
+ // don't deallocate!
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
@@ -1711,15 +1728,14 @@ private:
|
|
|
}
|
|
|
|
|
|
// members are sorted so no padding occurs
|
|
|
- Node* mKeyVals = reinterpret_cast<Node*>(reinterpret_cast<uint8_t*>(&detail::DummyInfoByte::b) -
|
|
|
- sizeof(Node)); // 8 byte 8
|
|
|
- uint8_t* mInfo = reinterpret_cast<uint8_t*>(&detail::DummyInfoByte::b); // 8 byte 16
|
|
|
- size_t mNumElements = 0; // 8 byte 24
|
|
|
- size_t mMask = 0; // 8 byte 32
|
|
|
- size_t mMaxNumElementsAllowed = 0; // 8 byte 40
|
|
|
- InfoType mInfoInc = InitialInfoInc; // 4 byte 44
|
|
|
- InfoType mInfoHashShift = InitialInfoHashShift; // 4 byte 48
|
|
|
- // 16 byte 56 if NodeAllocator
|
|
|
+ Node* mKeyVals = reinterpret_cast<Node*>(&mMask); // 8 byte 8
|
|
|
+ uint8_t* mInfo = reinterpret_cast<uint8_t*>(&mMask); // 8 byte 16
|
|
|
+ size_t mNumElements = 0; // 8 byte 24
|
|
|
+ size_t mMask = 0; // 8 byte 32
|
|
|
+ size_t mMaxNumElementsAllowed = 0; // 8 byte 40
|
|
|
+ InfoType mInfoInc = InitialInfoInc; // 4 byte 44
|
|
|
+ InfoType mInfoHashShift = InitialInfoHashShift; // 4 byte 48
|
|
|
+ // 16 byte 56 if NodeAllocator
|
|
|
};
|
|
|
|
|
|
} // namespace detail
|