-
Notifications
You must be signed in to change notification settings - Fork 48
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
QtHasher: a hasher for Qt datatypes inside STL unordered containers
Change-Id: I9347ca29d8e5399ee4ac78f7b45237c719a9ba28 Reviewed-on: https://codereview.kdab.com/c/kdab/KDToolBox/+/84829 Tested-by: Continuous Integration <build@kdab.com> Reviewed-by: Marc Mutz <marc.mutz@kdab.com>
- Loading branch information
Showing
8 changed files
with
298 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
QtHasher | ||
======== | ||
|
||
A hasher to be able to use Qt datatypes in the unordered associative containers from | ||
the Standard Library. | ||
|
||
``` | ||
std::unordered_map<QtType, Foo, KDToolBox::QtHasher<QtType>> map; | ||
map.insert(~~~); | ||
``` | ||
|
||
By default, types such as `std::unordered_map` or `std::unordered_set` require | ||
a specialization of `std::hash` for the key type. The problem is that such a | ||
specialization is not actually provided by most Qt datatypes -- only for string-like | ||
and byte arrays (QString, QStringView, QByteArray, etc.), and only starting | ||
from Qt 5.14. | ||
|
||
On the other hand, most Qt value types do have a hashing function -- `qHash()` -- | ||
already defined for them. We can use that one to implement a custom hasher. | ||
|
||
Note that one is not authorized to specialize customization points for types | ||
not under their direct control. Case in point, one is not authorized to specialize | ||
`std::hash` for Qt datatypes: Qt reserves the right to add specializations at | ||
any time (breaking our code and violating ODR). Hence, we define a custom | ||
hasher. | ||
|
||
Requires a C++11 capable compiler. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
/**************************************************************************** | ||
** MIT License | ||
** | ||
** Copyright (C) 2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com | ||
** Author: Giuseppe D'Angelo <giuseppe.dangelo@kdab.com> | ||
** | ||
** This file is part of KDToolBox (https://github.com/KDAB/KDToolBox). | ||
** | ||
** Permission is hereby granted, free of charge, to any person obtaining a copy | ||
** of this software and associated documentation files (the "Software"), to deal | ||
** in the Software without restriction, including without limitation the rights | ||
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
** copies of the Software, ** and to permit persons to whom the Software is | ||
** furnished to do so, subject to the following conditions: | ||
** | ||
** The above copyright notice and this permission notice (including the next paragraph) | ||
** shall be included in all copies or substantial portions of the Software. | ||
** | ||
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
** LIABILITY, WHETHER IN AN ACTION OF ** CONTRACT, TORT OR OTHERWISE, | ||
** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | ||
** DEALINGS IN THE SOFTWARE. | ||
****************************************************************************/ | ||
|
||
#ifndef KDTOOLBOX_QT_HASHER_H | ||
#define KDTOOLBOX_QT_HASHER_H | ||
|
||
#include <QtCore/qhashfunctions.h> | ||
|
||
#include <utility> | ||
#include <functional> | ||
|
||
namespace KDToolBox { | ||
|
||
namespace detail { | ||
|
||
QT_USE_NAMESPACE | ||
|
||
template <typename ...> using void_t = void; | ||
|
||
using QHashSeedType = decltype(qHash(0)); // which is also the return type of qHash | ||
|
||
template <typename T, typename = void> | ||
class QtHasherBase | ||
{ | ||
// poison | ||
private: | ||
~QtHasherBase(); | ||
QtHasherBase(QtHasherBase &&); | ||
}; | ||
|
||
template <typename T> | ||
class QtHasherBase<T, void_t<decltype(qHash(std::declval<const T &>()))>> | ||
{ | ||
public: | ||
using result_type = std::size_t; | ||
using argument_type = T; | ||
|
||
constexpr std::size_t operator()(const T &t) const noexcept | ||
{ | ||
// this seeds qHash with the result of | ||
// std::hash applied to an int, to reap | ||
// any protection against predictable hash | ||
// values the std implementation may provide | ||
return static_cast<std::size_t>(qHash(t, static_cast<QHashSeedType>(std::hash<int>{}(0)))); | ||
} | ||
|
||
protected: | ||
// prevent accidental slicing, or compilers complaining about a non-virtual dtor | ||
QtHasherBase() = default; | ||
~QtHasherBase() = default; | ||
QtHasherBase(const QtHasherBase &) = default; | ||
QtHasherBase(QtHasherBase &&) = default; | ||
QtHasherBase &operator=(const QtHasherBase &) = default; | ||
QtHasherBase &operator=(QtHasherBase &&) = default; | ||
}; | ||
|
||
} // namespace detail | ||
|
||
template <typename T> | ||
struct QtHasher : detail::QtHasherBase<T> | ||
{ | ||
}; | ||
|
||
} // namespace KDToolBox | ||
|
||
#endif // KDTOOLBOX_QT_HASHER_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
TEMPLATE = subdirs | ||
SUBDIRS += \ | ||
test \ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
TEMPLATE = app | ||
TARGET = tst_qt_hasher | ||
QT = core testlib gui | ||
CONFIG += testcase c++11 strict_c++ | ||
|
||
SOURCES += \ | ||
tst_qt_hasher.cpp | ||
|
||
HEADERS += \ | ||
tst_qt_hasher.h \ | ||
../qt_hasher.h |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
/**************************************************************************** | ||
** MIT License | ||
** | ||
** Copyright (C) 2019-2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com | ||
** | ||
** This file is part of KDToolBox (https://github.com/KDAB/KDToolBox). | ||
** | ||
** Permission is hereby granted, free of charge, to any person obtaining a copy | ||
** of this software and associated documentation files (the "Software"), to deal | ||
** in the Software without restriction, including without limitation the rights | ||
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
** copies of the Software, ** and to permit persons to whom the Software is | ||
** furnished to do so, subject to the following conditions: | ||
** | ||
** The above copyright notice and this permission notice (including the next paragraph) | ||
** shall be included in all copies or substantial portions of the Software. | ||
** | ||
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
** LIABILITY, WHETHER IN AN ACTION OF ** CONTRACT, TORT OR OTHERWISE, | ||
** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | ||
** DEALINGS IN THE SOFTWARE. | ||
****************************************************************************/ | ||
|
||
#include <QtCore> | ||
#include <QtTest> | ||
#include "tst_qt_hasher.h" | ||
#include "../qt_hasher.h" | ||
|
||
#include <unordered_map> | ||
#include <unordered_set> | ||
|
||
struct Foo { int i; }; | ||
|
||
void tst_QtHasher::hash() | ||
{ | ||
std::unordered_map<QString, Foo, KDToolBox::QtHasher<QString>> stringMap; | ||
stringMap.insert({ QStringLiteral("123"), {123} }); | ||
stringMap.insert({ QStringLiteral("456"), {456} }); | ||
stringMap.insert({ QStringLiteral("789"), {789} }); | ||
QCOMPARE(stringMap.size(), std::size_t(3)); | ||
|
||
stringMap.insert({ QStringLiteral("123"), {0} }); | ||
QCOMPARE(stringMap.size(), std::size_t(3)); | ||
|
||
stringMap.clear(); | ||
QCOMPARE(stringMap.size(), std::size_t(0)); | ||
|
||
constexpr int LOOPS = 195044; | ||
for (int i = 0; i < LOOPS; ++i) | ||
stringMap.insert({ QString::number(i), {i} }); | ||
|
||
QCOMPARE(stringMap.size(), std::size_t(LOOPS)); | ||
|
||
for (const auto &v : stringMap) | ||
QCOMPARE(v.first.toInt(), v.second.i); | ||
|
||
|
||
std::unordered_set<QRegularExpression, KDToolBox::QtHasher<QRegularExpression>> regexpSet; | ||
|
||
regexpSet.insert(QRegularExpression("a.*b")); | ||
regexpSet.insert(QRegularExpression("a|b")); | ||
QCOMPARE(regexpSet.size(), std::size_t(2)); | ||
QVERIFY(regexpSet.find(QRegularExpression("a|b")) != regexpSet.end()); | ||
QVERIFY(regexpSet.find(QRegularExpression("a+b")) == regexpSet.end()); | ||
|
||
regexpSet.insert(QRegularExpression("a+b")); | ||
QCOMPARE(regexpSet.size(), std::size_t(3)); | ||
QVERIFY(regexpSet.find(QRegularExpression("a|b")) != regexpSet.end()); | ||
QVERIFY(regexpSet.find(QRegularExpression("a+b")) != regexpSet.end()); | ||
|
||
regexpSet.erase(QRegularExpression()); | ||
QCOMPARE(regexpSet.size(), std::size_t(3)); | ||
|
||
regexpSet.erase(QRegularExpression("a|b")); | ||
QCOMPARE(regexpSet.size(), std::size_t(2)); | ||
QVERIFY(regexpSet.find(QRegularExpression("a|b")) == regexpSet.end()); | ||
QVERIFY(regexpSet.find(QRegularExpression("a+b")) != regexpSet.end()); | ||
|
||
|
||
std::unordered_set<QUrl, KDToolBox::QtHasher<QUrl>> urlSet; | ||
urlSet.insert(QUrl("http://www.kdab.com")); | ||
urlSet.insert(QUrl("http://www.qt.io")); | ||
QCOMPARE(urlSet.size(), std::size_t(2)); | ||
|
||
urlSet.insert(QUrl("http://isocpp.org")); | ||
QCOMPARE(urlSet.size(), std::size_t(3)); | ||
|
||
QVERIFY(urlSet.find(QUrl("http://www.kdab.com")) != urlSet.end()); | ||
QVERIFY(urlSet.find(QUrl("http://www.google.com")) == urlSet.end()); | ||
} | ||
|
||
namespace TestNS | ||
{ | ||
struct Hashable { int i ; }; | ||
|
||
using QHashIntReturnType = decltype(qHash(0)); | ||
|
||
QHashIntReturnType qHash(Hashable h) noexcept | ||
{ | ||
return QT_PREPEND_NAMESPACE(qHash)(h.i); | ||
} | ||
|
||
struct HashableHiddenFriend | ||
{ | ||
int i; | ||
friend QHashIntReturnType qHash(HashableHiddenFriend h) noexcept | ||
{ | ||
return QT_PREPEND_NAMESPACE(qHash)(h.i); | ||
} | ||
}; | ||
} // namespace TestNS | ||
|
||
void tst_QtHasher::poison() | ||
{ | ||
Q_STATIC_ASSERT(std::is_default_constructible<KDToolBox::QtHasher<QString>>::value); | ||
Q_STATIC_ASSERT(std::is_default_constructible<KDToolBox::QtHasher<QUrl>>::value); | ||
Q_STATIC_ASSERT(std::is_default_constructible<KDToolBox::QtHasher<QFont>>::value); | ||
Q_STATIC_ASSERT(!std::is_default_constructible<KDToolBox::QtHasher<Foo>>::value); // no qHash(Foo) | ||
Q_STATIC_ASSERT(std::is_default_constructible<KDToolBox::QtHasher<TestNS::Hashable>>::value); // found through ADL | ||
Q_STATIC_ASSERT(std::is_default_constructible<KDToolBox::QtHasher<TestNS::HashableHiddenFriend>>::value); // found through ADL | ||
} | ||
|
||
QTEST_MAIN(tst_QtHasher) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
/**************************************************************************** | ||
** MIT License | ||
** | ||
** Copyright (C) 2019-2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com | ||
** | ||
** This file is part of KDToolBox (https://github.com/KDAB/KDToolBox). | ||
** | ||
** Permission is hereby granted, free of charge, to any person obtaining a copy | ||
** of this software and associated documentation files (the "Software"), to deal | ||
** in the Software without restriction, including without limitation the rights | ||
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
** copies of the Software, ** and to permit persons to whom the Software is | ||
** furnished to do so, subject to the following conditions: | ||
** | ||
** The above copyright notice and this permission notice (including the next paragraph) | ||
** shall be included in all copies or substantial portions of the Software. | ||
** | ||
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
** LIABILITY, WHETHER IN AN ACTION OF ** CONTRACT, TORT OR OTHERWISE, | ||
** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | ||
** DEALINGS IN THE SOFTWARE. | ||
****************************************************************************/ | ||
|
||
#include <QObject> | ||
|
||
class tst_QtHasher : public QObject | ||
{ | ||
Q_OBJECT | ||
|
||
private Q_SLOTS: | ||
void hash(); | ||
void poison(); | ||
}; |