Skip to content

Commit abaf953

Browse files
committed
Containers: std::hash specialization for [Mutable]String[View].
It never ceases to amaze me how bloated the STL headers are. Here I am shaving #include dependencies to go from 3k to 2kLOC, and then you casually include <functional> and suddenly you're almost 30kLOC heavier.
1 parent 58e07f0 commit abaf953

File tree

7 files changed

+150
-6
lines changed

7 files changed

+150
-6
lines changed

src/Corrade/Containers/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ set(CorradeContainers_HEADERS
6161
StridedArrayViewStl.h
6262
String.h
6363
StringStl.h
64+
StringStlHash.h
6465
StringStlView.h
6566
StringView.h
6667
Triple.h

src/Corrade/Containers/String.h

+7
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,13 @@ mitigate the conversion impact, it's recommended to convert
241241
@ref std::string_view instances to @ref BasicStringView "StringView" instead
242242
where possible.
243243
244+
Finally, the @ref Corrade/Containers/StringStlHash.h header provides a
245+
@ref std::hash specialization for @ref String, making it usable in
246+
@ref std::unordered_map and @ref std::unordered_set. It's *also* separate, due
247+
to dependency on @cpp #include <functional> @ce which is among the heaviest STL
248+
headers in existence, and which is only really needed when you deal with
249+
unordered containers.
250+
244251
@experimental
245252
*/
246253
class CORRADE_UTILITY_EXPORT String {

src/Corrade/Containers/StringStl.h

+4-4
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@
3232
3333
Including this header allows you to convert a
3434
@ref Corrade::Containers::String / @ref Corrade::Containers::StringView from
35-
and to @ref std::string. A separate
36-
@ref Corrade/Containers/StringStlView.h header provides compatibility with
37-
@ref std::string_view from C++17. See
38-
@ref Containers-String-stl "String STL compatibility" and
35+
and to @ref std::string. A separate @ref Corrade/Containers/StringStlView.h
36+
header provides compatibility with @ref std::string_view from C++17,
37+
@ref Corrade/Containers/StringStlHash.h then provides a @ref std::hash
38+
specialization. See @ref Containers-String-stl "String STL compatibility" and
3939
@ref Containers-BasicStringView-stl "StringView STL compatibility" for more
4040
information.
4141
*/
+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#ifndef Corrade_Containers_StringStlHash_h
2+
#define Corrade_Containers_StringStlHash_h
3+
/*
4+
This file is part of Corrade.
5+
6+
Copyright © 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016,
7+
2017, 2018, 2019, 2020, 2021, 2022
8+
Vladimír Vondruš <[email protected]>
9+
10+
Permission is hereby granted, free of charge, to any person obtaining a
11+
copy of this software and associated documentation files (the "Software"),
12+
to deal in the Software without restriction, including without limitation
13+
the rights to use, copy, modify, merge, publish, distribute, sublicense,
14+
and/or sell copies of the Software, and to permit persons to whom the
15+
Software is furnished to do so, subject to the following conditions:
16+
17+
The above copyright notice and this permission notice shall be included
18+
in all copies or substantial portions of the Software.
19+
20+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23+
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26+
DEALINGS IN THE SOFTWARE.
27+
*/
28+
29+
/** @file
30+
@brief STL @ref std::hash compatibility for @ref Corrade::Containers::String and @ref Corrade::Containers::BasicStringView "StringView"
31+
@m_since_latest
32+
33+
Including this header adds a @ref std::hash specialization for
34+
@ref Corrade::Containers::String / @ref Corrade::Containers::BasicStringView "StringView",
35+
making them usable in @ref std::unordered_map and @ref std::unordered_set. See
36+
@ref Containers-String-stl "String STL compatibility" and
37+
@ref Containers-BasicStringView-stl "StringView STL compatibility" for more
38+
information.
39+
*/
40+
41+
/* Alone, <functional> is relatively big (9kLOC on GCC 11 -std=c++11), but when
42+
combined with <unordered_map> (12k) it results in just 13k, so it's not like
43+
we'd gain anything by trying to find a forward declaration. On libc++ 11
44+
it's 22k for <functional> (!!) and 36k (!!!) for <unordered_map> either
45+
alone or together with <functional>. So even though the numbers are QUITE
46+
HORRENDOUS, a forward declaration wouldn't help there either. On GCC 11 and
47+
-std=c++20 the size goes up to 30k as well. Haha.
48+
49+
Compared to these, I don't feel like bothering to optimize my side of the
50+
include chain, even though the MurmurHash2 include could potentially get
51+
deinlined. */
52+
#include <functional>
53+
54+
#include "Corrade/Containers/String.h"
55+
#include "Corrade/Containers/StringView.h"
56+
#include "Corrade/Utility/MurmurHash2.h"
57+
58+
/* Listing these namespaces doesn't add anything to the docs, so don't */
59+
#ifndef DOXYGEN_GENERATING_OUTPUT
60+
namespace std {
61+
62+
template<> struct hash<Corrade::Containers::StringView> {
63+
std::size_t operator()(Corrade::Containers::StringView key) const {
64+
const Corrade::Utility::MurmurHash2 hash;
65+
const Corrade::Utility::HashDigest<sizeof(std::size_t)> digest = hash(key.data(), key.size());
66+
return *reinterpret_cast<const std::size_t*>(digest.byteArray());
67+
}
68+
};
69+
70+
template<> struct hash<Corrade::Containers::MutableStringView>: hash<Corrade::Containers::StringView> {};
71+
72+
template<> struct hash<Corrade::Containers::String> {
73+
std::size_t operator()(const Corrade::Containers::String& key) const {
74+
const Corrade::Utility::MurmurHash2 hash;
75+
const Corrade::Utility::HashDigest<sizeof(std::size_t)> digest = hash(key.data(), key.size());
76+
return *reinterpret_cast<const std::size_t*>(digest.byteArray());
77+
}
78+
};
79+
80+
}
81+
#endif
82+
83+
#endif

src/Corrade/Containers/StringStlView.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ Including this header allows you to convert a
3434
@ref Corrade::Containers::String / @ref Corrade::Containers::StringView from
3535
and to a C++17 @ref std::string_view. A separate
3636
@ref Corrade/Containers/StringStl.h header provides compatibility with
37-
@ref std::string. See
37+
@ref std::string, @ref Corrade/Containers/StringStlHash.h then provides a
38+
@ref std::hash specialization. See
3839
@ref Containers-String-stl "String STL compatibility" and
3940
@ref Containers-BasicStringView-stl "StringView STL compatibility" for more
4041
information.

src/Corrade/Containers/StringView.h

+7
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,13 @@ no possibility to create a @ref MutableStringView out of it. Because
259259
neither @ref StringViewFlag::NullTerminated nor @ref StringViewFlag::Global is
260260
set in a @ref StringView converted from it.
261261
262+
Finally, the @ref Corrade/Containers/StringStlHash.h header provides a
263+
@ref std::hash specialization for @ref StringView / @ref MutableStringView,
264+
making it usable in @ref std::unordered_map and @ref std::unordered_set. It's
265+
* *also* separate, due to dependency on @cpp #include <functional> @ce which is
266+
among the heaviest STL headers in existence, and which is only really needed
267+
when you deal with unordered containers.
268+
262269
@experimental
263270
*/
264271
/* All member functions are const because the view doesn't own the data */

src/Corrade/Containers/Test/StringStlTest.cpp

+46-1
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,12 @@
2424
DEALINGS IN THE SOFTWARE.
2525
*/
2626

27+
#include <unordered_map>
28+
2729
#include "Corrade/Containers/StringStl.h"
30+
#include "Corrade/Containers/StringStlHash.h"
2831
#include "Corrade/TestSuite/Tester.h"
32+
#include "Corrade/TestSuite/Compare/Numeric.h"
2933
#include "Corrade/Utility/DebugStl.h"
3034

3135
namespace Corrade { namespace Containers { namespace Test { namespace {
@@ -46,6 +50,8 @@ struct StringStlTest: TestSuite::Tester {
4650
void convertViewFromStlStringEmpty();
4751
void convertMutableViewFromStlString();
4852
void convertMutableViewFromStlStringEmpty();
53+
54+
template<class T> void hash();
4955
};
5056

5157
StringStlTest::StringStlTest() {
@@ -61,7 +67,11 @@ StringStlTest::StringStlTest() {
6167
&StringStlTest::convertViewFromStlString,
6268
&StringStlTest::convertViewFromStlStringEmpty,
6369
&StringStlTest::convertMutableViewFromStlString,
64-
&StringStlTest::convertMutableViewFromStlStringEmpty});
70+
&StringStlTest::convertMutableViewFromStlStringEmpty,
71+
72+
&StringStlTest::hash<StringView>,
73+
&StringStlTest::hash<MutableStringView>,
74+
&StringStlTest::hash<String>});
6575
}
6676

6777
using namespace Literals;
@@ -147,6 +157,41 @@ void StringStlTest::convertMutableViewFromStlStringEmpty() {
147157
CORRADE_COMPARE(b.data(), static_cast<const void*>(a.data()));
148158
}
149159

160+
template<class> struct NameTraits;
161+
template<> struct NameTraits<StringView> {
162+
static const char* name() { return "StringView"; }
163+
};
164+
template<> struct NameTraits<MutableStringView> {
165+
static const char* name() { return "MutableStringView"; }
166+
};
167+
template<> struct NameTraits<String> {
168+
static const char* name() { return "String"; }
169+
};
170+
171+
template<class T> void StringStlTest::hash() {
172+
setTestCaseTemplateName(NameTraits<T>::name());
173+
174+
char hello[] = "hello";
175+
char olleh[] = "olleh";
176+
177+
std::unordered_map<T, int> map;
178+
map.emplace(hello, 3);
179+
map.emplace(olleh, 7);
180+
CORRADE_COMPARE(map[hello], 3);
181+
CORRADE_COMPARE(map[olleh], 7);
182+
183+
/* Verify the hash function is non-trivial */
184+
CORRADE_COMPARE_AS(std::hash<T>{}(hello), std::hash<T>{}({}),
185+
TestSuite::Compare::NotEqual);
186+
CORRADE_COMPARE_AS(std::hash<T>{}(olleh), std::hash<T>{}({}),
187+
TestSuite::Compare::NotEqual);
188+
CORRADE_COMPARE_AS(std::hash<T>{}(hello), std::hash<T>{}(olleh),
189+
TestSuite::Compare::NotEqual);
190+
191+
/* And also non-random and not depending on the data pointer */
192+
CORRADE_COMPARE(std::hash<T>{}(hello), std::hash<Containers::StringView>{}("hello!"_s.exceptSuffix(1)));
193+
}
194+
150195
}}}}
151196

152197
CORRADE_TEST_MAIN(Corrade::Containers::Test::StringStlTest)

0 commit comments

Comments
 (0)