├── CMakeLists.txt ├── LICENSE.md ├── README.md ├── semimap.h └── test.cpp /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.8) 2 | 3 | project (semimap) 4 | 5 | set(CMAKE_CXX_FLAGS_RELEASE -Ofast -march=native) 6 | 7 | add_executable(semimap_test test.cpp) 8 | 9 | # Set C++ standard for all targets 10 | set_property(TARGET semimap_test PROPERTY CXX_STANDARD 17) 11 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Fabian Renn-Giles 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | semi::static_map and semi::map 2 | ============================== 3 | 4 | This container was the topic of a cppcon talk: 5 | 6 | https://www.youtube.com/watch?v=qNAbGpV1ZkU 7 | 8 | (slides: https://goo.gl/igwVxD ) 9 | 10 | 11 | associative map containers with compile-time lookup! 12 | ---------------------------------------------------- 13 | 14 | Normally, associative containers require some runtime overhead when looking up their values from a key. However, when the key is known at compile-time (for example, when the key is a literal) then this run-time lookup could technically be avoided. This is exactly what the goal of `semi::static_map` and `semi::map` is. 15 | 16 | In fact, when using `semi::static_map` and looking up a value with C++ literal as a key, then the value lookup is nearly as efficient as looking up a global variable (on x86/arm it will reduce to only three machine instructions: a cmp, jne and a direct load). As long as you use C++ literals as your keys, the computational time of the lookup will stay constant and, for example, will not increase with the number of keys in your container! 17 | 18 | ```c++ 19 | #include 20 | #include 21 | 22 | #include "semimap.h" 23 | 24 | #define ID(x) []() constexpr { return x; } 25 | 26 | int main() 27 | { 28 | semi::map map; 29 | 30 | // Using string literals to access the container is super fast: 31 | // computational complexity remains constant regardless of the number of key, value pairs! 32 | map.get(ID("food")) = "pizza"; 33 | map.get(ID("drink")) = "soda"; 34 | std::cout << map.get(ID("drink")) << std::endl; 35 | 36 | 37 | // Values can also be looked-up with run-time keys 38 | // which will then use std::unordered_map as a fallback. 39 | std::string key; 40 | 41 | std::cin >> key; 42 | std::cout << map.get(key) << std::endl; // for example: outputs "soda" if key is "drink" 43 | 44 | // there is also a static version of the map where lookeup is even faster 45 | struct Tag {}; 46 | using Map = semi::static_map; 47 | 48 | // in fact, it is (nearly) as fast as looking up any plain old global variable 49 | Map::get(ID("food")) = "pizza"; 50 | Map::get(ID("drink")) = "beer"; 51 | 52 | return 0; 53 | } 54 | ``` 55 | 56 | The containers are very simple and only have the methods `get`, `erase`, `contains` and `clear`. `get` can also take any number of extra optoinal parameters which will be passed to your value's constructor if the value is not already in the container. As such, `get` is very similar to `std::map`'s `try_emplace`. For example: 57 | 58 | ```c++ 59 | #define ID(x) []() constexpr { return x; } 60 | 61 | semi::map m; 62 | 63 | m.get(ID("food"), "pizza"); // a value with key food is not already in the map so construct it with the parameter "pizza" 64 | m.get(ID("food"), "spaghetti"); // a value with key food is in the map. The second parameter will not be used 65 | ``` 66 | 67 | There are two variants of the map: 68 | 69 | 1) `semi::map` behaves very similar to a normal associative container. It's methods are non-static, so that the key, value pairs are not shared between several instances of `semi::map`s (as one would expect). 70 | 2) `semi::static_map` is completely static. It's even faster than `semi::map`. However, to achieve this speed, it requires that all the methods are static. This means that two `semi::static_map`s, with same key and value types, will share their contents. To avoid this, there is a third optional "tag" template parameter. Only `semi::static_map`s that also have the same tag template type will share their contents. It's useful to use a local `struct` as the tag type, like follows: 71 | 72 | ```c++ 73 | void foo() 74 | { 75 | struct Tag {}; 76 | using map = semi::static_map; 77 | 78 | map::get(ID("age")) = 18; 79 | } 80 | ``` 81 | 82 | As this ensures that even if the same `struct` name "Tag" is used in another block, the `semi::static_map`s will not share their contents. 83 | 84 | Also note, that as a static type, the contents of the `semi::static_map` will only be deleted when the program is exited. If you need to delete your key/values sooner then use the `clear` method. 85 | 86 | -Fabian 87 | 88 | @hogliux -------------------------------------------------------------------------------- /semimap.h: -------------------------------------------------------------------------------- 1 | /* 2 | semi::static_map and semi::map - associative map containers with compile-time 3 | lookup! 4 | 5 | ============================================================================== 6 | Copyright (c) 2018 Fabian Renn-Giles 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | ============================================================================== 26 | 27 | This associative map container avoids any hashing overhead when the key is 28 | a literal. It can also fall back to run-time lookup for non-literal keys. 29 | 30 | You use it as follows: 31 | 32 | #include 33 | #include 34 | 35 | #include "semimap.h" 36 | 37 | #define ID(x) []() constexpr { return x; } 38 | 39 | int main() 40 | { 41 | semi::map map; 42 | 43 | // Using string literals to access the container is super fast: 44 | // computational complexity remains constant regardless of the number of key, value pairs! 45 | map.get(ID("food")) = "pizza"; 46 | map.get(ID("drink")) = "soda"; 47 | std::cout << map.get(ID("drink")) << std::endl; 48 | 49 | // Values can also be looked-up with run-time keys 50 | // which will then use std::unordered_map as a fallback. 51 | std::string key; 52 | 53 | std::cin >> key; 54 | std::cout << map.get(key) << std::endl; // for example: outputs "soda" if key is "drink" 55 | 56 | // there is also a static version of the map where lookup is even faster 57 | struct Tag {}; 58 | using Map = semi::static_map; 59 | 60 | // in fact, it is (nearly) as fast as looking up any plain old global variable 61 | Map::get(ID("food")) = "pizza"; 62 | Map::get(ID("drink")) = "beer"; 63 | 64 | return 0; 65 | } 66 | 67 | See my cppcon talk about this container here: 68 | 69 | Slides: https://goo.gl/igwVxD 70 | Video: https://www.youtube.com/watch?v=qNAbGpV1ZkU 71 | 72 | Twitter: @hogliux 73 | */ 74 | 75 | #include 76 | #include 77 | #include 78 | #include 79 | #include 80 | #include 81 | #include 82 | 83 | // check C++ version 84 | #if (defined(_MSVC_LANG) && _MSVC_LANG < 201703L) || ((!defined(_MSVC_LANG)) && __cplusplus < 201703L) 85 | #error semi::map and semi::static_map require C++17 support 86 | #endif 87 | 88 | #ifdef __GNUC__ 89 | #define semi_branch_expect(x, y) __builtin_expect(x, y) 90 | #else 91 | #define semi_branch_expect(x, y) x 92 | #endif 93 | 94 | namespace semi { 95 | 96 | namespace detail { 97 | // evaluates to the type returned by a constexpr lambda 98 | template 99 | using identifier_type = decltype(std::declval()()); 100 | 101 | //============================================================================== 102 | constexpr std::size_t constexpr_strlen(const char* str) { return str[0] == 0 ? 0 : constexpr_strlen(str + 1) + 1; } 103 | 104 | //============================================================================== 105 | template 106 | struct dummy_t { 107 | }; 108 | 109 | template >, int> = 0> 110 | constexpr auto idval2type(Identifier id) 111 | { 112 | return dummy_t{}; 113 | } 114 | 115 | template 116 | constexpr auto array_id2type(Identifier id, std::index_sequence) 117 | { 118 | return dummy_t{}; 119 | } 120 | 121 | template , const char*>, int> = 0> 122 | constexpr auto idval2type(Identifier id) 123 | { 124 | return array_id2type(id, std::make_index_sequence{}); 125 | } 126 | 127 | template 128 | struct default_tag { 129 | }; 130 | 131 | //============================================================================== 132 | // super simple flat map implementation 133 | template 134 | class flat_map { 135 | public: 136 | template 137 | Value& get(const Key& key, Args&&... args) 138 | { 139 | for (auto& pair : storage) 140 | if (pair.first == key) 141 | return pair.second; 142 | 143 | return storage.emplace_back(key, Value(std::forward(args)...)).second; 144 | } 145 | 146 | auto size() const 147 | { 148 | return storage.size(); 149 | } 150 | 151 | void erase(const Key& key) 152 | { 153 | for (auto it = storage.begin(); it != storage.end(); ++it) { 154 | if (it->first == key) { 155 | storage.erase(it); 156 | return; 157 | } 158 | } 159 | } 160 | 161 | bool contains(const Key& key) const 162 | { 163 | for (auto& pair : storage) 164 | if (pair.first == key) 165 | return true; 166 | 167 | return false; 168 | } 169 | 170 | private: 171 | std::vector> storage; 172 | }; 173 | 174 | //============================================================================== 175 | // some versions of clang do not seem to have std::launder 176 | #if __cpp_lib_launder >= 201606 177 | template 178 | constexpr T* launder(T* p) 179 | { 180 | return std::launder(p); 181 | } 182 | #else 183 | template 184 | constexpr T* launder(T* p) 185 | { 186 | return p; 187 | } 188 | #endif 189 | 190 | } // namespace detail 191 | 192 | // forward declaration 193 | template 194 | class map; 195 | 196 | //============================================================================== 197 | //============================================================================== 198 | template > 199 | class static_map { 200 | public: 201 | static_map() = delete; 202 | 203 | template , int> = 0> 204 | static Value& get(Identifier identifier, Args&&... args) 205 | { 206 | static_assert(std::is_convertible_v, Key>); 207 | using UniqueTypeForKeyValue = decltype(detail::idval2type(identifier)); 208 | 209 | auto* mem = storage; 210 | auto& i_flag = init_flag; 211 | 212 | if (!semi_branch_expect(i_flag, true)) { 213 | Key key(identifier()); 214 | 215 | auto it = runtime_map.find(key); 216 | 217 | if (it != runtime_map.end()) 218 | it->second = u_ptr(new (mem) Value(std::move(*it->second)), { &i_flag }); 219 | else 220 | runtime_map.emplace_hint(it, key, u_ptr(new (mem) Value(std::forward(args)...), { &i_flag })); 221 | 222 | i_flag = true; 223 | } 224 | 225 | return *detail::launder(reinterpret_cast(mem)); 226 | } 227 | 228 | template 229 | static Value& get(const Key& key, Args&&... args) 230 | { 231 | auto it = runtime_map.find(key); 232 | 233 | if (it != runtime_map.end()) 234 | return *it->second; 235 | 236 | return *runtime_map.emplace_hint(it, key, u_ptr(new Value(std::forward(args)...), { nullptr }))->second; 237 | } 238 | 239 | template , int> = 0> 240 | static bool contains(Identifier identifier) 241 | { 242 | using UniqueTypeForKeyValue = decltype(detail::idval2type(identifier)); 243 | 244 | if (!semi_branch_expect(init_flag, true)) { 245 | auto key = identifier(); 246 | return contains(key); 247 | } 248 | 249 | return true; 250 | } 251 | 252 | static bool contains(const Key& key) 253 | { 254 | return (runtime_map.find(key) != runtime_map.end()); 255 | } 256 | 257 | template , int> = 0> 258 | static void erase(Identifier identifier) 259 | { 260 | erase(identifier()); 261 | } 262 | 263 | static void erase(const Key& key) 264 | { 265 | runtime_map.erase(key); 266 | } 267 | 268 | static void clear() 269 | { 270 | runtime_map.clear(); 271 | } 272 | 273 | private: 274 | struct value_deleter { 275 | bool* i_flag = nullptr; 276 | 277 | void operator()(Value* v) 278 | { 279 | if (i_flag != nullptr) { 280 | v->~Value(); 281 | *i_flag = false; 282 | } else { 283 | delete v; 284 | } 285 | } 286 | }; 287 | 288 | using u_ptr = std::unique_ptr; 289 | 290 | template 291 | friend class map; 292 | 293 | template 294 | alignas(Value) static char storage[sizeof(Value)]; 295 | template 296 | static bool init_flag; 297 | static std::unordered_map> runtime_map; 298 | }; 299 | 300 | template 301 | std::unordered_map::u_ptr> static_map::runtime_map; 302 | 303 | template 304 | template 305 | alignas(Value) char static_map::storage[sizeof(Value)]; 306 | 307 | template 308 | template 309 | bool static_map::init_flag = false; 310 | 311 | //============================================================================== 312 | //============================================================================== 313 | template > 314 | class map { 315 | public: 316 | ~map() 317 | { 318 | clear(); 319 | } 320 | 321 | template 322 | Value& get(Identifier key, Args&... args) 323 | { 324 | return staticmap::get(key).get(this, std::forward(args)...); 325 | } 326 | 327 | template 328 | bool contains(Identifier key) 329 | { 330 | if (staticmap::contains(key)) 331 | return staticmap::get(key).contains(this); 332 | 333 | return false; 334 | } 335 | 336 | template 337 | void erase(Identifier key) 338 | { 339 | if (staticmap::contains(key)) { 340 | auto& map = staticmap::get(key); 341 | map.erase(this); 342 | 343 | if (map.size() == 0) 344 | staticmap::erase(key); 345 | } 346 | } 347 | 348 | void clear() 349 | { 350 | auto it = staticmap::runtime_map.begin(); 351 | 352 | while (it != staticmap::runtime_map.end()) { 353 | auto& map = *it->second; 354 | 355 | map.erase(this); 356 | 357 | if (map.size() == 0) { 358 | it = staticmap::runtime_map.erase(staticmap::runtime_map.find(it->first)); 359 | continue; 360 | } 361 | 362 | ++it; 363 | } 364 | } 365 | 366 | private: 367 | using staticmap = static_map*, Value>, Tag>; 368 | }; 369 | 370 | #undef semi_branch_expect 371 | 372 | } // namespace semi 373 | -------------------------------------------------------------------------------- /test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "semimap.h" 6 | 7 | #define ID(x) \ 8 | []() constexpr { return x; } 9 | 10 | //============================================================================== 11 | void test_static_map() 12 | { 13 | // Test compile-time only load/store 14 | { 15 | struct Tag { 16 | }; 17 | using map = semi::static_map; 18 | 19 | auto& food = map::get(ID("food")); 20 | assert(food.empty()); 21 | 22 | food = "pizza"; 23 | assert(map::get(ID("food")) == "pizza"); 24 | 25 | auto& drink = map::get(ID("drink")); 26 | assert(drink.empty()); 27 | 28 | drink = "beer"; 29 | assert(map::get(ID("food")) == "pizza"); 30 | assert(map::get(ID("drink")) == "beer"); 31 | 32 | map::get(ID("food")) = "spaghetti"; 33 | assert(map::get(ID("food")) == "spaghetti"); 34 | assert(map::get(ID("drink")) == "beer"); 35 | 36 | map::get(ID("drink")) = "soda"; 37 | assert(map::get(ID("food")) == "spaghetti"); 38 | assert(map::get(ID("drink")) == "soda"); 39 | 40 | assert(map::get(ID("starter"), "soup") == "soup"); 41 | assert(map::get(ID("starter"), "salad") == "soup"); 42 | } 43 | 44 | // Test run-time only load/store 45 | { 46 | struct Tag { 47 | }; 48 | using map = semi::static_map; 49 | 50 | auto& food = map::get("food"); 51 | assert(food.empty()); 52 | 53 | food = "pizza"; 54 | assert(map::get("food") == "pizza"); 55 | 56 | auto& drink = map::get("drink"); 57 | assert(drink.empty()); 58 | 59 | drink = "beer"; 60 | assert(map::get("food") == "pizza"); 61 | assert(map::get("drink") == "beer"); 62 | 63 | map::get("food") = "spaghetti"; 64 | assert(map::get("food") == "spaghetti"); 65 | assert(map::get("drink") == "beer"); 66 | 67 | map::get("drink") = "soda"; 68 | assert(map::get("food") == "spaghetti"); 69 | assert(map::get("drink") == "soda"); 70 | 71 | assert(map::get("starter", "soup") == "soup"); 72 | assert(map::get("starter", "salad") == "soup"); 73 | } 74 | 75 | // Test mixed compile-time/run-time load/store 76 | { 77 | struct Tag { 78 | }; 79 | using map = semi::static_map; 80 | 81 | // compile-time first, then run-time 82 | map::get(ID("food")) = "pizza"; 83 | assert(map::get("food") == "pizza"); 84 | 85 | // runtime-time first, then compile-time 86 | map::get("drink") = "beer"; 87 | assert(map::get(ID("drink")) == "beer"); 88 | 89 | assert(map::get(ID("food")) == "pizza"); 90 | assert(map::get("drink") == "beer"); 91 | 92 | assert(map::get(ID("starter"), "soup") == "soup"); 93 | assert(map::get("starter", "salad") == "soup"); 94 | 95 | assert(map::get("side", "rice") == "rice"); 96 | assert(map::get(ID("side"), "peas") == "rice"); 97 | } 98 | 99 | // test clear & contains 100 | { 101 | struct Tag { 102 | }; 103 | using map = semi::static_map; 104 | 105 | assert(!map::contains(ID("food"))); 106 | assert(!map::contains("food")); 107 | 108 | // check again to see if contains by mistake add the element 109 | assert(!map::contains(ID("food"))); 110 | assert(!map::contains("food")); 111 | 112 | map::get(ID("food")) = "pizza"; 113 | assert(map::contains(ID("food"))); 114 | assert(map::contains("food")); 115 | 116 | map::get("drink") = "beer"; 117 | assert(map::contains("drink")); 118 | assert(map::contains(ID("drink"))); 119 | 120 | map::get(ID("dessert")) = "icecream"; 121 | assert(map::contains("dessert")); 122 | assert(map::contains(ID("dessert"))); 123 | 124 | map::get("starter") = "salad"; 125 | assert(map::contains(ID("starter"))); 126 | assert(map::contains("starter")); 127 | 128 | map::clear(); 129 | 130 | assert(!map::contains(ID("food"))); 131 | assert(!map::contains("food")); 132 | assert(!map::contains("drink")); 133 | assert(!map::contains(ID("drink"))); 134 | } 135 | 136 | // test erase 137 | { 138 | struct Tag { 139 | }; 140 | using map = semi::static_map; 141 | 142 | map::get(ID("food")) = "pizza"; 143 | map::get(ID("drink")) = "beer"; 144 | map::get(ID("dessert")) = "icecream"; 145 | map::get(ID("starter")) = "soup"; 146 | map::get(ID("side")) = "salad"; 147 | 148 | // erase first 149 | map::erase(ID("food")); 150 | assert((!map::contains(ID("food"))) && map::contains(ID("drink")) && map::contains(ID("dessert")) && map::contains(ID("starter")) && map::contains(ID("side"))); 151 | 152 | // erase last 153 | map::erase("side"); 154 | assert((!map::contains(ID("food"))) && map::contains(ID("drink")) && map::contains(ID("dessert")) && map::contains(ID("starter")) && (!map::contains(ID("side")))); 155 | 156 | // try adding something 157 | map::get("bill") = "too much"; 158 | assert((!map::contains(ID("food"))) && map::contains(ID("drink")) && map::contains(ID("dessert")) && map::contains(ID("starter")) && (!map::contains(ID("side"))) && map::contains(ID("bill"))); 159 | 160 | // erase middle 161 | map::erase(ID("dessert")); 162 | assert((!map::contains(ID("food"))) && map::contains(ID("drink")) && (!map::contains(ID("dessert"))) && map::contains(ID("starter")) && (!map::contains(ID("side"))) && map::contains(ID("bill"))); 163 | } 164 | 165 | // test independent maps do not influence each other 166 | { 167 | struct TagA { 168 | }; 169 | struct TagB { 170 | }; 171 | using mapA = semi::static_map; 172 | using mapB = semi::static_map; 173 | 174 | mapA::get(ID("food")) = "pizza"; 175 | assert(mapA::get("food") == "pizza"); 176 | assert(!mapB::contains("food")); 177 | 178 | mapB::get(ID("food")) = "spaghetti"; 179 | assert(mapA::get("food") == "pizza"); 180 | assert(mapB::get("food") == "spaghetti"); 181 | 182 | mapB::get("drink") = "beer"; 183 | assert(mapB::get(ID("drink")) == "beer"); 184 | assert(!mapA::contains(ID("drink"))); 185 | assert(mapA::contains("food")); 186 | 187 | mapA::get("drink") = "soda"; 188 | assert(mapA::get(ID("drink")) == "soda"); 189 | assert(mapB::get(ID("drink")) == "beer"); 190 | 191 | mapA::get(ID("starter")) = "salad"; 192 | mapB::get("starter") = "soup"; 193 | 194 | mapB::erase("drink"); 195 | assert(mapA::contains("drink")); 196 | assert(mapA::contains(ID("drink"))); 197 | assert(!mapB::contains("drink")); 198 | assert(!mapB::contains(ID("drink"))); 199 | 200 | mapB::clear(); 201 | assert(mapA::get(ID("starter")) == "salad"); 202 | assert(mapA::get("food") == "pizza"); 203 | assert(mapA::get(ID("drink")) == "soda"); 204 | assert(!mapB::contains("food")); 205 | assert(!mapB::contains(ID("drink"))); 206 | } 207 | } 208 | 209 | void test_map() 210 | { 211 | // Test compile-time only load/store 212 | { 213 | semi::map map; 214 | 215 | auto& food = map.get(ID("food")); 216 | assert(food.empty()); 217 | 218 | food = "pizza"; 219 | assert(map.get(ID("food")) == "pizza"); 220 | 221 | auto& drink = map.get(ID("drink")); 222 | assert(drink.empty()); 223 | 224 | drink = "beer"; 225 | assert(map.get(ID("food")) == "pizza"); 226 | assert(map.get(ID("drink")) == "beer"); 227 | 228 | map.get(ID("food")) = "spaghetti"; 229 | assert(map.get(ID("food")) == "spaghetti"); 230 | assert(map.get(ID("drink")) == "beer"); 231 | 232 | map.get(ID("drink")) = "soda"; 233 | assert(map.get(ID("food")) == "spaghetti"); 234 | assert(map.get(ID("drink")) == "soda"); 235 | 236 | assert(map.get(ID("starter"), "soup") == "soup"); 237 | assert(map.get(ID("starter"), "salad") == "soup"); 238 | } 239 | 240 | // Test run-time only load/store 241 | { 242 | semi::map map; 243 | 244 | auto& food = map.get("food"); 245 | assert(food.empty()); 246 | 247 | food = "pizza"; 248 | assert(map.get("food") == "pizza"); 249 | 250 | auto& drink = map.get("drink"); 251 | assert(drink.empty()); 252 | 253 | drink = "beer"; 254 | assert(map.get("food") == "pizza"); 255 | assert(map.get("drink") == "beer"); 256 | 257 | map.get("food") = "spaghetti"; 258 | assert(map.get("food") == "spaghetti"); 259 | assert(map.get("drink") == "beer"); 260 | 261 | map.get("drink") = "soda"; 262 | assert(map.get("food") == "spaghetti"); 263 | assert(map.get("drink") == "soda"); 264 | 265 | assert(map.get("starter", "soup") == "soup"); 266 | assert(map.get("starter", "salad") == "soup"); 267 | } 268 | 269 | // Test mixed compile-time/run-time load/store 270 | { 271 | semi::map map; 272 | 273 | // compile-time first, then run-time 274 | map.get(ID("food")) = "pizza"; 275 | assert(map.get("food") == "pizza"); 276 | 277 | // runtime-time first, then compile-time 278 | map.get("drink") = "beer"; 279 | assert(map.get(ID("drink")) == "beer"); 280 | 281 | assert(map.get(ID("food")) == "pizza"); 282 | assert(map.get("drink") == "beer"); 283 | 284 | assert(map.get(ID("starter"), "soup") == "soup"); 285 | assert(map.get("starter", "salad") == "soup"); 286 | 287 | assert(map.get("side", "rice") == "rice"); 288 | assert(map.get(ID("side"), "peas") == "rice"); 289 | } 290 | 291 | // test clear & contains 292 | { 293 | semi::map map; 294 | 295 | assert(!map.contains(ID("food"))); 296 | assert(!map.contains("food")); 297 | 298 | // check again to see if contains by mistake add the element 299 | assert(!map.contains(ID("food"))); 300 | assert(!map.contains("food")); 301 | 302 | map.get(ID("food")) = "pizza"; 303 | assert(map.contains(ID("food"))); 304 | assert(map.contains("food")); 305 | 306 | map.get("drink") = "beer"; 307 | assert(map.contains("drink")); 308 | assert(map.contains(ID("drink"))); 309 | 310 | map.get(ID("dessert")) = "icecream"; 311 | assert(map.contains("dessert")); 312 | assert(map.contains(ID("dessert"))); 313 | 314 | map.get("starter") = "salad"; 315 | assert(map.contains(ID("starter"))); 316 | assert(map.contains("starter")); 317 | 318 | map.clear(); 319 | 320 | assert(!map.contains(ID("food"))); 321 | assert(!map.contains("food")); 322 | assert(!map.contains("drink")); 323 | assert(!map.contains(ID("drink"))); 324 | } 325 | 326 | // test erase 327 | { 328 | semi::map map; 329 | 330 | map.get(ID("food")) = "pizza"; 331 | map.get(ID("drink")) = "beer"; 332 | map.get(ID("dessert")) = "icecream"; 333 | map.get(ID("starter")) = "soup"; 334 | map.get(ID("side")) = "salad"; 335 | 336 | // erase first 337 | map.erase(ID("food")); 338 | assert((!map.contains(ID("food"))) && map.contains(ID("drink")) && map.contains(ID("dessert")) && map.contains(ID("starter")) && map.contains(ID("side"))); 339 | 340 | // erase last 341 | map.erase("side"); 342 | assert((!map.contains(ID("food"))) && map.contains(ID("drink")) && map.contains(ID("dessert")) && map.contains(ID("starter")) && (!map.contains(ID("side")))); 343 | 344 | // try adding something 345 | map.get("bill") = "too much"; 346 | assert((!map.contains(ID("food"))) && map.contains(ID("drink")) && map.contains(ID("dessert")) && map.contains(ID("starter")) && (!map.contains(ID("side"))) && map.contains(ID("bill"))); 347 | 348 | // erase middle 349 | map.erase(ID("dessert")); 350 | assert((!map.contains(ID("food"))) && map.contains(ID("drink")) && (!map.contains(ID("dessert"))) && map.contains(ID("starter")) && (!map.contains(ID("side"))) && map.contains(ID("bill"))); 351 | } 352 | 353 | // test independent maps do not influence each other 354 | { 355 | semi::map mapA; 356 | semi::map mapB; 357 | 358 | mapA.get(ID("food")) = "pizza"; 359 | assert(mapA.get("food") == "pizza"); 360 | assert(!mapB.contains("food")); 361 | 362 | mapB.get(ID("food")) = "spaghetti"; 363 | assert(mapA.get("food") == "pizza"); 364 | assert(mapB.get("food") == "spaghetti"); 365 | 366 | mapB.get("drink") = "beer"; 367 | assert(mapB.get(ID("drink")) == "beer"); 368 | assert(!mapA.contains(ID("drink"))); 369 | assert(mapA.contains("food")); 370 | 371 | mapA.get("drink") = "soda"; 372 | assert(mapA.get(ID("drink")) == "soda"); 373 | assert(mapB.get(ID("drink")) == "beer"); 374 | 375 | mapA.get(ID("starter")) = "salad"; 376 | mapB.get("starter") = "soup"; 377 | 378 | mapB.erase("drink"); 379 | assert(mapA.contains("drink")); 380 | assert(mapA.contains(ID("drink"))); 381 | assert(!mapB.contains("drink")); 382 | assert(!mapB.contains(ID("drink"))); 383 | 384 | mapB.clear(); 385 | assert(mapA.get(ID("starter")) == "salad"); 386 | assert(mapA.get("food") == "pizza"); 387 | assert(mapA.get(ID("drink")) == "soda"); 388 | assert(!mapB.contains("food")); 389 | assert(!mapB.contains(ID("drink"))); 390 | } 391 | } 392 | 393 | int main() 394 | { 395 | test_static_map(); 396 | test_map(); 397 | 398 | return 0; 399 | } 400 | --------------------------------------------------------------------------------