Skip to content

Commit

Permalink
QtHasher: a hasher for Qt datatypes inside STL unordered containers
Browse files Browse the repository at this point in the history
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
dangelog committed Sep 23, 2020
1 parent 5390440 commit 6b0bf6c
Show file tree
Hide file tree
Showing 8 changed files with 298 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ Tools
A universal, safe, zero-allocation string splitter.
- [Single Shot Connect](https://github.com/KDAB/KDToolBox/tree/master/qt/singleshot_connect)
A header only version of QObject::connect, that establishes a single shot connection.
- [Qt Hasher](https://github.com/KDAB/KDToolBox/tree/master/qt/qt_hasher)
A header-only hasher object for using Qt types together with `unordered_map`, `unordered_set`, etc.

Other code snippets
===================
Expand Down
1 change: 1 addition & 0 deletions qt/qt.pro
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ SUBDIRS += \
messagehandler \
model_view \
qml \
qt_hasher \
ui_watchdog \
tabWindow \
singleshot_connect \
Expand Down
29 changes: 29 additions & 0 deletions qt/qt_hasher/README.md
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.

90 changes: 90 additions & 0 deletions qt/qt_hasher/qt_hasher.h
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
3 changes: 3 additions & 0 deletions qt/qt_hasher/qt_hasher.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
TEMPLATE = subdirs
SUBDIRS += \
test \
11 changes: 11 additions & 0 deletions qt/qt_hasher/test/test.pro
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
126 changes: 126 additions & 0 deletions qt/qt_hasher/test/tst_qt_hasher.cpp
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)
36 changes: 36 additions & 0 deletions qt/qt_hasher/test/tst_qt_hasher.h
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();
};

0 comments on commit 6b0bf6c

Please sign in to comment.