├── tests ├── Makefile └── tests.cpp ├── .travis.yml ├── LICENSE ├── README.md ├── .clang-format └── include ├── Hash.hpp └── HashMap.hpp /tests/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | $(CXX) -std=c++14 -O0 -g -I../include -Wall -Werror tests.cpp -o tests 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: jammy 2 | language: cpp 3 | 4 | compiler: 5 | - clang 6 | - gcc 7 | 8 | before_install: 9 | - sudo apt-get update -qq 10 | 11 | script: 12 | - CXX=$COMPILER make -C tests 13 | - ./tests/tests 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2012, Yuki 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Compile-Time Map - Static Hash Map at Compile Time 2 | 3 | [![Build Status](https://travis-ci.com/ChoppinBlockParty/ctm.svg?branch=master)](https://travis-ci.com/ChoppinBlockParty/ctm) 4 | 5 | Allows to have a hash map built at compile time. The map could be used to perform runtime as well 6 | as compile-time lookups. 7 | 8 | ## Install 9 | 10 | * C++-14 supporting compiler 11 | * Header-only usage without any external dependencies, except the Standard Library. 12 | 13 | ## Usage 14 | 15 | More examples could be found in `tests/tests.cpp`. 16 | 17 | ```cpp 18 | class JsonSerializable { 19 | public: 20 | virtual ~JsonSerializable() {} 21 | 22 | static JsonSerializable* createFromJson(JsonObject const& json_object); 23 | }; 24 | 25 | class Holy : public JsonSerializable { 26 | public: 27 | static JsonSerializable* createFromJson(JsonObject const& json_object) { 28 | auto holy = new Holy; 29 | // Fill `holy` with data from `json_object` 30 | return holy; 31 | } 32 | }; 33 | class Moly : public JsonSerializable { 34 | public: 35 | static JsonSerializable* createFromJson(JsonObject const& json_object) { 36 | auto moly = new Moly; 37 | // Fill `moly` with data from `json_object` 38 | return moly; 39 | } 40 | }; 41 | class Miny : public JsonSerializable { 42 | public: 43 | static JsonSerializable* createFromJson(JsonObject const& json_object) { 44 | auto miny = new Miny; 45 | // Fill `miny` with data from `json_object` 46 | return miny; 47 | } 48 | }; 49 | class Moe : public JsonSerializable { 50 | public: 51 | static JsonSerializable* createFromJson(JsonObject const& json_object) { 52 | auto moe = new Moe; 53 | // Fill `moe` with data from `json_object` 54 | return moe; 55 | } 56 | }; 57 | 58 | JsonSerializable* JsonSerializable::createFromJson(JsonObject const& json_object) { 59 | constexpr auto spec = ctm::makeHashMapSpec(std::make_tuple("Holy", &Holy::createFromJson), 60 | std::make_tuple("Moly", &Moly::createFromJson), 61 | std::make_tuple("Miny", &Miny::createFromJson), 62 | std::make_tuple("Moe", &Moe::createFromJson)); 63 | static constexpr auto map = ctm::HashMap::make(spec); 67 | auto factory_function = map[json_object.at("type")]; 68 | if (factory_function == nullptr) 69 | return nullptr; 70 | return factory_function(json_object); 71 | } 72 | ``` 73 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | Language: Cpp 2 | DisableFormat: false 3 | AccessModifierOffset: -4 4 | 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlinesLeft: false 9 | AlignOperands: false 10 | AlignTrailingComments: false 11 | 12 | AllowAllParametersOfDeclarationOnNextLine: true 13 | AllowShortBlocksOnASingleLine: false 14 | AllowShortCaseLabelsOnASingleLine: false 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AllowShortFunctionsOnASingleLine: All 18 | 19 | AlwaysBreakAfterDefinitionReturnType: None 20 | AlwaysBreakTemplateDeclarations: true 21 | AlwaysBreakBeforeMultilineStrings: false 22 | 23 | BinPackArguments: false 24 | BinPackParameters: false 25 | 26 | BraceWrapping: 27 | AfterClass: true 28 | AfterControlStatement: true 29 | AfterEnum: true 30 | AfterFunction: true 31 | AfterNamespace: true 32 | AfterObjCDeclaration: true 33 | AfterStruct: true 34 | AfterUnion: true 35 | BeforeCatch: true 36 | BeforeElse: true 37 | IndentBraces: true 38 | 39 | BreakBeforeBinaryOperators: true 40 | BreakBeforeBraces: Attach 41 | BreakBeforeTernaryOperators: true 42 | BreakConstructorInitializersBeforeComma: false 43 | BreakStringLiterals: false 44 | 45 | ColumnLimit: 90 46 | ConstructorInitializerIndentWidth: 4 47 | ContinuationIndentWidth: 4 48 | Standard: Cpp11 49 | IndentWidth: 4 50 | TabWidth: 4 51 | UseTab: Never 52 | 53 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 54 | Cpp11BracedListStyle: true 55 | 56 | IndentCaseLabels: false 57 | IndentWrappedFunctionNames: false 58 | IndentFunctionDeclarationAfterType: false 59 | NamespaceIndentation: None 60 | 61 | DerivePointerAlignment: false 62 | ExperimentalAutoDetectBinPacking: false 63 | MaxEmptyLinesToKeep: 1 64 | KeepEmptyLinesAtTheStartOfBlocks: true 65 | ObjCSpaceAfterProperty: false 66 | ObjCSpaceBeforeProtocolList: true 67 | 68 | PenaltyBreakBeforeFirstCallParameter: 19 69 | PenaltyBreakComment: 300 70 | PenaltyBreakString: 1000 71 | PenaltyBreakFirstLessLess: 120 72 | PenaltyExcessCharacter: 1000000 73 | PenaltyReturnTypeOnItsOwnLine: 60 74 | 75 | SpaceAfterCStyleCast: false 76 | SpacesBeforeTrailingComments: 1 77 | SpacesInParentheses: false 78 | SpacesInAngles: false 79 | SpaceInEmptyParentheses: false 80 | SpacesInCStyleCastParentheses: false 81 | SpacesInContainerLiterals: true 82 | SpaceBeforeAssignmentOperators: true 83 | CommentPragmas: '^ IWYU pragma:' 84 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 85 | SpaceBeforeParens: ControlStatements 86 | SpacesInSquareBrackets: false 87 | 88 | PointerAlignment: Left 89 | ReflowComments: false 90 | SortIncludes: true 91 | FixNamespaceComments: false 92 | 93 | -------------------------------------------------------------------------------- /include/Hash.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef CTM_BYTES_HASH_ALGORITHM_DEFAULT 4 | #define CTM_BYTES_HASH_ALGORITHM_DEFAULT FnvBytesHash 5 | #endif 6 | 7 | #ifndef CTM_BYTES_HASH_ALGORITHM_INTEGER_SIZE_DEFAULT 8 | #define CTM_BYTES_HASH_ALGORITHM_INTEGER_SIZE_DEFAULT 4 9 | #endif 10 | 11 | #include 12 | #include 13 | 14 | namespace ctm { 15 | namespace internal { 16 | template 17 | struct HashBase { 18 | using ResultType = TResult; 19 | using ArgumentType = TArg; 20 | }; 21 | } 22 | 23 | template 24 | struct Hash; 25 | 26 | template 27 | struct Hash : internal::HashBase { 28 | constexpr std::size_t operator()(T* ptr) const noexcept { 29 | return reinterpret_cast(ptr); 30 | } 31 | }; 32 | 33 | #define CTM_TRIVIAL_HASH_DEFINITION(T) \ 34 | template <> \ 35 | struct Hash : internal::HashBase { \ 36 | constexpr std::size_t operator()(T value) const noexcept { \ 37 | return static_cast(value); \ 38 | } \ 39 | }; 40 | 41 | CTM_TRIVIAL_HASH_DEFINITION(bool) 42 | CTM_TRIVIAL_HASH_DEFINITION(char) 43 | CTM_TRIVIAL_HASH_DEFINITION(signed char) 44 | CTM_TRIVIAL_HASH_DEFINITION(unsigned char) 45 | CTM_TRIVIAL_HASH_DEFINITION(wchar_t) 46 | CTM_TRIVIAL_HASH_DEFINITION(char16_t) 47 | CTM_TRIVIAL_HASH_DEFINITION(char32_t) 48 | CTM_TRIVIAL_HASH_DEFINITION(short) 49 | CTM_TRIVIAL_HASH_DEFINITION(int) 50 | CTM_TRIVIAL_HASH_DEFINITION(long) 51 | CTM_TRIVIAL_HASH_DEFINITION(long long) 52 | CTM_TRIVIAL_HASH_DEFINITION(unsigned short) 53 | CTM_TRIVIAL_HASH_DEFINITION(unsigned int) 54 | CTM_TRIVIAL_HASH_DEFINITION(unsigned long) 55 | CTM_TRIVIAL_HASH_DEFINITION(unsigned long long) 56 | 57 | #undef CTM_TRIVIAL_HASH_DEFINITION 58 | 59 | namespace internal { 60 | constexpr std::size_t convertCharsToSizet(char const* chars) { 61 | std::size_t result = 0; 62 | constexpr auto size = sizeof(std::size_t); 63 | auto chars_end = chars + size; 64 | for (; chars != chars_end; ++chars) { 65 | auto ch = static_cast(*chars); 66 | result >>= 8; 67 | ch <<= 8 * (size - 1); 68 | result |= ch; 69 | } 70 | return result; 71 | } 72 | 73 | // Loads n bytes, where 1 <= n < 8. 74 | constexpr std::size_t loadBytes(char const* p, int n) { 75 | std::size_t result = 0; 76 | --n; 77 | do 78 | result = (result << 8) + static_cast(p[n]); 79 | while (--n >= 0); 80 | return result; 81 | } 82 | 83 | constexpr std::size_t shiftMix(std::size_t v) { return v ^ (v >> 47); } 84 | 85 | template 86 | constexpr std::size_t 87 | hashBytesWithFnv(char const* ptr, std::size_t size, std::size_t hash) { 88 | switch (N) { 89 | case 4: { 90 | // Implementation of FNV hash for 32-bit std::size_t. 91 | 92 | for (; size; --size) { 93 | hash ^= static_cast(*ptr++); 94 | hash *= static_cast(16777619UL); 95 | } 96 | return hash; 97 | } 98 | case 8: { 99 | // Implementation of FNV hash for 64-bit std::size_t. 100 | 101 | for (; size; --size) { 102 | hash ^= static_cast(*ptr++); 103 | hash *= static_cast(1099511628211ULL); 104 | } 105 | return hash; 106 | } 107 | default: { 108 | // Dummy hash implementation for unusual sizeof(std::size_t). 109 | 110 | for (; size; --size) 111 | hash = (hash * 131) + *ptr++; 112 | return hash; 113 | } 114 | } 115 | } 116 | 117 | template 118 | constexpr std::size_t 119 | hashBytesWithMurmur(char const* ptr, std::size_t size, std::size_t seed) { 120 | switch (N) { 121 | case 4: { 122 | // Implementation of Murmur hash for 32-bit std::size_t. 123 | 124 | constexpr std::size_t m = 0x5bd1e995; 125 | std::size_t hash = seed ^ size; 126 | auto buf = ptr; 127 | 128 | // Mix 4 bytes at a time into the hash. 129 | while (size >= 4) { 130 | std::size_t k = convertCharsToSizet(buf); 131 | k *= m; 132 | k ^= k >> 24; 133 | k *= m; 134 | hash *= m; 135 | hash ^= k; 136 | buf += 4; 137 | size -= 4; 138 | } 139 | 140 | // Handle the last few bytes of the input array. 141 | switch (size) { 142 | case 3: 143 | hash ^= static_cast(buf[2]) << 16; 144 | case 2: 145 | hash ^= static_cast(buf[1]) << 8; 146 | case 1: 147 | hash ^= static_cast(buf[0]); 148 | hash *= m; 149 | }; 150 | 151 | // Do a few final mixes of the hash. 152 | hash ^= hash >> 13; 153 | hash *= m; 154 | hash ^= hash >> 15; 155 | return hash; 156 | } 157 | case 8: { 158 | // Implementation of Murmur hash for 64-bit std::size_t. 159 | 160 | constexpr std::size_t const mul 161 | = (((std::size_t)0xc6a4a793UL) << 32UL) + (std::size_t)0x5bd1e995UL; 162 | 163 | // Remove the bytes not divisible by the sizeof(std::size_t). This 164 | // allows the main loop to process the data as 64-bit integers. 165 | int const len_aligned = size & ~0x7; 166 | char const* const end = ptr + len_aligned; 167 | std::size_t hash = seed ^ (size * mul); 168 | for (char const* p = ptr; p != end; p += 8) { 169 | std::size_t const data = shiftMix(convertCharsToSizet(p) * mul) * mul; 170 | hash ^= data; 171 | hash *= mul; 172 | } 173 | if ((size & 0x7) != 0) { 174 | std::size_t const data = loadBytes(end, size & 0x7); 175 | hash ^= data; 176 | hash *= mul; 177 | } 178 | hash = shiftMix(hash) * mul; 179 | hash = shiftMix(hash); 180 | return hash; 181 | } 182 | default: { 183 | // Dummy hash implementation for unusual sizeof(std::size_t). 184 | 185 | std::size_t hash = seed; 186 | for (; size; --size) 187 | hash = (hash * 131) + *ptr++; 188 | return hash; 189 | } 190 | } 191 | } 192 | } 193 | 194 | template 195 | struct FnvBytesHash { 196 | constexpr static std::size_t hash(char const* ptr, 197 | std::size_t size, 198 | std::size_t seed 199 | = static_cast(2166136261UL)) { 200 | return internal::hashBytesWithFnv(ptr, size, seed); 201 | } 202 | }; 203 | 204 | template 205 | struct MurmurBytesHash { 206 | constexpr static std::size_t hash(char const* ptr, 207 | std::size_t size, 208 | std::size_t seed 209 | = static_cast(0xc70f6907UL)) { 210 | return internal::hashBytesWithMurmur(ptr, size, seed); 211 | } 212 | }; 213 | 214 | struct BytesHash 215 | : CTM_BYTES_HASH_ALGORITHM_DEFAULT {}; 216 | 217 | template <> 218 | struct Hash : internal::HashBase { 219 | constexpr std::size_t operator()(char const* chars) const noexcept { 220 | auto chars_begin = chars; 221 | while (*chars++ != '\0') { 222 | } 223 | return BytesHash::hash(chars_begin, 224 | static_cast(chars - chars_begin - 1)); 225 | } 226 | 227 | constexpr std::size_t operator()(char const* chars, std::size_t size) const noexcept { 228 | return BytesHash::hash(chars, size); 229 | } 230 | }; 231 | 232 | template 233 | struct Hash : internal::HashBase { 234 | constexpr std::size_t operator()(char const (&chars)[N]) const noexcept { 235 | return BytesHash::hash(chars, N - 1); 236 | } 237 | }; 238 | 239 | template <> 240 | struct Hash : internal::HashBase { 241 | std::size_t operator()(std::string const& string) const noexcept { 242 | return BytesHash::hash(string.data(), string.length()); 243 | } 244 | }; 245 | } 246 | -------------------------------------------------------------------------------- /tests/tests.cpp: -------------------------------------------------------------------------------- 1 | #ifdef NDEBUG 2 | #undef NDEBUG 3 | #endif 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace ctm; 12 | 13 | int f1() { return 1; } 14 | 15 | int f2() { return 2; } 16 | 17 | int f3() { return 3; } 18 | 19 | int f4() { return 4; } 20 | 21 | int f5() { return 5; } 22 | 23 | int f6() { return 5; } 24 | 25 | int fDuplicate() { return 999; } 26 | 27 | constexpr auto key4 = "ac"; 28 | 29 | constexpr auto makeTestMap0010() { 30 | constexpr auto data = makeHashMapSpec(1.0, 31 | 0.5, 32 | std::make_tuple("bsd", f1), 33 | std::make_tuple("holy", f2), 34 | std::make_tuple("", f3), 35 | std::make_tuple("duplicate", f4), 36 | std::make_tuple(key4, f5), 37 | std::make_tuple("duplicate", fDuplicate), 38 | std::make_tuple("ab", f6)); 39 | return HashMap::make(data); 43 | } 44 | 45 | void test0010() { 46 | constexpr auto map = makeTestMap0010(); 47 | 48 | static_assert(sizeof(map) == 10 * sizeof(std::tuple), 49 | "Invalid sizeof"); 50 | 51 | static_assert(std::is_same::value, 52 | "Invalid key type"); 53 | static_assert(std::is_same::value, 54 | "Invalid value type"); 55 | static_assert( 56 | std::is_same>::value, 57 | "Invalid pair type"); 58 | static_assert(map.bucketSize() == 1, "Invalid bucket size"); 59 | static_assert(map.bucketCount() == 10, "Invalid bucket count"); 60 | static_assert(map.size() == 6, "Invalid size"); 61 | assert(map.bucketSize() == 1); 62 | assert(map.bucketCount() == 10); 63 | assert(map.size() == 6); 64 | 65 | static_assert(map["bsd"] == f1, "Invalid value"); 66 | static_assert(map["holy"] == f2, "Invalid value"); 67 | static_assert(map[""] == f3, "Invalid value"); 68 | static_assert(map["duplicate"] == f4, "Invalid value"); 69 | static_assert(map["ac"] == f5, "Invalid value"); 70 | static_assert(map["ab"] == f6, "Invalid value"); 71 | static_assert(map["unknown"] == nullptr, "Invalid value"); 72 | assert(map["bsd"] == f1); 73 | assert(map["holy"] == f2); 74 | assert(map[""] == f3); 75 | assert(map["duplicate"] == f4); 76 | assert(map["ac"] == f5); 77 | assert(map["ab"] == f6); 78 | assert(map["unknown"] == nullptr); 79 | auto key = std::string("holy"); 80 | assert(map[key] == f2); 81 | assert(map[key]() == 2); 82 | key = "moly"; 83 | assert(map[key] == nullptr); 84 | 85 | bool has_occured = false; 86 | for (auto const& bucket : map) { 87 | for (auto const& pair : bucket) { 88 | if (pair.first && pair.first == "ac") { 89 | has_occured = true; 90 | assert(pair.first.chars() == key4); 91 | } 92 | } 93 | } 94 | assert(has_occured); 95 | } 96 | 97 | int fTest00201(int value) { return 1 * value; } 98 | 99 | int fTest00202(int value) { return 2 * value; } 100 | 101 | int fTest00203(int value) { return 3 * value; } 102 | 103 | constexpr auto makeTestMap0020() { 104 | constexpr auto data 105 | = makeHashMapSpec(1.0, 106 | 0.5, 107 | std::make_tuple("Eeny", fTest00201, fTest00202, fTest00203), 108 | std::make_tuple("meeny", fTest00202, fTest00201, fTest00203), 109 | std::make_tuple("miny", fTest00203, fTest00202, fTest00201), 110 | std::make_tuple("moe", fTest00201, fTest00203, fTest00202)); 111 | return HashMap::make(data); 115 | } 116 | 117 | void test0020() { 118 | constexpr auto map = makeTestMap0020(); 119 | 120 | static_assert(sizeof(map) == 5 * sizeof(std::tuple), 121 | "Invalid sizeof"); 122 | 123 | static_assert(std::is_same::value, 124 | "Invalid key type"); 125 | static_assert( 126 | std::is_same>::value, 128 | "Invalid value type"); 129 | static_assert( 130 | std::is_same< 131 | decltype(map)::PairType, 132 | std::pair>>::value, 134 | "Invalid pair type"); 135 | static_assert(map.bucketSize() == 1, "Invalid bucket size"); 136 | static_assert(map.bucketCount() == 5, "Invalid bucket count"); 137 | static_assert(map.size() == 4, "Invalid size"); 138 | assert(map.bucketSize() == 1); 139 | assert(map.bucketCount() == 5); 140 | assert(map.size() == 4); 141 | 142 | static_assert(std::get<0>(map["Eeny"]) == fTest00201, "Invalid value"); 143 | static_assert(std::get<1>(map["Eeny"]) == fTest00202, "Invalid value"); 144 | static_assert(std::get<2>(map["Eeny"]) == fTest00203, "Invalid value"); 145 | static_assert(std::get<0>(map["meeny"]) == fTest00202, "Invalid value"); 146 | static_assert(std::get<1>(map["meeny"]) == fTest00201, "Invalid value"); 147 | static_assert(std::get<2>(map["meeny"]) == fTest00203, "Invalid value"); 148 | static_assert(std::get<0>(map["miny"]) == fTest00203, "Invalid value"); 149 | static_assert(std::get<1>(map["miny"]) == fTest00202, "Invalid value"); 150 | static_assert(std::get<2>(map["miny"]) == fTest00201, "Invalid value"); 151 | static_assert(std::get<0>(map["moe"]) == fTest00201, "Invalid value"); 152 | static_assert(std::get<1>(map["moe"]) == fTest00203, "Invalid value"); 153 | static_assert(std::get<2>(map["moe"]) == fTest00202, "Invalid value"); 154 | static_assert(std::get<0>(map["unknown"]) == nullptr, "Invalid value"); 155 | static_assert(std::get<1>(map["unknown"]) == nullptr, "Invalid value"); 156 | static_assert(std::get<2>(map["unknown"]) == nullptr, "Invalid value"); 157 | 158 | assert(std::get<0>(map["Eeny"]) == fTest00201); 159 | assert(std::get<1>(map["Eeny"]) == fTest00202); 160 | assert(std::get<2>(map["Eeny"]) == fTest00203); 161 | assert(std::get<0>(map["meeny"]) == fTest00202); 162 | assert(std::get<1>(map["meeny"]) == fTest00201); 163 | assert(std::get<2>(map["meeny"]) == fTest00203); 164 | assert(std::get<0>(map["miny"]) == fTest00203); 165 | assert(std::get<1>(map["miny"]) == fTest00202); 166 | assert(std::get<2>(map["miny"]) == fTest00201); 167 | assert(std::get<0>(map["moe"]) == fTest00201); 168 | assert(std::get<1>(map["moe"]) == fTest00203); 169 | assert(std::get<2>(map["moe"]) == fTest00202); 170 | 171 | assert(std::get<0>(map["Eeny"])(10) == 10); 172 | assert(std::get<1>(map["Eeny"])(10) == 20); 173 | assert(std::get<2>(map["Eeny"])(10) == 30); 174 | assert(std::get<0>(map["meeny"])(10) == 20); 175 | assert(std::get<1>(map["meeny"])(10) == 10); 176 | assert(std::get<2>(map["meeny"])(10) == 30); 177 | assert(std::get<0>(map["miny"])(10) == 30); 178 | assert(std::get<1>(map["miny"])(10) == 20); 179 | assert(std::get<2>(map["miny"])(10) == 10); 180 | assert(std::get<0>(map["moe"])(10) == 10); 181 | assert(std::get<1>(map["moe"])(10) == 30); 182 | assert(std::get<2>(map["moe"])(10) == 20); 183 | assert(std::get<0>(map["unknown"]) == nullptr); 184 | assert(std::get<1>(map["unknown"]) == nullptr); 185 | assert(std::get<2>(map["unknown"]) == nullptr); 186 | } 187 | 188 | constexpr auto makeTestMap0030() { 189 | constexpr auto spec 190 | = makeHashMapSpec(4.0, 191 | 2.0, 192 | std::make_tuple("Catch", 'c'), 193 | std::make_tuple("a tiger", 'a'), 194 | std::make_tuple("by the toe", 'b'), 195 | std::make_tuple("If he hollers, let him go", 'i')); 196 | 197 | return HashMap::make(spec); 201 | } 202 | 203 | void test0030() { 204 | constexpr auto map = makeTestMap0030(); 205 | 206 | static_assert(sizeof(map) == 6 * sizeof(std::tuple), "Invalid sizeof"); 207 | static_assert(map.bucketSize() == 3, "Invalid bucket size"); 208 | static_assert(map.bucketCount() == 2, "Invalid bucket count"); 209 | static_assert(map.size() == 4, "Invalid size"); 210 | assert(map.bucketSize() == 3); 211 | assert(map.bucketCount() == 2); 212 | assert(map.size() == 4); 213 | 214 | static_assert(map["Catch"] == 'c', "Invalid value"); 215 | static_assert(map["a tiger"] == 'a', "Invalid value"); 216 | static_assert(map["by the toe"] == 'b', "Invalid value"); 217 | static_assert(map["If he hollers, let him go"] == 'i', "Invalid value"); 218 | static_assert(map["unknown"] == '\0', "Invalid value"); 219 | assert(map["Catch"] == 'c'); 220 | assert(map["a tiger"] == 'a'); 221 | assert(map["by the toe"] == 'b'); 222 | assert(map["If he hollers, let him go"] == 'i'); 223 | assert(map["unknown"] == '\0'); 224 | } 225 | 226 | constexpr auto makeTestMap0040() { 227 | constexpr auto data = makeHashMapSpec(1.0, 228 | 0.5, 229 | std::make_tuple(4096, 1, 'q'), 230 | std::make_tuple(2048, 2, 'w'), 231 | std::make_tuple(8192, 3, 'e'), 232 | std::make_tuple(1024, 4, 'r')); 233 | 234 | return HashMap::make(data); 238 | } 239 | 240 | void test0040() { 241 | constexpr auto map = makeTestMap0040(); 242 | static_assert(sizeof(map) == 5 * sizeof(std::tuple), 243 | "Invalid sizeof"); 244 | static_assert(map.bucketSize() == 1, "Invalid bucket size"); 245 | static_assert(map.bucketCount() == 5, "Invalid bucket count"); 246 | static_assert(map.size() == 4, "Invalid size"); 247 | assert(map.bucketSize() == 1); 248 | assert(map.bucketCount() == 5); 249 | assert(map.size() == 4); 250 | 251 | static_assert(std::get<0>(map[4096]) == 1, "Invalid value"); 252 | static_assert(std::get<1>(map[4096]) == 'q', "Invalid value"); 253 | static_assert(std::get<0>(map[2048]) == 2, "Invalid value"); 254 | static_assert(std::get<1>(map[2048]) == 'w', "Invalid value"); 255 | static_assert(std::get<0>(map[8192]) == 3, "Invalid value"); 256 | static_assert(std::get<1>(map[8192]) == 'e', "Invalid value"); 257 | static_assert(std::get<0>(map[1024]) == 4, "Invalid value"); 258 | static_assert(std::get<1>(map[1024]) == 'r', "Invalid value"); 259 | static_assert(std::get<0>(map[0]) == 0, "Invalid value"); 260 | static_assert(std::get<1>(map[0]) == '\0', "Invalid value"); 261 | 262 | assert(std::get<0>(map[4096]) == 1); 263 | assert(std::get<1>(map[4096]) == 'q'); 264 | assert(std::get<0>(map[2048]) == 2); 265 | assert(std::get<1>(map[2048]) == 'w'); 266 | assert(std::get<0>(map[8192]) == 3); 267 | assert(std::get<1>(map[8192]) == 'e'); 268 | assert(std::get<0>(map[1024]) == 4); 269 | assert(std::get<1>(map[1024]) == 'r'); 270 | assert(std::get<0>(map[0]) == 0); 271 | assert(std::get<1>(map[0]) == '\0'); 272 | } 273 | 274 | int main() { 275 | test0010(); 276 | test0020(); 277 | test0030(); 278 | test0040(); 279 | return 0; 280 | } 281 | -------------------------------------------------------------------------------- /include/HashMap.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "Hash.hpp" 10 | 11 | namespace ctm { 12 | template 13 | struct Array { 14 | using value_type = T; 15 | 16 | constexpr std::size_t size() const { return N; } 17 | 18 | constexpr T const* begin() const { return data; } 19 | 20 | constexpr T const* end() const { return data + N; } 21 | 22 | constexpr T* begin() { return data; } 23 | 24 | constexpr T* end() { return data + N; } 25 | 26 | constexpr T const& operator[](std::size_t index) const { return data[index]; } 27 | 28 | constexpr T& operator[](std::size_t index) { return data[index]; } 29 | 30 | T data[N]; 31 | }; 32 | 33 | class String { 34 | public: 35 | constexpr String() : _ptr(nullptr), _size(0) {} 36 | 37 | template 38 | constexpr String(char const (&ptr)[N]) : _ptr(ptr), _size(N - 1) {} 39 | 40 | String(std::string const& string) : _ptr(string.data()), _size(string.size()) {} 41 | 42 | constexpr String(char const* ptr) : _ptr(ptr), _size(0) { 43 | while ((*ptr++) != '\0') { 44 | } 45 | _size = static_cast(ptr - _ptr - 1); 46 | } 47 | 48 | constexpr char const* chars() const { return _ptr; } 49 | 50 | constexpr std::size_t size() const { return _size; } 51 | 52 | constexpr bool empty() const { return _size == 0; } 53 | 54 | constexpr std::size_t hash() const { return Hash()(_ptr, _size); } 55 | 56 | std::string toStdString() const { return std::string(_ptr, _size); } 57 | 58 | constexpr bool operator==(String const& other) const { 59 | if (_size != other._size) 60 | return false; 61 | for (auto lhs_ptr = _ptr, rhs_ptr = other._ptr; *lhs_ptr != '\0'; 62 | ++lhs_ptr, ++rhs_ptr) { 63 | if (*lhs_ptr != *rhs_ptr) 64 | return false; 65 | } 66 | return true; 67 | } 68 | 69 | constexpr bool operator==(char const* chars) const { 70 | auto lhs_ptr = _ptr, rhs_ptr = chars; 71 | for (; *lhs_ptr != '\0'; ++lhs_ptr, ++rhs_ptr) { 72 | if (*rhs_ptr == '\0') 73 | return false; 74 | if (*lhs_ptr != *rhs_ptr) 75 | return false; 76 | } 77 | return *rhs_ptr == '\0'; 78 | } 79 | 80 | constexpr operator bool() const { return _ptr; } 81 | 82 | private: 83 | char const* _ptr; 84 | std::size_t _size; 85 | }; 86 | 87 | template <> 88 | struct Hash { 89 | using ResultType = std::size_t; 90 | using ArgumentType = String; 91 | 92 | constexpr std::size_t operator()(String const& string) { return string.hash(); } 93 | }; 94 | 95 | namespace Internal { 96 | template 97 | struct TupleHeadTypeProvider { 98 | using type = THead; 99 | }; 100 | 101 | template 102 | struct TupleToPairConversionImpl 103 | : public TupleToPairConversionImpl::type, 107 | TTail...> { 108 | 109 | using Base = TupleToPairConversionImpl::type, 113 | TTail...>; 114 | 115 | constexpr static void setPairTupleValueFromTuple(typename Base::ValueType& pair_tuple, 116 | T const& tuple) { 117 | std::get(pair_tuple) = std::get(tuple); 118 | Base::setPairTupleValueFromTuple(pair_tuple, tuple); 119 | } 120 | }; 121 | 122 | template 123 | struct TupleToPairConversionImpl<1, 2, T> : public TupleToPairConversionImpl<2, 2, T> { 124 | 125 | using Base = TupleToPairConversionImpl<2, 2, T>; 126 | 127 | constexpr static void setPairTupleValueFromTuple(typename Base::ValueType& pair_tuple, 128 | T const& tuple) { 129 | pair_tuple = std::get<1>(tuple); 130 | } 131 | }; 132 | 133 | template 134 | struct TupleToPairConversionImpl<0, M, T> : public TupleToPairConversionImpl<1, M, T> { 135 | using Base = TupleToPairConversionImpl<1, M, T>; 136 | 137 | using KeyType = typename std::conditional< 138 | std::is_convertible::type>::type>::type, 140 | char const*>::value, 141 | String, 142 | typename std::tuple_element<0, T>::type>::type; 143 | using ValueType = typename Base::ValueType; 144 | using PairType = std::pair; 145 | 146 | constexpr static auto makePairFromTuple(T const& t) { 147 | PairType pair; 148 | pair.first = std::get<0>(t); 149 | Base::setPairTupleValueFromTuple(pair.second, t); 150 | return pair; 151 | } 152 | }; 153 | 154 | template 155 | struct TupleToPairConversionImpl { 156 | using ValueType = std::tuple; 157 | 158 | constexpr static auto setPairTupleValueFromTuple(ValueType& pair_tuple, 159 | T const& tuple) {} 160 | 161 | constexpr static bool isValueTuple = true; 162 | }; 163 | 164 | template 165 | struct TupleToPairConversionImpl<2, 2, T, TTail...> { 166 | using ValueType = typename std::tuple_element<1, T>::type; 167 | 168 | constexpr static auto setPairTupleValueFromTuple(ValueType& pair_tuple, 169 | T const& tuple) {} 170 | 171 | constexpr static bool isValueTuple = false; 172 | }; 173 | 174 | template 175 | struct TupleToPairConversion 176 | : public TupleToPairConversionImpl<0, std::tuple_size::value, T> {}; 177 | 178 | template 179 | struct TupleAssignmentImpl : TupleAssignmentImpl { 180 | using Base = TupleAssignmentImpl; 181 | 182 | template 183 | constexpr static auto assign(std::tuple& lhs, 184 | std::tuple const& rhs) { 185 | std::get(lhs) = std::get(rhs); 186 | Base::assign(lhs, rhs); 187 | } 188 | }; 189 | 190 | template 191 | struct TupleAssignmentImpl { 192 | 193 | template 194 | constexpr static auto assign(std::tuple& lhs, 195 | std::tuple const& rhs) { 196 | std::get(lhs) = std::get(rhs); 197 | } 198 | }; 199 | 200 | template 201 | constexpr auto assignTuples(T& lhs, T const& rhs) { 202 | lhs = rhs; 203 | } 204 | 205 | template 206 | constexpr auto assignTuples(std::tuple& lhs, std::tuple const& rhs) { 207 | TupleAssignmentImpl<0, TArgs...>::assign(lhs, rhs); 208 | } 209 | } 210 | 211 | template 212 | struct HashMapSpec { 213 | using KeyType = typename T::first_type; 214 | using ValueType = typename T::second_type; 215 | using PairType = T; 216 | 217 | std::size_t maxBucketSize; 218 | std::size_t bucketCount; 219 | std::size_t elementCount; 220 | Array dataPairs; 221 | Array bucketIndexes; 222 | Array nonuniquenesses; 223 | }; 224 | 225 | namespace Internal { 226 | template 227 | constexpr auto 228 | makeHashMapSpecImpl(double load_factor, double min_load_factor, TArgs&&... args) { 229 | using tuple_type = typename Internal::TupleHeadTypeProvider::type; 230 | using tuple_pair_converter_type = Internal::TupleToPairConversion; 231 | using pair_type = typename tuple_pair_converter_type::PairType; 232 | 233 | Array const data_pairs{ 234 | {tuple_pair_converter_type::makePairFromTuple(args)...}}; 235 | Array bucket_indexes{}; 236 | Array nonuniquenesses{}; 237 | for (std::size_t i = 0; i < bucket_indexes.size(); ++i) { 238 | bucket_indexes[i] 239 | = Hash()(data_pairs[i].first); 240 | } 241 | for (std::size_t i = 0; i < bucket_indexes.size(); ++i) { 242 | if (nonuniquenesses[i]) 243 | continue; 244 | for (std::size_t j = i + 1; j < bucket_indexes.size(); ++j) { 245 | if (bucket_indexes[j] == bucket_indexes[i]) { 246 | if (data_pairs[j].first == data_pairs[i].first) { 247 | nonuniquenesses[j] = true; 248 | } 249 | } 250 | } 251 | } 252 | std::size_t element_count = 0; 253 | for (std::size_t i = 0; i < nonuniquenesses.size(); ++i) { 254 | if (!nonuniquenesses[i]) 255 | ++element_count; 256 | } 257 | std::size_t current_bucket_count 258 | = static_cast(element_count / load_factor); 259 | std::size_t last_improving_bucket_count = current_bucket_count; 260 | std::size_t current_max_bucket_size = 0; 261 | std::size_t last_improving_max_bucket_size = std::numeric_limits::max(); 262 | while (true) { 263 | current_max_bucket_size = 0; 264 | for (std::size_t i = 0; i < bucket_indexes.size(); ++i) { 265 | if (nonuniquenesses[i]) 266 | continue; 267 | std::size_t current_bucket_size = 1; 268 | std::size_t current_bucket_index = bucket_indexes[i] % current_bucket_count; 269 | for (std::size_t j = i + 1; j < bucket_indexes.size(); ++j) { 270 | if (!nonuniquenesses[j] 271 | && (bucket_indexes[j] % current_bucket_count) == current_bucket_index) 272 | ++current_bucket_size; 273 | } 274 | if (current_bucket_size > current_max_bucket_size) 275 | current_max_bucket_size = current_bucket_size; 276 | } 277 | if (current_max_bucket_size < last_improving_max_bucket_size) { 278 | last_improving_bucket_count = current_bucket_count; 279 | last_improving_max_bucket_size = current_max_bucket_size; 280 | } 281 | if (current_max_bucket_size <= 1) { 282 | break; 283 | } 284 | ++current_bucket_count; 285 | if ((float)element_count / current_bucket_count < min_load_factor) { 286 | break; 287 | } 288 | } 289 | for (std::size_t i = 0; i < bucket_indexes.size(); ++i) { 290 | if (!nonuniquenesses[i]) 291 | bucket_indexes[i] %= last_improving_bucket_count; 292 | } 293 | return HashMapSpec{last_improving_max_bucket_size, 294 | last_improving_bucket_count, 295 | element_count, 296 | data_pairs, 297 | bucket_indexes, 298 | nonuniquenesses}; 299 | } 300 | } 301 | 302 | template ::type>::value>::type> 305 | constexpr static auto makeHashMapSpec(TArgs&&... args) { 306 | return Internal::makeHashMapSpecImpl(std::forward(args)...); 307 | } 308 | 309 | template ::type>::value, 313 | int>::type 314 | = 0> 315 | constexpr static auto makeHashMapSpec(TArgs&&... args) { 316 | return Internal::makeHashMapSpecImpl(1.0, 0.5, std::forward(args)...); 317 | } 318 | 319 | template 320 | class HashMap { 321 | public: 322 | using KeyType = typename TSpec::KeyType; 323 | using ValueType = typename TSpec::ValueType; 324 | using PairType = typename TSpec::PairType; 325 | 326 | constexpr auto begin() const { return _buckets.begin(); } 327 | 328 | constexpr auto end() const { return _buckets.end(); } 329 | 330 | constexpr std::size_t bucketSize() const { return N; }; 331 | 332 | constexpr std::size_t bucketCount() const { return M; }; 333 | 334 | constexpr std::size_t size() const { return C; }; 335 | 336 | template 337 | constexpr ValueType find(U const& key) const noexcept { 338 | for (auto ptr = _buckets[Hash()(key) % M].begin(), end_ptr = ptr + N; 339 | ptr != end_ptr; 340 | ++ptr) { 341 | if (!ptr->first) 342 | return ValueType{}; 343 | if (ptr->first == key) 344 | return ptr->second; 345 | } 346 | return ValueType{}; 347 | } 348 | 349 | template 350 | constexpr auto operator[](U const& key) const { 351 | return find(key); 352 | } 353 | 354 | static constexpr HashMap make(TSpec const& spec) { 355 | Array, M> array{}; 356 | for (std::size_t i = 0; i < spec.dataPairs.size(); ++i) { 357 | if (spec.nonuniquenesses[i]) 358 | continue; 359 | for (auto ptr = array[spec.bucketIndexes[i]].begin(), end_ptr = ptr + N; 360 | ptr != end_ptr; 361 | ++ptr) { 362 | if (ptr->first) 363 | continue; 364 | ptr->first = spec.dataPairs[i].first; 365 | Internal::assignTuples(ptr->second, spec.dataPairs[i].second); 366 | break; 367 | } 368 | } 369 | return HashMap{array}; 370 | } 371 | 372 | private: 373 | constexpr HashMap(Array, M> const& data) : _buckets(data){}; 374 | 375 | Array, M> _buckets; 376 | }; 377 | } 378 | --------------------------------------------------------------------------------