├── .clang-format ├── .codecov.yml ├── .github └── workflows │ └── ci.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake └── tsl-ordered-mapConfig.cmake.in ├── doxygen.conf ├── include └── tsl │ ├── ordered_hash.h │ ├── ordered_map.h │ └── ordered_set.h ├── tests ├── CMakeLists.txt ├── custom_allocator_tests.cpp ├── main.cpp ├── ordered_map_tests.cpp ├── ordered_set_tests.cpp └── utils.h └── tsl-ordered-map.natvis /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | comment: off 2 | coverage: 3 | status: 4 | project: off 5 | patch: off 6 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request, release] 4 | 5 | jobs: 6 | build: 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | config: 11 | - { 12 | name: linux-x64-gcc, 13 | os: ubuntu-latest, 14 | cxx: g++, 15 | cmake-build-type: Release 16 | } 17 | - { 18 | name: linux-x64-gcc-no-exceptions, 19 | os: ubuntu-latest, 20 | cxx: g++, 21 | cxx-flags: -fno-exceptions, 22 | cmake-build-type: Release 23 | } 24 | - { 25 | name: linux-x64-clang, 26 | os: ubuntu-latest, 27 | cxx: clang++, 28 | cmake-build-type: Release 29 | } 30 | - { 31 | name: macos-x64-gcc, 32 | os: macos-13, 33 | cxx: g++, 34 | cmake-build-type: Release 35 | } 36 | - { 37 | name: macos-x64-clang, 38 | os: macos-13, 39 | cxx: clang++, 40 | cmake-build-type: Release 41 | } 42 | - { 43 | name: linux-x64-clang-sanitize, 44 | os: ubuntu-latest, 45 | cxx: clang++, 46 | cxx-flags: "-fsanitize=address,undefined", 47 | cmake-build-type: Release 48 | } 49 | - { 50 | name: linux-x64-gcc-coverage, 51 | os: ubuntu-latest, 52 | cxx: g++, 53 | cxx-flags: --coverage, 54 | gcov-tool: gcov, 55 | cmake-build-type: Debug 56 | } 57 | - { 58 | name: windows-x64-vs-2019, 59 | os: windows-2019, 60 | cmake-build-type: Release, 61 | cmake-generator: Visual Studio 16 2019, 62 | cmake-platform: x64, 63 | vcpkg-triplet: x64-windows-static-md 64 | } 65 | - { 66 | name: windows-x86-vs-2019, 67 | os: windows-2019, 68 | cmake-build-type: Release, 69 | cmake-generator: Visual Studio 16 2019, 70 | cmake-platform: Win32, 71 | vcpkg-triplet: x86-windows-static-md 72 | } 73 | - { 74 | name: windows-x64-vs-2022, 75 | os: windows-2022, 76 | cmake-build-type: Release, 77 | cmake-generator: Visual Studio 17 2022, 78 | cmake-platform: x64, 79 | vcpkg-triplet: x64-windows-static-md 80 | } 81 | - { 82 | name: windows-x86-vs-2022, 83 | os: windows-2022, 84 | cmake-build-type: Release, 85 | cmake-generator: Visual Studio 17 2022, 86 | cmake-platform: Win32, 87 | vcpkg-triplet: x86-windows-static-md 88 | } 89 | name: ${{matrix.config.name}} 90 | runs-on: ${{matrix.config.os}} 91 | steps: 92 | - uses: actions/checkout@v2 93 | 94 | # Windows 95 | - name: Install boost (Windows) 96 | run: vcpkg install boost-test:${{matrix.config.vcpkg-triplet}} 97 | if: runner.os == 'Windows' 98 | 99 | - name: Configure CMake (Windows) 100 | run: cmake -G "${{matrix.config.cmake-generator}}" -A ${{matrix.config.cmake-platform}} -DCMAKE_BUILD_TYPE=${{matrix.config.cmake-build-type}} -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" -DVCPKG_TARGET_TRIPLET=${{matrix.config.vcpkg-triplet}} -S ${{github.workspace}}/tests -B ${{github.workspace}}/build 101 | if: runner.os == 'Windows' 102 | 103 | - name: Build (Windows) 104 | run: cmake --build ${{github.workspace}}/build --config ${{matrix.config.cmake-build-type}} --verbose 105 | if: runner.os == 'Windows' 106 | 107 | - name: Test (Windows) 108 | run: ${{github.workspace}}/build/${{matrix.config.cmake-build-type}}/tsl_ordered_map_tests.exe 109 | if: runner.os == 'Windows' 110 | 111 | # Linux or macOS 112 | - name: Install boost (Linux or macOS) 113 | run: vcpkg install boost-test 114 | if: runner.os == 'Linux' || runner.os == 'macOS' 115 | 116 | - name: Configure CMake (Linux or macOS) 117 | run: cmake -DCMAKE_BUILD_TYPE=${{matrix.config.cmake-build-type}} -DCMAKE_TOOLCHAIN_FILE="$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" -S ${{github.workspace}}/tests -B ${{github.workspace}}/build 118 | env: 119 | CXX: ${{matrix.config.cxx}} 120 | CXXFLAGS: ${{matrix.config.cxx-flags}} 121 | if: runner.os == 'Linux' || runner.os == 'macOS' 122 | 123 | - name: Build (Linux or macOS) 124 | run: cmake --build ${{github.workspace}}/build --verbose 125 | if: runner.os == 'Linux' || runner.os == 'macOS' 126 | 127 | - name: Test (Linux or macOS) 128 | run: ${{github.workspace}}/build/tsl_ordered_map_tests 129 | if: runner.os == 'Linux' || runner.os == 'macOS' 130 | 131 | - name: Coverage 132 | run: | 133 | sudo apt-get install -y lcov 134 | lcov -c -b ${{github.workspace}}/include -d ${{github.workspace}}/build -o ${{github.workspace}}/coverage.info --no-external --gcov-tool ${{matrix.config.gcov-tool}} 135 | bash <(curl -s https://codecov.io/bash) -f ${{github.workspace}}/coverage.info 136 | if: ${{matrix.config.name == 'linux-x64-gcc-coverage'}} 137 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | include(GNUInstallDirs) 3 | 4 | 5 | project(tsl-ordered-map VERSION 1.1.0) 6 | 7 | add_library(ordered_map INTERFACE) 8 | # Use tsl::ordered_map as target, more consistent with other libraries conventions (Boost, Qt, ...) 9 | add_library(tsl::ordered_map ALIAS ordered_map) 10 | 11 | target_include_directories(ordered_map INTERFACE 12 | "$" 13 | "$") 14 | 15 | list(APPEND headers "${CMAKE_CURRENT_SOURCE_DIR}/include/tsl/ordered_hash.h" 16 | "${CMAKE_CURRENT_SOURCE_DIR}/include/tsl/ordered_map.h" 17 | "${CMAKE_CURRENT_SOURCE_DIR}/include/tsl/ordered_set.h") 18 | target_sources(ordered_map INTERFACE "$") 19 | 20 | if(MSVC) 21 | target_sources(ordered_map INTERFACE 22 | "$" 23 | "$") 24 | endif() 25 | 26 | 27 | 28 | 29 | include(CMakePackageConfigHelpers) 30 | 31 | ## Install include directory and potential natvis file 32 | install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/tsl" 33 | DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") 34 | 35 | if(MSVC) 36 | install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/tsl-ordered-map.natvis" 37 | DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}") 38 | endif() 39 | 40 | 41 | 42 | ## Create and install tsl-ordered-mapConfig.cmake 43 | configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/tsl-ordered-mapConfig.cmake.in" 44 | "${CMAKE_CURRENT_BINARY_DIR}/tsl-ordered-mapConfig.cmake" 45 | INSTALL_DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/cmake/tsl-ordered-map") 46 | 47 | install(FILES "${CMAKE_CURRENT_BINARY_DIR}/tsl-ordered-mapConfig.cmake" 48 | DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/cmake/tsl-ordered-map") 49 | 50 | 51 | 52 | ## Create and install tsl-ordered-mapTargets.cmake 53 | install(TARGETS ordered_map 54 | EXPORT tsl-ordered-mapTargets) 55 | 56 | install(EXPORT tsl-ordered-mapTargets 57 | NAMESPACE tsl:: 58 | DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/cmake/tsl-ordered-map") 59 | 60 | 61 | 62 | ## Create and install tsl-ordered-mapConfigVersion.cmake 63 | # tsl-ordered-map is header-only and does not depend on the architecture. 64 | # Remove CMAKE_SIZEOF_VOID_P from tsl-ordered-mapConfigVersion.cmake so that a 65 | # tsl-ordered-mapConfig.cmake generated for a 64 bit target can be used for 32 bit 66 | # targets and vice versa. 67 | set(CMAKE_SIZEOF_VOID_P_BACKUP ${CMAKE_SIZEOF_VOID_P}) 68 | unset(CMAKE_SIZEOF_VOID_P) 69 | write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/tsl-ordered-mapConfigVersion.cmake" 70 | COMPATIBILITY SameMajorVersion) 71 | set(CMAKE_SIZEOF_VOID_P ${CMAKE_SIZEOF_VOID_P_BACKUP}) 72 | 73 | install(FILES "${CMAKE_CURRENT_BINARY_DIR}/tsl-ordered-mapConfigVersion.cmake" 74 | DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/cmake/tsl-ordered-map") 75 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Thibaut Goetghebuer-Planchon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CI](https://github.com/Tessil/ordered-map/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/Tessil/ordered-map/actions/workflows/ci.yml) 2 | 3 | ## C++ hash map and hash set which preserves the order of insertion 4 | 5 | The ordered-map library provides a hash map and a hash set which preserve the order of insertion in a way similar to Python's [OrderedDict](https://docs.python.org/3/library/collections.html#collections.OrderedDict). When iterating over the map, the values will be returned in the same order as they were inserted. 6 | 7 | The values are stored contiguously in an underlying structure, no holes in-between values even after an erase operation. By default a `std::deque` is used for this structure, but it's also possible to use a `std::vector`. This structure is directly accessible through the `values_container()` method and if the structure is a `std::vector`, a `data()` method is also provided to easily interact with C APIs. This provides fast iteration but with the drawback of an O(bucket_count) erase operation. An O(1) `pop_back()` and an O(1) `unordered_erase()` functions are available. **If ordered erase is often used, another data structure is recommended.** 8 | 9 | To resolve collisions on hashes, the library uses linear robin hood probing with backward shift deletion. 10 | 11 | The library provides a behaviour similar to a `std::deque/std::vector` with unique values but with an average time complexity of O(1) for lookups and an amortised time complexity of O(1) for insertions. This comes at the price of a little higher memory footprint (8 bytes per bucket by default). 12 | 13 | Two classes are provided: `tsl::ordered_map` and `tsl::ordered_set`. 14 | 15 | **Note**: The library uses a power of two for the size of its buckets array to take advantage of the [fast modulo](https://en.wikipedia.org/wiki/Modulo_operation#Performance_issues). For good performances, it requires the hash table to have a well-distributed hash function. If you encounter performance issues check your hash function. 16 | 17 | ### Key features 18 | 19 | - Header-only library, just add the [include](include/) directory to your include path and you are ready to go. If you use CMake, you can also use the `tsl::ordered_map` exported target from the [CMakeLists.txt](CMakeLists.txt). 20 | - Values are stored in the same order as the insertion order. The library provides a direct access to the underlying structure which stores the values. 21 | - O(1) average time complexity for lookups with performances similar to `std::unordered_map` but with faster insertions and reduced memory usage (see [benchmark](https://tessil.github.io/2016/08/29/benchmark-hopscotch-map.html) for details). 22 | - Provide random access iterators and also reverse iterators. 23 | - Support for heterogeneous lookups allowing the usage of `find` with a type different than `Key` (e.g. if you have a map that uses `std::unique_ptr` as key, you can use a `foo*` or a `std::uintptr_t` as key parameter to `find` without constructing a `std::unique_ptr`, see [example](#heterogeneous-lookups)). 24 | - If the hash is known before a lookup, it is possible to pass it as parameter to speed-up the lookup (see `precalculated_hash` parameter in [API](https://tessil.github.io/ordered-map/classtsl_1_1ordered__map.html#a7fcde27edc6697a0b127f4b1aefa8a7d)). 25 | - Support for efficient serialization and deserialization (see [example](#serialization) and the `serialize/deserialize` methods in the [API](https://tessil.github.io/ordered-map/classtsl_1_1ordered__map.html) for details). 26 | - The library can be used with exceptions disabled (through `-fno-exceptions` option on Clang and GCC, without an `/EH` option on MSVC or simply by defining `TSL_NO_EXCEPTIONS`). `std::terminate` is used in replacement of the `throw` instruction when exceptions are disabled. 27 | - API closely similar to `std::unordered_map` and `std::unordered_set`. 28 | 29 | ### Differences compared to `std::unordered_map` 30 | `tsl::ordered_map` tries to have an interface similar to `std::unordered_map`, but some differences exist. 31 | - The iterators are `RandomAccessIterator`. 32 | - Iterator invalidation behaves in a way closer to `std::vector` and `std::deque` (see [API](https://tessil.github.io/ordered-map/classtsl_1_1ordered__map.html#details) for details). If you use `std::vector` as `ValueTypeContainer`, you can use `reserve()` to preallocate some space and avoid the invalidation of the iterators on insert. 33 | - Slow `erase()` operation, it has a complexity of O(bucket_count). A faster O(1) version `unordered_erase()` exists, but it breaks the insertion order (see [API](https://tessil.github.io/ordered-map/classtsl_1_1ordered__map.html#a9f94a7889fa7fa92eea41ca63b3f98a4) for details). An O(1) `pop_back()` is also available. 34 | - The equality operators `operator==` and `operator!=` are order dependent. Two `tsl::ordered_map` with the same values but inserted in a different order don't compare equal. 35 | - For iterators, `operator*()` and `operator->()` return a reference and a pointer to `const std::pair` instead of `std::pair` making the value `T` not modifiable. To modify the value you have to call the `value()` method of the iterator to get a mutable reference. Example: 36 | ```c++ 37 | tsl::ordered_map map = {{1, 1}, {2, 1}, {3, 1}}; 38 | for(auto it = map.begin(); it != map.end(); ++it) { 39 | //it->second = 2; // Illegal 40 | it.value() = 2; // Ok 41 | } 42 | ``` 43 | - By default the map can only hold up to 232 - 1 values, that is 4 294 967 295 values. This can be raised through the `IndexType` class template parameter, check the [API](https://tessil.github.io/ordered-map/classtsl_1_1ordered__map.html#details) for details. 44 | - No support for some bucket related methods (like `bucket_size`, `bucket`, ...). 45 | 46 | Thread-safety guarantee is the same as `std::unordered_map` (i.e. possible to have multiple concurrent readers with no writer). 47 | 48 | These differences also apply between `std::unordered_set` and `tsl::ordered_set`. 49 | 50 | ### Exception Guarantees 51 | 52 | If not mentioned otherwise, functions have the strong exception guarantee, see [details](https://en.cppreference.com/w/cpp/language/exceptions). We below list cases in which this guarantee is not provided. 53 | 54 | The guarantee is only provided if `ValueContainer::emplace_back` has the strong exception guarantee (which is true for `std::vector` and `std::deque` as long as the type `T` is not a move-only type with a move constructor that may throw an exception, see [details](http://en.cppreference.com/w/cpp/container/vector/emplace_back#Exceptions)). 55 | 56 | The `tsl::ordered_map::erase_if` and `tsl::ordered_set::erase_if` functions only have the guarantee under the preconditions listed in their documentation. 57 | 58 | ### Installation 59 | 60 | To use ordered-map, just add the [include](include/) directory to your include path. It is a **header-only** library. 61 | 62 | If you use CMake, you can also use the `tsl::ordered_map` exported target from the [CMakeLists.txt](CMakeLists.txt) with `target_link_libraries`. 63 | ```cmake 64 | # Example where the ordered-map project is stored in a third-party directory 65 | add_subdirectory(third-party/ordered-map) 66 | target_link_libraries(your_target PRIVATE tsl::ordered_map) 67 | ``` 68 | 69 | If the project has been installed through `make install`, you can also use `find_package(tsl-ordered-map REQUIRED)` instead of `add_subdirectory`. 70 | 71 | The code should work with any C++11 standard-compliant compiler and has been tested with GCC 4.8.4, Clang 3.5.0 and Visual Studio 2015. 72 | 73 | To run the tests you will need the Boost Test library and CMake. 74 | 75 | ```bash 76 | git clone https://github.com/Tessil/ordered-map.git 77 | cd ordered-map/tests 78 | mkdir build 79 | cd build 80 | cmake .. 81 | cmake --build . 82 | ./tsl_ordered_map_tests 83 | ``` 84 | 85 | ### Usage 86 | 87 | The API can be found [here](https://tessil.github.io/ordered-map/). 88 | 89 | ### Example 90 | 91 | ```c++ 92 | #include 93 | #include 94 | #include 95 | #include 96 | #include 97 | 98 | int main() { 99 | tsl::ordered_map map = {{'d', 1}, {'a', 2}, {'g', 3}}; 100 | map.insert({'b', 4}); 101 | map['h'] = 5; 102 | map['e'] = 6; 103 | 104 | map.erase('a'); 105 | 106 | 107 | // {d, 1} {g, 3} {b, 4} {h, 5} {e, 6} 108 | for(const auto& key_value : map) { 109 | std::cout << "{" << key_value.first << ", " << key_value.second << "}" << std::endl; 110 | } 111 | 112 | 113 | map.unordered_erase('b'); 114 | 115 | // Break order: {d, 1} {g, 3} {e, 6} {h, 5} 116 | for(const auto& key_value : map) { 117 | std::cout << "{" << key_value.first << ", " << key_value.second << "}" << std::endl; 118 | } 119 | 120 | 121 | for(auto it = map.begin(); it != map.end(); ++it) { 122 | //it->second += 2; // Not valid. 123 | it.value() += 2; 124 | } 125 | 126 | 127 | if(map.find('d') != map.end()) { 128 | std::cout << "Found 'd'." << std::endl; 129 | } 130 | 131 | const std::size_t precalculated_hash = std::hash()('d'); 132 | // If we already know the hash beforehand, we can pass it as argument to speed-up the lookup. 133 | if(map.find('d', precalculated_hash) != map.end()) { 134 | std::cout << "Found 'd' with hash " << precalculated_hash << "." << std::endl; 135 | } 136 | 137 | 138 | tsl::ordered_set, std::equal_to, 139 | std::allocator, std::vector> set; 140 | set.reserve(6); 141 | 142 | set = {'3', '4', '9', '2'}; 143 | set.erase('2'); 144 | set.insert('1'); 145 | set.insert('\0'); 146 | 147 | set.pop_back(); 148 | set.insert({'0', '\0'}); 149 | 150 | // Get raw buffer for C API: 34910 151 | std::cout << atoi(set.data()) << std::endl; 152 | } 153 | ``` 154 | 155 | #### Heterogeneous lookup 156 | 157 | Heterogeneous overloads allow the usage of other types than `Key` for lookup and erase operations as long as the used types are hashable and comparable to `Key`. 158 | 159 | To activate the heterogeneous overloads in `tsl::ordered_map/set`, the qualified-id `KeyEqual::is_transparent` must be valid. It works the same way as for [`std::map::find`](http://en.cppreference.com/w/cpp/container/map/find). You can either use [`std::equal_to<>`](http://en.cppreference.com/w/cpp/utility/functional/equal_to_void) or define your own function object. 160 | 161 | Both `KeyEqual` and `Hash` will need to be able to deal with the different types. 162 | 163 | ```c++ 164 | #include 165 | #include 166 | #include 167 | #include 168 | 169 | 170 | 171 | struct employee { 172 | employee(int id, std::string name) : m_id(id), m_name(std::move(name)) { 173 | } 174 | 175 | // Either we include the comparators in the class and we use `std::equal_to<>`... 176 | friend bool operator==(const employee& empl, int empl_id) { 177 | return empl.m_id == empl_id; 178 | } 179 | 180 | friend bool operator==(int empl_id, const employee& empl) { 181 | return empl_id == empl.m_id; 182 | } 183 | 184 | friend bool operator==(const employee& empl1, const employee& empl2) { 185 | return empl1.m_id == empl2.m_id; 186 | } 187 | 188 | 189 | int m_id; 190 | std::string m_name; 191 | }; 192 | 193 | // ... or we implement a separate class to compare employees. 194 | struct equal_employee { 195 | using is_transparent = void; 196 | 197 | bool operator()(const employee& empl, int empl_id) const { 198 | return empl.m_id == empl_id; 199 | } 200 | 201 | bool operator()(int empl_id, const employee& empl) const { 202 | return empl_id == empl.m_id; 203 | } 204 | 205 | bool operator()(const employee& empl1, const employee& empl2) const { 206 | return empl1.m_id == empl2.m_id; 207 | } 208 | }; 209 | 210 | struct hash_employee { 211 | std::size_t operator()(const employee& empl) const { 212 | return std::hash()(empl.m_id); 213 | } 214 | 215 | std::size_t operator()(int id) const { 216 | return std::hash()(id); 217 | } 218 | }; 219 | 220 | 221 | int main() { 222 | // Use std::equal_to<> which will automatically deduce and forward the parameters 223 | tsl::ordered_map> map; 224 | map.insert({employee(1, "John Doe"), 2001}); 225 | map.insert({employee(2, "Jane Doe"), 2002}); 226 | map.insert({employee(3, "John Smith"), 2003}); 227 | 228 | // John Smith 2003 229 | auto it = map.find(3); 230 | if(it != map.end()) { 231 | std::cout << it->first.m_name << " " << it->second << std::endl; 232 | } 233 | 234 | map.erase(1); 235 | 236 | 237 | 238 | // Use a custom KeyEqual which has an is_transparent member type 239 | tsl::ordered_map map2; 240 | map2.insert({employee(4, "Johnny Doe"), 2004}); 241 | 242 | // 2004 243 | std::cout << map2.at(4) << std::endl; 244 | } 245 | ``` 246 | 247 | #### Serialization 248 | 249 | The library provides an efficient way to serialize and deserialize a map or a set so that it can be saved to a file or send through the network. 250 | To do so, it requires the user to provide a function object for both serialization and deserialization. 251 | 252 | ```c++ 253 | struct serializer { 254 | // Must support the following types for U: std::uint64_t, float 255 | // and std::pair if a map is used or Key for a set. 256 | template 257 | void operator()(const U& value); 258 | }; 259 | ``` 260 | 261 | ```c++ 262 | struct deserializer { 263 | // Must support the following types for U: std::uint64_t, float 264 | // and std::pair if a map is used or Key for a set. 265 | template 266 | U operator()(); 267 | }; 268 | ``` 269 | 270 | Note that the implementation leaves binary compatibility (endianness, float binary representation, size of int, ...) of the types it serializes/deserializes in the hands of the provided function objects if compatibility is required. 271 | 272 | More details regarding the `serialize` and `deserialize` methods can be found in the [API](https://tessil.github.io/ordered-map/classtsl_1_1ordered__map.html). 273 | 274 | ```c++ 275 | #include 276 | #include 277 | #include 278 | #include 279 | #include 280 | 281 | 282 | class serializer { 283 | public: 284 | explicit serializer(const char* file_name) { 285 | m_ostream.exceptions(m_ostream.badbit | m_ostream.failbit); 286 | m_ostream.open(file_name, std::ios::binary); 287 | } 288 | 289 | template::value>::type* = nullptr> 291 | void operator()(const T& value) { 292 | m_ostream.write(reinterpret_cast(&value), sizeof(T)); 293 | } 294 | 295 | void operator()(const std::pair& value) { 296 | (*this)(value.first); 297 | (*this)(value.second); 298 | } 299 | 300 | private: 301 | std::ofstream m_ostream; 302 | }; 303 | 304 | class deserializer { 305 | public: 306 | explicit deserializer(const char* file_name) { 307 | m_istream.exceptions(m_istream.badbit | m_istream.failbit | m_istream.eofbit); 308 | m_istream.open(file_name, std::ios::binary); 309 | } 310 | 311 | template 312 | T operator()() { 313 | T value; 314 | deserialize(value); 315 | 316 | return value; 317 | } 318 | 319 | private: 320 | template::value>::type* = nullptr> 322 | void deserialize(T& value) { 323 | m_istream.read(reinterpret_cast(&value), sizeof(T)); 324 | } 325 | 326 | void deserialize(std::pair& value) { 327 | deserialize(value.first); 328 | deserialize(value.second); 329 | } 330 | 331 | private: 332 | std::ifstream m_istream; 333 | }; 334 | 335 | 336 | int main() { 337 | const tsl::ordered_map map = {{1, -1}, {2, -2}, {3, -3}, {4, -4}}; 338 | 339 | 340 | const char* file_name = "ordered_map.data"; 341 | { 342 | serializer serial(file_name); 343 | map.serialize(serial); 344 | } 345 | 346 | { 347 | deserializer dserial(file_name); 348 | auto map_deserialized = tsl::ordered_map::deserialize(dserial); 349 | 350 | assert(map == map_deserialized); 351 | } 352 | 353 | { 354 | deserializer dserial(file_name); 355 | 356 | /** 357 | * If the serialized and deserialized map are hash compatibles (see conditions in API), 358 | * setting the argument to true speed-up the deserialization process as we don't have 359 | * to recalculate the hash of each key. We also know how much space each bucket needs. 360 | */ 361 | const bool hash_compatible = true; 362 | auto map_deserialized = 363 | tsl::ordered_map::deserialize(dserial, hash_compatible); 364 | 365 | assert(map == map_deserialized); 366 | } 367 | } 368 | ``` 369 | 370 | ##### Serialization with Boost Serialization and compression with zlib 371 | 372 | It's possible to use a serialization library to avoid the boilerplate. 373 | 374 | The following example uses Boost Serialization with the Boost zlib compression stream to reduce the size of the resulting serialized file. The example requires C++20 due to the usage of the template parameter list syntax in lambdas, but it can be adapted to less recent versions. 375 | 376 | ```c++ 377 | #include 378 | #include 379 | #include 380 | #include 381 | #include 382 | #include 383 | #include 384 | #include 385 | #include 386 | #include 387 | 388 | 389 | namespace boost { namespace serialization { 390 | template 391 | void serialize(Archive & ar, tsl::ordered_map& map, const unsigned int version) { 392 | split_free(ar, map, version); 393 | } 394 | 395 | template 396 | void save(Archive & ar, const tsl::ordered_map& map, const unsigned int /*version*/) { 397 | auto serializer = [&ar](const auto& v) { ar & v; }; 398 | map.serialize(serializer); 399 | } 400 | 401 | template 402 | void load(Archive & ar, tsl::ordered_map& map, const unsigned int /*version*/) { 403 | auto deserializer = [&ar]() { U u; ar & u; return u; }; 404 | map = tsl::ordered_map::deserialize(deserializer); 405 | } 406 | }} 407 | 408 | 409 | int main() { 410 | tsl::ordered_map map = {{1, -1}, {2, -2}, {3, -3}, {4, -4}}; 411 | 412 | 413 | const char* file_name = "ordered_map.data"; 414 | { 415 | std::ofstream ofs; 416 | ofs.exceptions(ofs.badbit | ofs.failbit); 417 | ofs.open(file_name, std::ios::binary); 418 | 419 | boost::iostreams::filtering_ostream fo; 420 | fo.push(boost::iostreams::zlib_compressor()); 421 | fo.push(ofs); 422 | 423 | boost::archive::binary_oarchive oa(fo); 424 | 425 | oa << map; 426 | } 427 | 428 | { 429 | std::ifstream ifs; 430 | ifs.exceptions(ifs.badbit | ifs.failbit | ifs.eofbit); 431 | ifs.open(file_name, std::ios::binary); 432 | 433 | boost::iostreams::filtering_istream fi; 434 | fi.push(boost::iostreams::zlib_decompressor()); 435 | fi.push(ifs); 436 | 437 | boost::archive::binary_iarchive ia(fi); 438 | 439 | tsl::ordered_map map_deserialized; 440 | ia >> map_deserialized; 441 | 442 | assert(map == map_deserialized); 443 | } 444 | } 445 | ``` 446 | 447 | ### License 448 | 449 | The code is licensed under the MIT license, see the [LICENSE file](LICENSE) for details. 450 | -------------------------------------------------------------------------------- /cmake/tsl-ordered-mapConfig.cmake.in: -------------------------------------------------------------------------------- 1 | # This module sets the following variables: 2 | # * tsl-ordered-map_FOUND - true if tsl-ordered-map found on the system 3 | # * tsl-ordered-map_INCLUDE_DIRS - the directory containing tsl-ordered-map headers 4 | @PACKAGE_INIT@ 5 | 6 | if(NOT TARGET tsl::ordered_map) 7 | include("${CMAKE_CURRENT_LIST_DIR}/tsl-ordered-mapTargets.cmake") 8 | get_target_property(tsl-ordered-map_INCLUDE_DIRS tsl::ordered_map INTERFACE_INCLUDE_DIRECTORIES) 9 | endif() 10 | -------------------------------------------------------------------------------- /include/tsl/ordered_hash.h: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2017 Thibaut Goetghebuer-Planchon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | #ifndef TSL_ORDERED_HASH_H 25 | #define TSL_ORDERED_HASH_H 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | /** 45 | * Macros for compatibility with GCC 4.8 46 | */ 47 | #if (defined(__GNUC__) && (__GNUC__ == 4) && (__GNUC_MINOR__ < 9)) 48 | #define TSL_OH_NO_CONTAINER_ERASE_CONST_ITERATOR 49 | #define TSL_OH_NO_CONTAINER_EMPLACE_CONST_ITERATOR 50 | #endif 51 | 52 | /** 53 | * Only activate tsl_oh_assert if TSL_DEBUG is defined. 54 | * This way we avoid the performance hit when NDEBUG is not defined with assert 55 | * as tsl_oh_assert is used a lot (people usually compile with "-O3" and not 56 | * "-O3 -DNDEBUG"). 57 | */ 58 | #ifdef TSL_DEBUG 59 | #define tsl_oh_assert(expr) assert(expr) 60 | #else 61 | #define tsl_oh_assert(expr) (static_cast(0)) 62 | #endif 63 | 64 | /** 65 | * If exceptions are enabled, throw the exception passed in parameter, otherwise 66 | * call std::terminate. 67 | */ 68 | #if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || \ 69 | (defined(_MSC_VER) && defined(_CPPUNWIND))) && \ 70 | !defined(TSL_NO_EXCEPTIONS) 71 | #define TSL_OH_THROW_OR_TERMINATE(ex, msg) throw ex(msg) 72 | #else 73 | #define TSL_OH_NO_EXCEPTIONS 74 | #ifdef TSL_DEBUG 75 | #include 76 | #define TSL_OH_THROW_OR_TERMINATE(ex, msg) \ 77 | do { \ 78 | std::cerr << msg << std::endl; \ 79 | std::terminate(); \ 80 | } while (0) 81 | #else 82 | #define TSL_OH_THROW_OR_TERMINATE(ex, msg) std::terminate() 83 | #endif 84 | #endif 85 | 86 | namespace tsl { 87 | 88 | namespace detail_ordered_hash { 89 | 90 | template 91 | struct make_void { 92 | using type = void; 93 | }; 94 | 95 | template 96 | struct has_is_transparent : std::false_type {}; 97 | 98 | template 99 | struct has_is_transparent::type> 101 | : std::true_type {}; 102 | 103 | template 104 | struct is_vector : std::false_type {}; 105 | 106 | template 107 | struct is_vector>::value>::type> 111 | : std::true_type {}; 112 | 113 | // Only available in C++17, we need to be compatible with C++11 114 | template 115 | const T& clamp(const T& v, const T& lo, const T& hi) { 116 | return std::min(hi, std::max(lo, v)); 117 | } 118 | 119 | template 120 | static T numeric_cast(U value, 121 | const char* error_message = "numeric_cast() failed.") { 122 | T ret = static_cast(value); 123 | if (static_cast(ret) != value) { 124 | TSL_OH_THROW_OR_TERMINATE(std::runtime_error, error_message); 125 | } 126 | 127 | const bool is_same_signedness = 128 | (std::is_unsigned::value && std::is_unsigned::value) || 129 | (std::is_signed::value && std::is_signed::value); 130 | if (!is_same_signedness && (ret < T{}) != (value < U{})) { 131 | TSL_OH_THROW_OR_TERMINATE(std::runtime_error, error_message); 132 | } 133 | 134 | return ret; 135 | } 136 | 137 | /** 138 | * Fixed size type used to represent size_type values on serialization. Need to 139 | * be big enough to represent a std::size_t on 32 and 64 bits platforms, and 140 | * must be the same size on both platforms. 141 | */ 142 | using slz_size_type = std::uint64_t; 143 | static_assert(std::numeric_limits::max() >= 144 | std::numeric_limits::max(), 145 | "slz_size_type must be >= std::size_t"); 146 | 147 | template 148 | static T deserialize_value(Deserializer& deserializer) { 149 | // MSVC < 2017 is not conformant, circumvent the problem by removing the 150 | // template keyword 151 | #if defined(_MSC_VER) && _MSC_VER < 1910 152 | return deserializer.Deserializer::operator()(); 153 | #else 154 | return deserializer.Deserializer::template operator()(); 155 | #endif 156 | } 157 | 158 | /** 159 | * Each bucket entry stores an index which is the index in m_values 160 | * corresponding to the bucket's value and a hash (which may be truncated to 32 161 | * bits depending on IndexType) corresponding to the hash of the value. 162 | * 163 | * The size of IndexType limits the size of the hash table to 164 | * std::numeric_limits::max() - 1 elements (-1 due to a reserved 165 | * value used to mark a bucket as empty). 166 | */ 167 | template 168 | class bucket_entry { 169 | static_assert(std::is_unsigned::value, 170 | "IndexType must be an unsigned value."); 171 | static_assert(std::numeric_limits::max() <= 172 | std::numeric_limits::max(), 173 | "std::numeric_limits::max() must be <= " 174 | "std::numeric_limits::max()."); 175 | 176 | public: 177 | using index_type = IndexType; 178 | using truncated_hash_type = typename std::conditional< 179 | std::numeric_limits::max() <= 180 | std::numeric_limits::max(), 181 | std::uint_least32_t, std::size_t>::type; 182 | 183 | bucket_entry() noexcept : m_index(EMPTY_MARKER_INDEX), m_hash(0) {} 184 | 185 | bool empty() const noexcept { return m_index == EMPTY_MARKER_INDEX; } 186 | 187 | void clear() noexcept { m_index = EMPTY_MARKER_INDEX; } 188 | 189 | index_type index() const noexcept { 190 | tsl_oh_assert(!empty()); 191 | return m_index; 192 | } 193 | 194 | index_type& index_ref() noexcept { 195 | tsl_oh_assert(!empty()); 196 | return m_index; 197 | } 198 | 199 | void set_index(index_type index) noexcept { 200 | tsl_oh_assert(index <= max_size()); 201 | 202 | m_index = index; 203 | } 204 | 205 | truncated_hash_type truncated_hash() const noexcept { 206 | tsl_oh_assert(!empty()); 207 | return m_hash; 208 | } 209 | 210 | truncated_hash_type& truncated_hash_ref() noexcept { 211 | tsl_oh_assert(!empty()); 212 | return m_hash; 213 | } 214 | 215 | void set_hash(std::size_t hash) noexcept { m_hash = truncate_hash(hash); } 216 | 217 | template 218 | void serialize(Serializer& serializer) const { 219 | const slz_size_type index = m_index; 220 | serializer(index); 221 | 222 | const slz_size_type hash = m_hash; 223 | serializer(hash); 224 | } 225 | 226 | template 227 | static bucket_entry deserialize(Deserializer& deserializer) { 228 | const slz_size_type index = deserialize_value(deserializer); 229 | const slz_size_type hash = deserialize_value(deserializer); 230 | 231 | bucket_entry bentry; 232 | bentry.m_index = 233 | numeric_cast(index, "Deserialized index is too big."); 234 | bentry.m_hash = numeric_cast( 235 | hash, "Deserialized hash is too big."); 236 | 237 | return bentry; 238 | } 239 | 240 | static truncated_hash_type truncate_hash(std::size_t hash) noexcept { 241 | return truncated_hash_type(hash); 242 | } 243 | 244 | static std::size_t max_size() noexcept { 245 | return static_cast(std::numeric_limits::max()) - 246 | NB_RESERVED_INDEXES; 247 | } 248 | 249 | private: 250 | static const index_type EMPTY_MARKER_INDEX = 251 | std::numeric_limits::max(); 252 | static const std::size_t NB_RESERVED_INDEXES = 1; 253 | 254 | index_type m_index; 255 | truncated_hash_type m_hash; 256 | }; 257 | 258 | /** 259 | * Internal common class used by ordered_map and ordered_set. 260 | * 261 | * ValueType is what will be stored by ordered_hash (usually std::pair 262 | * for map and Key for set). 263 | * 264 | * KeySelect should be a FunctionObject which takes a ValueType in parameter and 265 | * return a reference to the key. 266 | * 267 | * ValueSelect should be a FunctionObject which takes a ValueType in parameter 268 | * and return a reference to the value. ValueSelect should be void if there is 269 | * no value (in set for example). 270 | * 271 | * ValueTypeContainer is the container which will be used to store ValueType 272 | * values. Usually a std::deque or std::vector. 274 | * 275 | * 276 | * 277 | * The ordered_hash structure is a hash table which preserves the order of 278 | * insertion of the elements. To do so, it stores the values in the 279 | * ValueTypeContainer (m_values) using emplace_back at each insertion of a new 280 | * element. Another structure (m_buckets of type std::vector) will 281 | * serve as buckets array for the hash table part. Each bucket stores an index 282 | * which corresponds to the index in m_values where the bucket's value is and 283 | * the (truncated) hash of this value. An index is used instead of a pointer to 284 | * the value to reduce the size of each bucket entry. 285 | * 286 | * To resolve collisions in the buckets array, the structures use robin hood 287 | * linear probing with backward shift deletion. 288 | */ 289 | template 292 | class ordered_hash : private Hash, private KeyEqual { 293 | private: 294 | template 295 | using has_mapped_type = 296 | typename std::integral_constant::value>; 297 | 298 | static_assert( 299 | std::is_same::value, 300 | "ValueTypeContainer::value_type != ValueType. " 301 | "Check that the ValueTypeContainer has 'Key' as type for a set or " 302 | "'std::pair' as type for a map."); 303 | 304 | static_assert(std::is_same::value, 306 | "ValueTypeContainer::allocator_type != Allocator. " 307 | "Check that the allocator for ValueTypeContainer is the same " 308 | "as Allocator."); 309 | 310 | static_assert(std::is_same::value, 311 | "Allocator::value_type != ValueType. " 312 | "Check that the allocator has 'Key' as type for a set or " 313 | "'std::pair' as type for a map."); 314 | 315 | public: 316 | template 317 | class ordered_iterator; 318 | 319 | using key_type = typename KeySelect::key_type; 320 | using value_type = ValueType; 321 | using size_type = std::size_t; 322 | using difference_type = std::ptrdiff_t; 323 | using hasher = Hash; 324 | using key_equal = KeyEqual; 325 | using allocator_type = Allocator; 326 | using reference = value_type&; 327 | using const_reference = const value_type&; 328 | using pointer = value_type*; 329 | using const_pointer = const value_type*; 330 | using iterator = ordered_iterator; 331 | using const_iterator = ordered_iterator; 332 | using reverse_iterator = std::reverse_iterator; 333 | using const_reverse_iterator = std::reverse_iterator; 334 | 335 | using values_container_type = ValueTypeContainer; 336 | 337 | public: 338 | template 339 | class ordered_iterator { 340 | friend class ordered_hash; 341 | 342 | private: 343 | using iterator = typename std::conditional< 344 | IsConst, typename values_container_type::const_iterator, 345 | typename values_container_type::iterator>::type; 346 | 347 | ordered_iterator(iterator it) noexcept : m_iterator(it) {} 348 | 349 | public: 350 | using iterator_category = std::random_access_iterator_tag; 351 | using value_type = const typename ordered_hash::value_type; 352 | using difference_type = typename iterator::difference_type; 353 | using reference = value_type&; 354 | using pointer = value_type*; 355 | 356 | ordered_iterator() noexcept {} 357 | 358 | // Copy constructor from iterator to const_iterator. 359 | template ::type* = nullptr> 361 | ordered_iterator(const ordered_iterator& other) noexcept 362 | : m_iterator(other.m_iterator) {} 363 | 364 | ordered_iterator(const ordered_iterator& other) = default; 365 | ordered_iterator(ordered_iterator&& other) = default; 366 | ordered_iterator& operator=(const ordered_iterator& other) = default; 367 | ordered_iterator& operator=(ordered_iterator&& other) = default; 368 | 369 | const typename ordered_hash::key_type& key() const { 370 | return KeySelect()(*m_iterator); 371 | } 372 | 373 | template ::value && 375 | IsConst>::type* = nullptr> 376 | const typename U::value_type& value() const { 377 | return U()(*m_iterator); 378 | } 379 | 380 | template ::value && 382 | !IsConst>::type* = nullptr> 383 | typename U::value_type& value() { 384 | return U()(*m_iterator); 385 | } 386 | 387 | reference operator*() const { return *m_iterator; } 388 | pointer operator->() const { return m_iterator.operator->(); } 389 | 390 | ordered_iterator& operator++() { 391 | ++m_iterator; 392 | return *this; 393 | } 394 | ordered_iterator& operator--() { 395 | --m_iterator; 396 | return *this; 397 | } 398 | 399 | ordered_iterator operator++(int) { 400 | ordered_iterator tmp(*this); 401 | ++(*this); 402 | return tmp; 403 | } 404 | ordered_iterator operator--(int) { 405 | ordered_iterator tmp(*this); 406 | --(*this); 407 | return tmp; 408 | } 409 | 410 | reference operator[](difference_type n) const { return m_iterator[n]; } 411 | 412 | ordered_iterator& operator+=(difference_type n) { 413 | m_iterator += n; 414 | return *this; 415 | } 416 | ordered_iterator& operator-=(difference_type n) { 417 | m_iterator -= n; 418 | return *this; 419 | } 420 | 421 | ordered_iterator operator+(difference_type n) { 422 | ordered_iterator tmp(*this); 423 | tmp += n; 424 | return tmp; 425 | } 426 | ordered_iterator operator-(difference_type n) { 427 | ordered_iterator tmp(*this); 428 | tmp -= n; 429 | return tmp; 430 | } 431 | 432 | friend bool operator==(const ordered_iterator& lhs, 433 | const ordered_iterator& rhs) { 434 | return lhs.m_iterator == rhs.m_iterator; 435 | } 436 | 437 | friend bool operator!=(const ordered_iterator& lhs, 438 | const ordered_iterator& rhs) { 439 | return lhs.m_iterator != rhs.m_iterator; 440 | } 441 | 442 | friend bool operator<(const ordered_iterator& lhs, 443 | const ordered_iterator& rhs) { 444 | return lhs.m_iterator < rhs.m_iterator; 445 | } 446 | 447 | friend bool operator>(const ordered_iterator& lhs, 448 | const ordered_iterator& rhs) { 449 | return lhs.m_iterator > rhs.m_iterator; 450 | } 451 | 452 | friend bool operator<=(const ordered_iterator& lhs, 453 | const ordered_iterator& rhs) { 454 | return lhs.m_iterator <= rhs.m_iterator; 455 | } 456 | 457 | friend bool operator>=(const ordered_iterator& lhs, 458 | const ordered_iterator& rhs) { 459 | return lhs.m_iterator >= rhs.m_iterator; 460 | } 461 | 462 | friend ordered_iterator operator+(difference_type n, 463 | const ordered_iterator& it) { 464 | return n + it.m_iterator; 465 | } 466 | 467 | friend difference_type operator-(const ordered_iterator& lhs, 468 | const ordered_iterator& rhs) { 469 | return lhs.m_iterator - rhs.m_iterator; 470 | } 471 | 472 | private: 473 | iterator m_iterator; 474 | }; 475 | 476 | private: 477 | using bucket_entry = tsl::detail_ordered_hash::bucket_entry; 478 | 479 | using buckets_container_allocator = typename std::allocator_traits< 480 | allocator_type>::template rebind_alloc; 481 | 482 | using buckets_container_type = 483 | std::vector; 484 | 485 | using truncated_hash_type = typename bucket_entry::truncated_hash_type; 486 | using index_type = typename bucket_entry::index_type; 487 | 488 | public: 489 | ordered_hash(size_type bucket_count, const Hash& hash, const KeyEqual& equal, 490 | const Allocator& alloc, float max_load_factor) 491 | : Hash(hash), 492 | KeyEqual(equal), 493 | m_buckets_data(alloc), 494 | m_buckets(static_empty_bucket_ptr()), 495 | m_hash_mask(0), 496 | m_values(alloc), 497 | m_grow_on_next_insert(false) { 498 | if (bucket_count > max_bucket_count()) { 499 | TSL_OH_THROW_OR_TERMINATE(std::length_error, 500 | "The map exceeds its maximum size."); 501 | } 502 | 503 | if (bucket_count > 0) { 504 | bucket_count = round_up_to_power_of_two(bucket_count); 505 | 506 | m_buckets_data.resize(bucket_count); 507 | m_buckets = m_buckets_data.data(), m_hash_mask = bucket_count - 1; 508 | } 509 | 510 | this->max_load_factor(max_load_factor); 511 | } 512 | 513 | ordered_hash(const ordered_hash& other) 514 | : Hash(other), 515 | KeyEqual(other), 516 | m_buckets_data(other.m_buckets_data), 517 | m_buckets(m_buckets_data.empty() ? static_empty_bucket_ptr() 518 | : m_buckets_data.data()), 519 | m_hash_mask(other.m_hash_mask), 520 | m_values(other.m_values), 521 | m_load_threshold(other.m_load_threshold), 522 | m_max_load_factor(other.m_max_load_factor), 523 | m_grow_on_next_insert(other.m_grow_on_next_insert) {} 524 | 525 | ordered_hash(ordered_hash&& other) noexcept( 526 | std::is_nothrow_move_constructible< 527 | Hash>::value&& std::is_nothrow_move_constructible::value&& 528 | std::is_nothrow_move_constructible::value&& 529 | std::is_nothrow_move_constructible::value) 530 | : Hash(std::move(static_cast(other))), 531 | KeyEqual(std::move(static_cast(other))), 532 | m_buckets_data(std::move(other.m_buckets_data)), 533 | m_buckets(m_buckets_data.empty() ? static_empty_bucket_ptr() 534 | : m_buckets_data.data()), 535 | m_hash_mask(other.m_hash_mask), 536 | m_values(std::move(other.m_values)), 537 | m_load_threshold(other.m_load_threshold), 538 | m_max_load_factor(other.m_max_load_factor), 539 | m_grow_on_next_insert(other.m_grow_on_next_insert) { 540 | other.m_buckets_data.clear(); 541 | other.m_buckets = static_empty_bucket_ptr(); 542 | other.m_hash_mask = 0; 543 | other.m_values.clear(); 544 | other.m_load_threshold = 0; 545 | other.m_grow_on_next_insert = false; 546 | } 547 | 548 | ordered_hash& operator=(const ordered_hash& other) { 549 | if (&other != this) { 550 | Hash::operator=(other); 551 | KeyEqual::operator=(other); 552 | 553 | m_buckets_data = other.m_buckets_data; 554 | m_buckets = m_buckets_data.empty() ? static_empty_bucket_ptr() 555 | : m_buckets_data.data(); 556 | 557 | m_hash_mask = other.m_hash_mask; 558 | m_values = other.m_values; 559 | m_load_threshold = other.m_load_threshold; 560 | m_max_load_factor = other.m_max_load_factor; 561 | m_grow_on_next_insert = other.m_grow_on_next_insert; 562 | } 563 | 564 | return *this; 565 | } 566 | 567 | ordered_hash& operator=(ordered_hash&& other) { 568 | other.swap(*this); 569 | other.clear(); 570 | 571 | return *this; 572 | } 573 | 574 | allocator_type get_allocator() const { return m_values.get_allocator(); } 575 | 576 | /* 577 | * Iterators 578 | */ 579 | iterator begin() noexcept { return iterator(m_values.begin()); } 580 | 581 | const_iterator begin() const noexcept { return cbegin(); } 582 | 583 | const_iterator cbegin() const noexcept { 584 | return const_iterator(m_values.cbegin()); 585 | } 586 | 587 | iterator end() noexcept { return iterator(m_values.end()); } 588 | 589 | const_iterator end() const noexcept { return cend(); } 590 | 591 | const_iterator cend() const noexcept { 592 | return const_iterator(m_values.cend()); 593 | } 594 | 595 | reverse_iterator rbegin() noexcept { 596 | return reverse_iterator(m_values.end()); 597 | } 598 | 599 | const_reverse_iterator rbegin() const noexcept { return rcbegin(); } 600 | 601 | const_reverse_iterator rcbegin() const noexcept { 602 | return const_reverse_iterator(m_values.cend()); 603 | } 604 | 605 | reverse_iterator rend() noexcept { 606 | return reverse_iterator(m_values.begin()); 607 | } 608 | 609 | const_reverse_iterator rend() const noexcept { return rcend(); } 610 | 611 | const_reverse_iterator rcend() const noexcept { 612 | return const_reverse_iterator(m_values.cbegin()); 613 | } 614 | 615 | /* 616 | * Capacity 617 | */ 618 | bool empty() const noexcept { return m_values.empty(); } 619 | 620 | size_type size() const noexcept { return m_values.size(); } 621 | 622 | size_type max_size() const noexcept { 623 | return std::min(bucket_entry::max_size(), m_values.max_size()); 624 | } 625 | 626 | /* 627 | * Modifiers 628 | */ 629 | void clear() noexcept { 630 | for (auto& bucket : m_buckets_data) { 631 | bucket.clear(); 632 | } 633 | 634 | m_values.clear(); 635 | m_grow_on_next_insert = false; 636 | } 637 | 638 | template 639 | std::pair insert(P&& value) { 640 | return insert_impl(KeySelect()(value), std::forward

(value)); 641 | } 642 | 643 | template 644 | iterator insert_hint(const_iterator hint, P&& value) { 645 | if (hint != cend() && 646 | compare_keys(KeySelect()(*hint), KeySelect()(value))) { 647 | return mutable_iterator(hint); 648 | } 649 | 650 | return insert(std::forward

(value)).first; 651 | } 652 | 653 | template 654 | void insert(InputIt first, InputIt last) { 655 | if (std::is_base_of< 656 | std::forward_iterator_tag, 657 | typename std::iterator_traits::iterator_category>::value) { 658 | const auto nb_elements_insert = std::distance(first, last); 659 | const size_type nb_free_buckets = m_load_threshold - size(); 660 | tsl_oh_assert(m_load_threshold >= size()); 661 | 662 | if (nb_elements_insert > 0 && 663 | nb_free_buckets < size_type(nb_elements_insert)) { 664 | reserve(size() + size_type(nb_elements_insert)); 665 | } 666 | } 667 | 668 | for (; first != last; ++first) { 669 | insert(*first); 670 | } 671 | } 672 | 673 | template 674 | std::pair insert_or_assign(K&& key, M&& value) { 675 | auto it = try_emplace(std::forward(key), std::forward(value)); 676 | if (!it.second) { 677 | it.first.value() = std::forward(value); 678 | } 679 | 680 | return it; 681 | } 682 | 683 | template 684 | iterator insert_or_assign(const_iterator hint, K&& key, M&& obj) { 685 | if (hint != cend() && compare_keys(KeySelect()(*hint), key)) { 686 | auto it = mutable_iterator(hint); 687 | it.value() = std::forward(obj); 688 | 689 | return it; 690 | } 691 | 692 | return insert_or_assign(std::forward(key), std::forward(obj)).first; 693 | } 694 | 695 | template 696 | std::pair emplace(Args&&... args) { 697 | return insert(value_type(std::forward(args)...)); 698 | } 699 | 700 | template 701 | iterator emplace_hint(const_iterator hint, Args&&... args) { 702 | return insert_hint(hint, value_type(std::forward(args)...)); 703 | } 704 | 705 | template 706 | std::pair try_emplace(K&& key, Args&&... value_args) { 707 | return insert_impl( 708 | key, std::piecewise_construct, 709 | std::forward_as_tuple(std::forward(key)), 710 | std::forward_as_tuple(std::forward(value_args)...)); 711 | } 712 | 713 | template 714 | iterator try_emplace_hint(const_iterator hint, K&& key, Args&&... args) { 715 | if (hint != cend() && compare_keys(KeySelect()(*hint), key)) { 716 | return mutable_iterator(hint); 717 | } 718 | 719 | return try_emplace(std::forward(key), std::forward(args)...).first; 720 | } 721 | 722 | /** 723 | * Here to avoid `template size_type erase(const K& key)` being used 724 | * when we use an `iterator` instead of a `const_iterator`. 725 | */ 726 | iterator erase(iterator pos) { return erase(const_iterator(pos)); } 727 | 728 | iterator erase(const_iterator pos) { 729 | tsl_oh_assert(pos != cend()); 730 | 731 | const std::size_t index_erase = iterator_to_index(pos); 732 | 733 | auto it_bucket = find_key(pos.key(), hash_key(pos.key())); 734 | tsl_oh_assert(it_bucket != m_buckets_data.end()); 735 | 736 | erase_value_from_bucket(it_bucket); 737 | 738 | /* 739 | * One element was removed from m_values, due to the left shift the next 740 | * element is now at the position of the previous element (or end if none). 741 | */ 742 | return begin() + index_erase; 743 | } 744 | 745 | iterator erase(const_iterator first, const_iterator last) { 746 | if (first == last) { 747 | return mutable_iterator(first); 748 | } 749 | 750 | tsl_oh_assert(std::distance(first, last) > 0); 751 | const std::size_t start_index = iterator_to_index(first); 752 | const std::size_t nb_values = std::size_t(std::distance(first, last)); 753 | const std::size_t end_index = start_index + nb_values; 754 | 755 | // Delete all values 756 | #ifdef TSL_OH_NO_CONTAINER_ERASE_CONST_ITERATOR 757 | auto next_it = m_values.erase(mutable_iterator(first).m_iterator, 758 | mutable_iterator(last).m_iterator); 759 | #else 760 | auto next_it = m_values.erase(first.m_iterator, last.m_iterator); 761 | #endif 762 | 763 | /* 764 | * Mark the buckets corresponding to the values as empty and do a backward 765 | * shift. 766 | * 767 | * Also, the erase operation on m_values has shifted all the values on the 768 | * right of last.m_iterator. Adapt the indexes for these values. 769 | */ 770 | std::size_t ibucket = 0; 771 | while (ibucket < m_buckets_data.size()) { 772 | if (m_buckets[ibucket].empty()) { 773 | ibucket++; 774 | } else if (m_buckets[ibucket].index() >= start_index && 775 | m_buckets[ibucket].index() < end_index) { 776 | m_buckets[ibucket].clear(); 777 | backward_shift(ibucket); 778 | // Don't increment ibucket, backward_shift may have replaced current 779 | // bucket. 780 | } else if (m_buckets[ibucket].index() >= end_index) { 781 | m_buckets[ibucket].set_index( 782 | index_type(m_buckets[ibucket].index() - nb_values)); 783 | ibucket++; 784 | } else { 785 | ibucket++; 786 | } 787 | } 788 | 789 | return iterator(next_it); 790 | } 791 | 792 | template 793 | size_type erase(const K& key) { 794 | return erase(key, hash_key(key)); 795 | } 796 | 797 | template 798 | size_type erase(const K& key, std::size_t hash) { 799 | return erase_impl(key, hash); 800 | } 801 | 802 | void swap(ordered_hash& other) { 803 | using std::swap; 804 | 805 | swap(static_cast(*this), static_cast(other)); 806 | swap(static_cast(*this), static_cast(other)); 807 | swap(m_buckets_data, other.m_buckets_data); 808 | swap(m_buckets, other.m_buckets); 809 | swap(m_hash_mask, other.m_hash_mask); 810 | swap(m_values, other.m_values); 811 | swap(m_load_threshold, other.m_load_threshold); 812 | swap(m_max_load_factor, other.m_max_load_factor); 813 | swap(m_grow_on_next_insert, other.m_grow_on_next_insert); 814 | } 815 | 816 | /* 817 | * Lookup 818 | */ 819 | template ::value>::type* = nullptr> 821 | typename U::value_type& at(const K& key) { 822 | return at(key, hash_key(key)); 823 | } 824 | 825 | template ::value>::type* = nullptr> 827 | typename U::value_type& at(const K& key, std::size_t hash) { 828 | return const_cast( 829 | static_cast(this)->at(key, hash)); 830 | } 831 | 832 | template ::value>::type* = nullptr> 834 | const typename U::value_type& at(const K& key) const { 835 | return at(key, hash_key(key)); 836 | } 837 | 838 | template ::value>::type* = nullptr> 840 | const typename U::value_type& at(const K& key, std::size_t hash) const { 841 | auto it = find(key, hash); 842 | if (it != end()) { 843 | return it.value(); 844 | } else { 845 | TSL_OH_THROW_OR_TERMINATE(std::out_of_range, "Couldn't find the key."); 846 | } 847 | } 848 | 849 | template ::value>::type* = nullptr> 851 | typename U::value_type& operator[](K&& key) { 852 | return try_emplace(std::forward(key)).first.value(); 853 | } 854 | 855 | template 856 | size_type count(const K& key) const { 857 | return count(key, hash_key(key)); 858 | } 859 | 860 | template 861 | size_type count(const K& key, std::size_t hash) const { 862 | if (find(key, hash) == cend()) { 863 | return 0; 864 | } else { 865 | return 1; 866 | } 867 | } 868 | 869 | template 870 | iterator find(const K& key) { 871 | return find(key, hash_key(key)); 872 | } 873 | 874 | template 875 | iterator find(const K& key, std::size_t hash) { 876 | auto it_bucket = find_key(key, hash); 877 | return (it_bucket != m_buckets_data.end()) 878 | ? iterator(m_values.begin() + it_bucket->index()) 879 | : end(); 880 | } 881 | 882 | template 883 | const_iterator find(const K& key) const { 884 | return find(key, hash_key(key)); 885 | } 886 | 887 | template 888 | const_iterator find(const K& key, std::size_t hash) const { 889 | auto it_bucket = find_key(key, hash); 890 | return (it_bucket != m_buckets_data.cend()) 891 | ? const_iterator(m_values.begin() + it_bucket->index()) 892 | : end(); 893 | } 894 | 895 | template 896 | bool contains(const K& key) const { 897 | return contains(key, hash_key(key)); 898 | } 899 | 900 | template 901 | bool contains(const K& key, std::size_t hash) const { 902 | return find(key, hash) != cend(); 903 | } 904 | 905 | template 906 | std::pair equal_range(const K& key) { 907 | return equal_range(key, hash_key(key)); 908 | } 909 | 910 | template 911 | std::pair equal_range(const K& key, std::size_t hash) { 912 | iterator it = find(key, hash); 913 | return std::make_pair(it, (it == end()) ? it : std::next(it)); 914 | } 915 | 916 | template 917 | std::pair equal_range(const K& key) const { 918 | return equal_range(key, hash_key(key)); 919 | } 920 | 921 | template 922 | std::pair equal_range( 923 | const K& key, std::size_t hash) const { 924 | const_iterator it = find(key, hash); 925 | return std::make_pair(it, (it == cend()) ? it : std::next(it)); 926 | } 927 | 928 | /* 929 | * Bucket interface 930 | */ 931 | size_type bucket_count() const { return m_buckets_data.size(); } 932 | 933 | size_type max_bucket_count() const { return m_buckets_data.max_size(); } 934 | 935 | /* 936 | * Hash policy 937 | */ 938 | float load_factor() const { 939 | if (bucket_count() == 0) { 940 | return 0; 941 | } 942 | 943 | return float(size()) / float(bucket_count()); 944 | } 945 | 946 | float max_load_factor() const { return m_max_load_factor; } 947 | 948 | void max_load_factor(float ml) { 949 | m_max_load_factor = clamp(ml, float(MAX_LOAD_FACTOR__MINIMUM), 950 | float(MAX_LOAD_FACTOR__MAXIMUM)); 951 | 952 | m_max_load_factor = ml; 953 | m_load_threshold = size_type(float(bucket_count()) * m_max_load_factor); 954 | } 955 | 956 | void rehash(size_type count) { 957 | count = std::max(count, 958 | size_type(std::ceil(float(size()) / max_load_factor()))); 959 | rehash_impl(count); 960 | } 961 | 962 | void reserve(size_type count) { 963 | reserve_space_for_values(count); 964 | 965 | count = size_type(std::ceil(float(count) / max_load_factor())); 966 | rehash(count); 967 | } 968 | 969 | /* 970 | * Observers 971 | */ 972 | hasher hash_function() const { return static_cast(*this); } 973 | 974 | key_equal key_eq() const { return static_cast(*this); } 975 | 976 | /* 977 | * Other 978 | */ 979 | iterator mutable_iterator(const_iterator pos) { 980 | return iterator(m_values.begin() + iterator_to_index(pos)); 981 | } 982 | 983 | iterator nth(size_type index) { 984 | tsl_oh_assert(index <= size()); 985 | return iterator(m_values.begin() + index); 986 | } 987 | 988 | const_iterator nth(size_type index) const { 989 | tsl_oh_assert(index <= size()); 990 | return const_iterator(m_values.cbegin() + index); 991 | } 992 | 993 | const_reference front() const { 994 | tsl_oh_assert(!empty()); 995 | return m_values.front(); 996 | } 997 | 998 | const_reference back() const { 999 | tsl_oh_assert(!empty()); 1000 | return m_values.back(); 1001 | } 1002 | 1003 | const values_container_type& values_container() const noexcept { 1004 | return m_values; 1005 | } 1006 | 1007 | values_container_type release() { 1008 | values_container_type ret; 1009 | for (auto& bucket : m_buckets_data) { 1010 | bucket.clear(); 1011 | } 1012 | m_grow_on_next_insert = false; 1013 | std::swap(ret, m_values); 1014 | return ret; 1015 | } 1016 | 1017 | template ::value>::type* = nullptr> 1019 | const typename values_container_type::value_type* data() const noexcept { 1020 | return m_values.data(); 1021 | } 1022 | 1023 | template ::value>::type* = nullptr> 1025 | size_type capacity() const noexcept { 1026 | return m_values.capacity(); 1027 | } 1028 | 1029 | void shrink_to_fit() { m_values.shrink_to_fit(); } 1030 | 1031 | template 1032 | std::pair insert_at_position(const_iterator pos, P&& value) { 1033 | return insert_at_position_impl(pos.m_iterator, KeySelect()(value), 1034 | std::forward

(value)); 1035 | } 1036 | 1037 | template 1038 | std::pair emplace_at_position(const_iterator pos, 1039 | Args&&... args) { 1040 | return insert_at_position(pos, value_type(std::forward(args)...)); 1041 | } 1042 | 1043 | template 1044 | std::pair try_emplace_at_position(const_iterator pos, K&& key, 1045 | Args&&... value_args) { 1046 | return insert_at_position_impl( 1047 | pos.m_iterator, key, std::piecewise_construct, 1048 | std::forward_as_tuple(std::forward(key)), 1049 | std::forward_as_tuple(std::forward(value_args)...)); 1050 | } 1051 | 1052 | void pop_back() { 1053 | tsl_oh_assert(!empty()); 1054 | erase(std::prev(end())); 1055 | } 1056 | 1057 | /** 1058 | * Here to avoid `template size_type unordered_erase(const K& key)` 1059 | * being used when we use a iterator instead of a const_iterator. 1060 | */ 1061 | iterator unordered_erase(iterator pos) { 1062 | return unordered_erase(const_iterator(pos)); 1063 | } 1064 | 1065 | iterator unordered_erase(const_iterator pos) { 1066 | const std::size_t index_erase = iterator_to_index(pos); 1067 | unordered_erase(pos.key()); 1068 | 1069 | /* 1070 | * One element was deleted, index_erase now points to the next element as 1071 | * the elements after the deleted value were shifted to the left in m_values 1072 | * (will be end() if we deleted the last element). 1073 | */ 1074 | return begin() + index_erase; 1075 | } 1076 | 1077 | template 1078 | size_type unordered_erase(const K& key) { 1079 | return unordered_erase(key, hash_key(key)); 1080 | } 1081 | 1082 | template 1083 | size_type unordered_erase(const K& key, std::size_t hash) { 1084 | auto it_bucket_key = find_key(key, hash); 1085 | if (it_bucket_key == m_buckets_data.end()) { 1086 | return 0; 1087 | } 1088 | 1089 | /** 1090 | * If we are not erasing the last element in m_values, we swap 1091 | * the element we are erasing with the last element. We then would 1092 | * just have to do a pop_back() in m_values. 1093 | */ 1094 | if (!compare_keys(key, KeySelect()(back()))) { 1095 | auto it_bucket_last_elem = 1096 | find_key(KeySelect()(back()), hash_key(KeySelect()(back()))); 1097 | tsl_oh_assert(it_bucket_last_elem != m_buckets_data.end()); 1098 | tsl_oh_assert(it_bucket_last_elem->index() == m_values.size() - 1); 1099 | 1100 | using std::swap; 1101 | swap(m_values[it_bucket_key->index()], 1102 | m_values[it_bucket_last_elem->index()]); 1103 | swap(it_bucket_key->index_ref(), it_bucket_last_elem->index_ref()); 1104 | } 1105 | 1106 | erase_value_from_bucket(it_bucket_key); 1107 | 1108 | return 1; 1109 | } 1110 | 1111 | /** 1112 | * Remove all entries for which the given predicate matches. 1113 | */ 1114 | template 1115 | size_type erase_if(Predicate& pred) { 1116 | // Get the bucket associated with the given element. 1117 | auto get_bucket = [this](typename values_container_type::iterator it) { 1118 | return find_key(KeySelect()(*it), hash_key(KeySelect()(*it))); 1119 | }; 1120 | // Clear a bucket without touching the container holding the values. 1121 | auto clear_bucket = [this](typename buckets_container_type::iterator it) { 1122 | tsl_oh_assert(it != m_buckets_data.end()); 1123 | it->clear(); 1124 | backward_shift(std::size_t(std::distance(m_buckets_data.begin(), it))); 1125 | }; 1126 | // Ensure that only const references are passed to the predicate. 1127 | auto cpred = [&pred](typename values_container_type::const_reference x) { 1128 | return pred(x); 1129 | }; 1130 | 1131 | // Find first element that matches the predicate. 1132 | const auto last = m_values.end(); 1133 | auto first = std::find_if(m_values.begin(), last, cpred); 1134 | if (first == last) { 1135 | return 0; 1136 | } 1137 | // Remove all elements that match the predicate. 1138 | clear_bucket(get_bucket(first)); 1139 | for (auto it = std::next(first); it != last; ++it) { 1140 | auto it_bucket = get_bucket(it); 1141 | if (cpred(*it)) { 1142 | clear_bucket(it_bucket); 1143 | } else { 1144 | it_bucket->set_index( 1145 | static_cast(std::distance(m_values.begin(), first))); 1146 | *first++ = std::move(*it); 1147 | } 1148 | } 1149 | // Resize the vector and return the number of deleted elements. 1150 | auto deleted = static_cast(std::distance(first, last)); 1151 | m_values.erase(first, last); 1152 | return deleted; 1153 | } 1154 | 1155 | template 1156 | void serialize(Serializer& serializer) const { 1157 | serialize_impl(serializer); 1158 | } 1159 | 1160 | template 1161 | void deserialize(Deserializer& deserializer, bool hash_compatible) { 1162 | deserialize_impl(deserializer, hash_compatible); 1163 | } 1164 | 1165 | friend bool operator==(const ordered_hash& lhs, const ordered_hash& rhs) { 1166 | return lhs.m_values == rhs.m_values; 1167 | } 1168 | 1169 | friend bool operator!=(const ordered_hash& lhs, const ordered_hash& rhs) { 1170 | return lhs.m_values != rhs.m_values; 1171 | } 1172 | 1173 | friend bool operator<(const ordered_hash& lhs, const ordered_hash& rhs) { 1174 | return lhs.m_values < rhs.m_values; 1175 | } 1176 | 1177 | friend bool operator<=(const ordered_hash& lhs, const ordered_hash& rhs) { 1178 | return lhs.m_values <= rhs.m_values; 1179 | } 1180 | 1181 | friend bool operator>(const ordered_hash& lhs, const ordered_hash& rhs) { 1182 | return lhs.m_values > rhs.m_values; 1183 | } 1184 | 1185 | friend bool operator>=(const ordered_hash& lhs, const ordered_hash& rhs) { 1186 | return lhs.m_values >= rhs.m_values; 1187 | } 1188 | 1189 | private: 1190 | template 1191 | std::size_t hash_key(const K& key) const { 1192 | return Hash::operator()(key); 1193 | } 1194 | 1195 | template 1196 | bool compare_keys(const K1& key1, const K2& key2) const { 1197 | return KeyEqual::operator()(key1, key2); 1198 | } 1199 | 1200 | template 1201 | typename buckets_container_type::iterator find_key(const K& key, 1202 | std::size_t hash) { 1203 | auto it = static_cast(this)->find_key(key, hash); 1204 | return m_buckets_data.begin() + std::distance(m_buckets_data.cbegin(), it); 1205 | } 1206 | 1207 | /** 1208 | * Return bucket which has the key 'key' or m_buckets_data.end() if none. 1209 | * 1210 | * From the bucket_for_hash, search for the value until we either find an 1211 | * empty bucket or a bucket which has a value with a distance from its ideal 1212 | * bucket longer than the probe length for the value we are looking for. 1213 | */ 1214 | template 1215 | typename buckets_container_type::const_iterator find_key( 1216 | const K& key, std::size_t hash) const { 1217 | for (std::size_t ibucket = bucket_for_hash(hash), 1218 | dist_from_ideal_bucket = 0; 1219 | ; ibucket = next_bucket(ibucket), dist_from_ideal_bucket++) { 1220 | if (m_buckets[ibucket].empty()) { 1221 | return m_buckets_data.end(); 1222 | } else if (m_buckets[ibucket].truncated_hash() == 1223 | bucket_entry::truncate_hash(hash) && 1224 | compare_keys( 1225 | key, KeySelect()(m_values[m_buckets[ibucket].index()]))) { 1226 | return m_buckets_data.begin() + ibucket; 1227 | } else if (dist_from_ideal_bucket > distance_from_ideal_bucket(ibucket)) { 1228 | return m_buckets_data.end(); 1229 | } 1230 | } 1231 | } 1232 | 1233 | void rehash_impl(size_type bucket_count) { 1234 | tsl_oh_assert(bucket_count >= 1235 | size_type(std::ceil(float(size()) / max_load_factor()))); 1236 | 1237 | if (bucket_count > max_bucket_count()) { 1238 | TSL_OH_THROW_OR_TERMINATE(std::length_error, 1239 | "The map exceeds its maximum size."); 1240 | } 1241 | 1242 | if (bucket_count > 0) { 1243 | bucket_count = round_up_to_power_of_two(bucket_count); 1244 | } 1245 | 1246 | if (bucket_count == this->bucket_count()) { 1247 | return; 1248 | } 1249 | 1250 | buckets_container_type old_buckets(bucket_count); 1251 | m_buckets_data.swap(old_buckets); 1252 | m_buckets = m_buckets_data.empty() ? static_empty_bucket_ptr() 1253 | : m_buckets_data.data(); 1254 | // Everything should be noexcept from here. 1255 | 1256 | m_hash_mask = (bucket_count > 0) ? (bucket_count - 1) : 0; 1257 | this->max_load_factor(m_max_load_factor); 1258 | m_grow_on_next_insert = false; 1259 | 1260 | for (const bucket_entry& old_bucket : old_buckets) { 1261 | if (old_bucket.empty()) { 1262 | continue; 1263 | } 1264 | 1265 | truncated_hash_type insert_hash = old_bucket.truncated_hash(); 1266 | index_type insert_index = old_bucket.index(); 1267 | 1268 | for (std::size_t ibucket = bucket_for_hash(insert_hash), 1269 | dist_from_ideal_bucket = 0; 1270 | ; ibucket = next_bucket(ibucket), dist_from_ideal_bucket++) { 1271 | if (m_buckets[ibucket].empty()) { 1272 | m_buckets[ibucket].set_index(insert_index); 1273 | m_buckets[ibucket].set_hash(insert_hash); 1274 | break; 1275 | } 1276 | 1277 | const std::size_t distance = distance_from_ideal_bucket(ibucket); 1278 | if (dist_from_ideal_bucket > distance) { 1279 | std::swap(insert_index, m_buckets[ibucket].index_ref()); 1280 | std::swap(insert_hash, m_buckets[ibucket].truncated_hash_ref()); 1281 | dist_from_ideal_bucket = distance; 1282 | } 1283 | } 1284 | } 1285 | } 1286 | 1287 | template ::value>::type* = nullptr> 1289 | void reserve_space_for_values(size_type count) { 1290 | m_values.reserve(count); 1291 | } 1292 | 1293 | template ::value>::type* = nullptr> 1295 | void reserve_space_for_values(size_type /*count*/) {} 1296 | 1297 | /** 1298 | * Swap the empty bucket with the values on its right until we cross another 1299 | * empty bucket or if the other bucket has a distance_from_ideal_bucket == 0. 1300 | */ 1301 | void backward_shift(std::size_t empty_ibucket) noexcept { 1302 | tsl_oh_assert(m_buckets[empty_ibucket].empty()); 1303 | 1304 | std::size_t previous_ibucket = empty_ibucket; 1305 | for (std::size_t current_ibucket = next_bucket(previous_ibucket); 1306 | !m_buckets[current_ibucket].empty() && 1307 | distance_from_ideal_bucket(current_ibucket) > 0; 1308 | previous_ibucket = current_ibucket, 1309 | current_ibucket = next_bucket(current_ibucket)) { 1310 | std::swap(m_buckets[current_ibucket], m_buckets[previous_ibucket]); 1311 | } 1312 | } 1313 | 1314 | void erase_value_from_bucket( 1315 | typename buckets_container_type::iterator it_bucket) { 1316 | tsl_oh_assert(it_bucket != m_buckets_data.end() && !it_bucket->empty()); 1317 | 1318 | m_values.erase(m_values.begin() + it_bucket->index()); 1319 | 1320 | /* 1321 | * m_values.erase shifted all the values on the right of the erased value, 1322 | * shift the indexes by -1 in the buckets array for these values. 1323 | */ 1324 | if (it_bucket->index() != m_values.size()) { 1325 | shift_indexes_in_buckets(it_bucket->index() + 1, -1); 1326 | } 1327 | 1328 | // Mark the bucket as empty and do a backward shift of the values on the 1329 | // right 1330 | it_bucket->clear(); 1331 | backward_shift( 1332 | std::size_t(std::distance(m_buckets_data.begin(), it_bucket))); 1333 | } 1334 | 1335 | /** 1336 | * Shift any index >= index_above_or_equal in m_buckets_data by delta. 1337 | * 1338 | * delta must be equal to 1 or -1. 1339 | */ 1340 | void shift_indexes_in_buckets(index_type index_above_or_equal, 1341 | int delta) noexcept { 1342 | tsl_oh_assert(delta == 1 || delta == -1); 1343 | 1344 | for (bucket_entry& bucket : m_buckets_data) { 1345 | if (!bucket.empty() && bucket.index() >= index_above_or_equal) { 1346 | tsl_oh_assert(delta >= 0 || 1347 | bucket.index() >= static_cast(-delta)); 1348 | tsl_oh_assert(delta <= 0 || 1349 | (bucket_entry::max_size() - bucket.index()) >= 1350 | static_cast(delta)); 1351 | bucket.set_index(static_cast(bucket.index() + delta)); 1352 | } 1353 | } 1354 | } 1355 | 1356 | template 1357 | size_type erase_impl(const K& key, std::size_t hash) { 1358 | auto it_bucket = find_key(key, hash); 1359 | if (it_bucket != m_buckets_data.end()) { 1360 | erase_value_from_bucket(it_bucket); 1361 | 1362 | return 1; 1363 | } else { 1364 | return 0; 1365 | } 1366 | } 1367 | 1368 | /** 1369 | * Insert the element at the end. 1370 | */ 1371 | template 1372 | std::pair insert_impl(const K& key, 1373 | Args&&... value_type_args) { 1374 | const std::size_t hash = hash_key(key); 1375 | 1376 | std::size_t ibucket = bucket_for_hash(hash); 1377 | std::size_t dist_from_ideal_bucket = 0; 1378 | 1379 | while (!m_buckets[ibucket].empty() && 1380 | dist_from_ideal_bucket <= distance_from_ideal_bucket(ibucket)) { 1381 | if (m_buckets[ibucket].truncated_hash() == 1382 | bucket_entry::truncate_hash(hash) && 1383 | compare_keys(key, 1384 | KeySelect()(m_values[m_buckets[ibucket].index()]))) { 1385 | return std::make_pair(begin() + m_buckets[ibucket].index(), false); 1386 | } 1387 | 1388 | ibucket = next_bucket(ibucket); 1389 | dist_from_ideal_bucket++; 1390 | } 1391 | 1392 | if (size() >= max_size()) { 1393 | TSL_OH_THROW_OR_TERMINATE( 1394 | std::length_error, "We reached the maximum size for the hash table."); 1395 | } 1396 | 1397 | if (grow_on_high_load()) { 1398 | ibucket = bucket_for_hash(hash); 1399 | dist_from_ideal_bucket = 0; 1400 | } 1401 | 1402 | m_values.emplace_back(std::forward(value_type_args)...); 1403 | insert_index(ibucket, dist_from_ideal_bucket, 1404 | index_type(m_values.size() - 1), 1405 | bucket_entry::truncate_hash(hash)); 1406 | 1407 | return std::make_pair(std::prev(end()), true); 1408 | } 1409 | 1410 | /** 1411 | * Insert the element before insert_position. 1412 | */ 1413 | template 1414 | std::pair insert_at_position_impl( 1415 | typename values_container_type::const_iterator insert_position, 1416 | const K& key, Args&&... value_type_args) { 1417 | const std::size_t hash = hash_key(key); 1418 | 1419 | std::size_t ibucket = bucket_for_hash(hash); 1420 | std::size_t dist_from_ideal_bucket = 0; 1421 | 1422 | while (!m_buckets[ibucket].empty() && 1423 | dist_from_ideal_bucket <= distance_from_ideal_bucket(ibucket)) { 1424 | if (m_buckets[ibucket].truncated_hash() == 1425 | bucket_entry::truncate_hash(hash) && 1426 | compare_keys(key, 1427 | KeySelect()(m_values[m_buckets[ibucket].index()]))) { 1428 | return std::make_pair(begin() + m_buckets[ibucket].index(), false); 1429 | } 1430 | 1431 | ibucket = next_bucket(ibucket); 1432 | dist_from_ideal_bucket++; 1433 | } 1434 | 1435 | if (size() >= max_size()) { 1436 | TSL_OH_THROW_OR_TERMINATE( 1437 | std::length_error, "We reached the maximum size for the hash table."); 1438 | } 1439 | 1440 | if (grow_on_high_load()) { 1441 | ibucket = bucket_for_hash(hash); 1442 | dist_from_ideal_bucket = 0; 1443 | } 1444 | 1445 | const index_type index_insert_position = 1446 | index_type(std::distance(m_values.cbegin(), insert_position)); 1447 | 1448 | #ifdef TSL_OH_NO_CONTAINER_EMPLACE_CONST_ITERATOR 1449 | m_values.emplace( 1450 | m_values.begin() + std::distance(m_values.cbegin(), insert_position), 1451 | std::forward(value_type_args)...); 1452 | #else 1453 | m_values.emplace(insert_position, std::forward(value_type_args)...); 1454 | #endif 1455 | 1456 | /* 1457 | * The insertion didn't happend at the end of the m_values container, 1458 | * we need to shift the indexes in m_buckets_data. 1459 | */ 1460 | if (index_insert_position != m_values.size() - 1) { 1461 | shift_indexes_in_buckets(index_insert_position, 1); 1462 | } 1463 | 1464 | insert_index(ibucket, dist_from_ideal_bucket, index_insert_position, 1465 | bucket_entry::truncate_hash(hash)); 1466 | 1467 | return std::make_pair(iterator(m_values.begin() + index_insert_position), 1468 | true); 1469 | } 1470 | 1471 | void insert_index(std::size_t ibucket, std::size_t dist_from_ideal_bucket, 1472 | index_type index_insert, 1473 | truncated_hash_type hash_insert) noexcept { 1474 | while (!m_buckets[ibucket].empty()) { 1475 | const std::size_t distance = distance_from_ideal_bucket(ibucket); 1476 | if (dist_from_ideal_bucket > distance) { 1477 | std::swap(index_insert, m_buckets[ibucket].index_ref()); 1478 | std::swap(hash_insert, m_buckets[ibucket].truncated_hash_ref()); 1479 | 1480 | dist_from_ideal_bucket = distance; 1481 | } 1482 | 1483 | ibucket = next_bucket(ibucket); 1484 | dist_from_ideal_bucket++; 1485 | 1486 | if (dist_from_ideal_bucket > REHASH_ON_HIGH_NB_PROBES__NPROBES && 1487 | !m_grow_on_next_insert && 1488 | load_factor() >= REHASH_ON_HIGH_NB_PROBES__MIN_LOAD_FACTOR) { 1489 | // We don't want to grow the map now as we need this method to be 1490 | // noexcept. Do it on next insert. 1491 | m_grow_on_next_insert = true; 1492 | } 1493 | } 1494 | 1495 | m_buckets[ibucket].set_index(index_insert); 1496 | m_buckets[ibucket].set_hash(hash_insert); 1497 | } 1498 | 1499 | std::size_t distance_from_ideal_bucket(std::size_t ibucket) const noexcept { 1500 | const std::size_t ideal_bucket = 1501 | bucket_for_hash(m_buckets[ibucket].truncated_hash()); 1502 | 1503 | if (ibucket >= ideal_bucket) { 1504 | return ibucket - ideal_bucket; 1505 | } 1506 | // If the bucket is smaller than the ideal bucket for the value, there was a 1507 | // wrapping at the end of the bucket array due to the modulo. 1508 | else { 1509 | return (bucket_count() + ibucket) - ideal_bucket; 1510 | } 1511 | } 1512 | 1513 | std::size_t next_bucket(std::size_t index) const noexcept { 1514 | tsl_oh_assert(index < m_buckets_data.size()); 1515 | 1516 | index++; 1517 | return (index < m_buckets_data.size()) ? index : 0; 1518 | } 1519 | 1520 | std::size_t bucket_for_hash(std::size_t hash) const noexcept { 1521 | return hash & m_hash_mask; 1522 | } 1523 | 1524 | std::size_t iterator_to_index(const_iterator it) const noexcept { 1525 | const auto dist = std::distance(cbegin(), it); 1526 | tsl_oh_assert(dist >= 0); 1527 | 1528 | return std::size_t(dist); 1529 | } 1530 | 1531 | /** 1532 | * Return true if the map has been rehashed. 1533 | */ 1534 | bool grow_on_high_load() { 1535 | if (m_grow_on_next_insert || size() >= m_load_threshold) { 1536 | rehash_impl(std::max(size_type(1), bucket_count() * 2)); 1537 | m_grow_on_next_insert = false; 1538 | 1539 | return true; 1540 | } else { 1541 | return false; 1542 | } 1543 | } 1544 | 1545 | template 1546 | void serialize_impl(Serializer& serializer) const { 1547 | const slz_size_type version = SERIALIZATION_PROTOCOL_VERSION; 1548 | serializer(version); 1549 | 1550 | const slz_size_type nb_elements = m_values.size(); 1551 | serializer(nb_elements); 1552 | 1553 | const slz_size_type bucket_count = m_buckets_data.size(); 1554 | serializer(bucket_count); 1555 | 1556 | const float max_load_factor = m_max_load_factor; 1557 | serializer(max_load_factor); 1558 | 1559 | for (const value_type& value : m_values) { 1560 | serializer(value); 1561 | } 1562 | 1563 | for (const bucket_entry& bucket : m_buckets_data) { 1564 | bucket.serialize(serializer); 1565 | } 1566 | } 1567 | 1568 | template 1569 | void deserialize_impl(Deserializer& deserializer, bool hash_compatible) { 1570 | tsl_oh_assert(m_buckets_data.empty()); // Current hash table must be empty 1571 | 1572 | const slz_size_type version = 1573 | deserialize_value(deserializer); 1574 | // For now we only have one version of the serialization protocol. 1575 | // If it doesn't match there is a problem with the file. 1576 | if (version != SERIALIZATION_PROTOCOL_VERSION) { 1577 | TSL_OH_THROW_OR_TERMINATE(std::runtime_error, 1578 | "Can't deserialize the ordered_map/set. " 1579 | "The protocol version header is invalid."); 1580 | } 1581 | 1582 | const slz_size_type nb_elements = 1583 | deserialize_value(deserializer); 1584 | const slz_size_type bucket_count_ds = 1585 | deserialize_value(deserializer); 1586 | const float max_load_factor = deserialize_value(deserializer); 1587 | 1588 | if (max_load_factor < MAX_LOAD_FACTOR__MINIMUM || 1589 | max_load_factor > MAX_LOAD_FACTOR__MAXIMUM) { 1590 | TSL_OH_THROW_OR_TERMINATE( 1591 | std::runtime_error, 1592 | "Invalid max_load_factor. Check that the serializer " 1593 | "and deserializer support floats correctly as they " 1594 | "can be converted implicitly to ints."); 1595 | } 1596 | 1597 | this->max_load_factor(max_load_factor); 1598 | 1599 | if (bucket_count_ds == 0) { 1600 | tsl_oh_assert(nb_elements == 0); 1601 | return; 1602 | } 1603 | 1604 | if (!hash_compatible) { 1605 | reserve(numeric_cast(nb_elements, 1606 | "Deserialized nb_elements is too big.")); 1607 | for (slz_size_type el = 0; el < nb_elements; el++) { 1608 | insert(deserialize_value(deserializer)); 1609 | } 1610 | } else { 1611 | m_buckets_data.reserve(numeric_cast( 1612 | bucket_count_ds, "Deserialized bucket_count is too big.")); 1613 | m_buckets = m_buckets_data.data(), 1614 | m_hash_mask = m_buckets_data.capacity() - 1; 1615 | 1616 | reserve_space_for_values(numeric_cast( 1617 | nb_elements, "Deserialized nb_elements is too big.")); 1618 | for (slz_size_type el = 0; el < nb_elements; el++) { 1619 | m_values.push_back(deserialize_value(deserializer)); 1620 | } 1621 | 1622 | for (slz_size_type b = 0; b < bucket_count_ds; b++) { 1623 | m_buckets_data.push_back(bucket_entry::deserialize(deserializer)); 1624 | } 1625 | } 1626 | } 1627 | 1628 | static std::size_t round_up_to_power_of_two(std::size_t value) { 1629 | if (is_power_of_two(value)) { 1630 | return value; 1631 | } 1632 | 1633 | if (value == 0) { 1634 | return 1; 1635 | } 1636 | 1637 | --value; 1638 | for (std::size_t i = 1; i < sizeof(std::size_t) * CHAR_BIT; i *= 2) { 1639 | value |= value >> i; 1640 | } 1641 | 1642 | return value + 1; 1643 | } 1644 | 1645 | static constexpr bool is_power_of_two(std::size_t value) { 1646 | return value != 0 && (value & (value - 1)) == 0; 1647 | } 1648 | 1649 | public: 1650 | static const size_type DEFAULT_INIT_BUCKETS_SIZE = 0; 1651 | static constexpr float DEFAULT_MAX_LOAD_FACTOR = 0.75f; 1652 | 1653 | private: 1654 | static constexpr float MAX_LOAD_FACTOR__MINIMUM = 0.1f; 1655 | static constexpr float MAX_LOAD_FACTOR__MAXIMUM = 0.95f; 1656 | 1657 | static const size_type REHASH_ON_HIGH_NB_PROBES__NPROBES = 128; 1658 | static constexpr float REHASH_ON_HIGH_NB_PROBES__MIN_LOAD_FACTOR = 0.15f; 1659 | 1660 | /** 1661 | * Protocol version currenlty used for serialization. 1662 | */ 1663 | static const slz_size_type SERIALIZATION_PROTOCOL_VERSION = 1; 1664 | 1665 | /** 1666 | * Return an always valid pointer to an static empty bucket_entry with 1667 | * last_bucket() == true. 1668 | */ 1669 | bucket_entry* static_empty_bucket_ptr() { 1670 | static bucket_entry empty_bucket; 1671 | return &empty_bucket; 1672 | } 1673 | 1674 | private: 1675 | buckets_container_type m_buckets_data; 1676 | 1677 | /** 1678 | * Points to m_buckets_data.data() if !m_buckets_data.empty() otherwise points 1679 | * to static_empty_bucket_ptr. This variable is useful to avoid the cost of 1680 | * checking if m_buckets_data is empty when trying to find an element. 1681 | * 1682 | * TODO Remove m_buckets_data and only use a pointer+size instead of a 1683 | * pointer+vector to save some space in the ordered_hash object. 1684 | */ 1685 | bucket_entry* m_buckets; 1686 | 1687 | size_type m_hash_mask; 1688 | 1689 | values_container_type m_values; 1690 | 1691 | size_type m_load_threshold; 1692 | float m_max_load_factor; 1693 | 1694 | bool m_grow_on_next_insert; 1695 | }; 1696 | 1697 | } // end namespace detail_ordered_hash 1698 | 1699 | } // end namespace tsl 1700 | 1701 | #endif 1702 | -------------------------------------------------------------------------------- /include/tsl/ordered_map.h: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2017 Thibaut Goetghebuer-Planchon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | #ifndef TSL_ORDERED_MAP_H 25 | #define TSL_ORDERED_MAP_H 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #include "ordered_hash.h" 38 | 39 | namespace tsl { 40 | 41 | /** 42 | * Implementation of an hash map using open addressing with robin hood with 43 | * backshift delete to resolve collisions. 44 | * 45 | * The particularity of this hash map is that it remembers the order in which 46 | * the elements were added and provide a way to access the structure which 47 | * stores these values through the 'values_container()' method. The used 48 | * container is defined by ValueTypeContainer, by default a std::deque is used 49 | * (grows faster) but a std::vector may be used. In this case the map provides a 50 | * 'data()' method which give a direct access to the memory used to store the 51 | * values (which can be useful to communicate with C API's). 52 | * 53 | * The Key and T must be copy constructible and/or move constructible. To use 54 | * `unordered_erase` they both must be swappable. 55 | * 56 | * The behaviour of the hash map is undefined if the destructor of Key or T 57 | * throws an exception. 58 | * 59 | * By default the maximum size of a map is limited to 2^32 - 1 values, if needed 60 | * this can be changed through the IndexType template parameter. Using an 61 | * `uint64_t` will raise this limit to 2^64 - 1 values but each bucket will use 62 | * 16 bytes instead of 8 bytes in addition to the space needed to store the 63 | * values. 64 | * 65 | * Iterators invalidation: 66 | * - clear, operator=, reserve, rehash: always invalidate the iterators (also 67 | * invalidate end()). 68 | * - insert, emplace, emplace_hint, operator[]: when a std::vector is used as 69 | * ValueTypeContainer and if size() < capacity(), only end(). Otherwise all the 70 | * iterators are invalidated if an insert occurs. 71 | * - erase, unordered_erase: when a std::vector is used as ValueTypeContainer 72 | * invalidate the iterator of the erased element and all the ones after the 73 | * erased element (including end()). Otherwise all the iterators are invalidated 74 | * if an erase occurs. 75 | */ 76 | template , 77 | class KeyEqual = std::equal_to, 78 | class Allocator = std::allocator>, 79 | class ValueTypeContainer = std::deque, Allocator>, 80 | class IndexType = std::uint_least32_t> 81 | class ordered_map { 82 | private: 83 | template 84 | using has_is_transparent = tsl::detail_ordered_hash::has_is_transparent; 85 | 86 | class KeySelect { 87 | public: 88 | using key_type = Key; 89 | 90 | const key_type& operator()( 91 | const std::pair& key_value) const noexcept { 92 | return key_value.first; 93 | } 94 | 95 | key_type& operator()(std::pair& key_value) noexcept { 96 | return key_value.first; 97 | } 98 | }; 99 | 100 | class ValueSelect { 101 | public: 102 | using value_type = T; 103 | 104 | const value_type& operator()( 105 | const std::pair& key_value) const noexcept { 106 | return key_value.second; 107 | } 108 | 109 | value_type& operator()(std::pair& key_value) noexcept { 110 | return key_value.second; 111 | } 112 | }; 113 | 114 | using ht = 115 | detail_ordered_hash::ordered_hash, KeySelect, 116 | ValueSelect, Hash, KeyEqual, Allocator, 117 | ValueTypeContainer, IndexType>; 118 | 119 | public: 120 | using key_type = typename ht::key_type; 121 | using mapped_type = T; 122 | using value_type = typename ht::value_type; 123 | using size_type = typename ht::size_type; 124 | using difference_type = typename ht::difference_type; 125 | using hasher = typename ht::hasher; 126 | using key_equal = typename ht::key_equal; 127 | using allocator_type = typename ht::allocator_type; 128 | using reference = typename ht::reference; 129 | using const_reference = typename ht::const_reference; 130 | using pointer = typename ht::pointer; 131 | using const_pointer = typename ht::const_pointer; 132 | using iterator = typename ht::iterator; 133 | using const_iterator = typename ht::const_iterator; 134 | using reverse_iterator = typename ht::reverse_iterator; 135 | using const_reverse_iterator = typename ht::const_reverse_iterator; 136 | 137 | using values_container_type = typename ht::values_container_type; 138 | 139 | /* 140 | * Constructors 141 | */ 142 | ordered_map() : ordered_map(ht::DEFAULT_INIT_BUCKETS_SIZE) {} 143 | 144 | explicit ordered_map(size_type bucket_count, const Hash& hash = Hash(), 145 | const KeyEqual& equal = KeyEqual(), 146 | const Allocator& alloc = Allocator()) 147 | : m_ht(bucket_count, hash, equal, alloc, ht::DEFAULT_MAX_LOAD_FACTOR) {} 148 | 149 | ordered_map(size_type bucket_count, const Allocator& alloc) 150 | : ordered_map(bucket_count, Hash(), KeyEqual(), alloc) {} 151 | 152 | ordered_map(size_type bucket_count, const Hash& hash, const Allocator& alloc) 153 | : ordered_map(bucket_count, hash, KeyEqual(), alloc) {} 154 | 155 | explicit ordered_map(const Allocator& alloc) 156 | : ordered_map(ht::DEFAULT_INIT_BUCKETS_SIZE, alloc) {} 157 | 158 | template 159 | ordered_map(InputIt first, InputIt last, 160 | size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, 161 | const Hash& hash = Hash(), const KeyEqual& equal = KeyEqual(), 162 | const Allocator& alloc = Allocator()) 163 | : ordered_map(bucket_count, hash, equal, alloc) { 164 | insert(first, last); 165 | } 166 | 167 | template 168 | ordered_map(InputIt first, InputIt last, size_type bucket_count, 169 | const Allocator& alloc) 170 | : ordered_map(first, last, bucket_count, Hash(), KeyEqual(), alloc) {} 171 | 172 | template 173 | ordered_map(InputIt first, InputIt last, size_type bucket_count, 174 | const Hash& hash, const Allocator& alloc) 175 | : ordered_map(first, last, bucket_count, hash, KeyEqual(), alloc) {} 176 | 177 | ordered_map(std::initializer_list init, 178 | size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, 179 | const Hash& hash = Hash(), const KeyEqual& equal = KeyEqual(), 180 | const Allocator& alloc = Allocator()) 181 | : ordered_map(init.begin(), init.end(), bucket_count, hash, equal, 182 | alloc) {} 183 | 184 | ordered_map(std::initializer_list init, size_type bucket_count, 185 | const Allocator& alloc) 186 | : ordered_map(init.begin(), init.end(), bucket_count, Hash(), KeyEqual(), 187 | alloc) {} 188 | 189 | ordered_map(std::initializer_list init, size_type bucket_count, 190 | const Hash& hash, const Allocator& alloc) 191 | : ordered_map(init.begin(), init.end(), bucket_count, hash, KeyEqual(), 192 | alloc) {} 193 | 194 | ordered_map& operator=(std::initializer_list ilist) { 195 | m_ht.clear(); 196 | 197 | m_ht.reserve(ilist.size()); 198 | m_ht.insert(ilist.begin(), ilist.end()); 199 | 200 | return *this; 201 | } 202 | 203 | allocator_type get_allocator() const { return m_ht.get_allocator(); } 204 | 205 | /* 206 | * Iterators 207 | */ 208 | iterator begin() noexcept { return m_ht.begin(); } 209 | const_iterator begin() const noexcept { return m_ht.begin(); } 210 | const_iterator cbegin() const noexcept { return m_ht.cbegin(); } 211 | 212 | iterator end() noexcept { return m_ht.end(); } 213 | const_iterator end() const noexcept { return m_ht.end(); } 214 | const_iterator cend() const noexcept { return m_ht.cend(); } 215 | 216 | reverse_iterator rbegin() noexcept { return m_ht.rbegin(); } 217 | const_reverse_iterator rbegin() const noexcept { return m_ht.rbegin(); } 218 | const_reverse_iterator rcbegin() const noexcept { return m_ht.rcbegin(); } 219 | 220 | reverse_iterator rend() noexcept { return m_ht.rend(); } 221 | const_reverse_iterator rend() const noexcept { return m_ht.rend(); } 222 | const_reverse_iterator rcend() const noexcept { return m_ht.rcend(); } 223 | 224 | /* 225 | * Capacity 226 | */ 227 | bool empty() const noexcept { return m_ht.empty(); } 228 | size_type size() const noexcept { return m_ht.size(); } 229 | size_type max_size() const noexcept { return m_ht.max_size(); } 230 | 231 | /* 232 | * Modifiers 233 | */ 234 | void clear() noexcept { m_ht.clear(); } 235 | 236 | std::pair insert(const value_type& value) { 237 | return m_ht.insert(value); 238 | } 239 | 240 | template ::value>::type* = nullptr> 242 | std::pair insert(P&& value) { 243 | return m_ht.emplace(std::forward

(value)); 244 | } 245 | 246 | std::pair insert(value_type&& value) { 247 | return m_ht.insert(std::move(value)); 248 | } 249 | 250 | iterator insert(const_iterator hint, const value_type& value) { 251 | return m_ht.insert_hint(hint, value); 252 | } 253 | 254 | template ::value>::type* = nullptr> 256 | iterator insert(const_iterator hint, P&& value) { 257 | return m_ht.emplace_hint(hint, std::forward

(value)); 258 | } 259 | 260 | iterator insert(const_iterator hint, value_type&& value) { 261 | return m_ht.insert_hint(hint, std::move(value)); 262 | } 263 | 264 | template 265 | void insert(InputIt first, InputIt last) { 266 | m_ht.insert(first, last); 267 | } 268 | void insert(std::initializer_list ilist) { 269 | m_ht.insert(ilist.begin(), ilist.end()); 270 | } 271 | 272 | template 273 | std::pair insert_or_assign(const key_type& k, M&& obj) { 274 | return m_ht.insert_or_assign(k, std::forward(obj)); 275 | } 276 | 277 | template 278 | std::pair insert_or_assign(key_type&& k, M&& obj) { 279 | return m_ht.insert_or_assign(std::move(k), std::forward(obj)); 280 | } 281 | 282 | template 283 | iterator insert_or_assign(const_iterator hint, const key_type& k, M&& obj) { 284 | return m_ht.insert_or_assign(hint, k, std::forward(obj)); 285 | } 286 | 287 | template 288 | iterator insert_or_assign(const_iterator hint, key_type&& k, M&& obj) { 289 | return m_ht.insert_or_assign(hint, std::move(k), std::forward(obj)); 290 | } 291 | 292 | /** 293 | * Due to the way elements are stored, emplace will need to move or copy the 294 | * key-value once. The method is equivalent to 295 | * insert(value_type(std::forward(args)...)); 296 | * 297 | * Mainly here for compatibility with the std::unordered_map interface. 298 | */ 299 | template 300 | std::pair emplace(Args&&... args) { 301 | return m_ht.emplace(std::forward(args)...); 302 | } 303 | 304 | /** 305 | * Due to the way elements are stored, emplace_hint will need to move or copy 306 | * the key-value once. The method is equivalent to insert(hint, 307 | * value_type(std::forward(args)...)); 308 | * 309 | * Mainly here for compatibility with the std::unordered_map interface. 310 | */ 311 | template 312 | iterator emplace_hint(const_iterator hint, Args&&... args) { 313 | return m_ht.emplace_hint(hint, std::forward(args)...); 314 | } 315 | 316 | template 317 | std::pair try_emplace(const key_type& k, Args&&... args) { 318 | return m_ht.try_emplace(k, std::forward(args)...); 319 | } 320 | 321 | template 322 | std::pair try_emplace(key_type&& k, Args&&... args) { 323 | return m_ht.try_emplace(std::move(k), std::forward(args)...); 324 | } 325 | 326 | template 327 | iterator try_emplace(const_iterator hint, const key_type& k, Args&&... args) { 328 | return m_ht.try_emplace_hint(hint, k, std::forward(args)...); 329 | } 330 | 331 | template 332 | iterator try_emplace(const_iterator hint, key_type&& k, Args&&... args) { 333 | return m_ht.try_emplace_hint(hint, std::move(k), 334 | std::forward(args)...); 335 | } 336 | 337 | /** 338 | * When erasing an element, the insert order will be preserved and no holes 339 | * will be present in the container returned by 'values_container()'. 340 | * 341 | * The method is in O(bucket_count()), if the order is not important 342 | * 'unordered_erase(...)' method is faster with an O(1) average complexity. 343 | */ 344 | iterator erase(iterator pos) { return m_ht.erase(pos); } 345 | 346 | /** 347 | * @copydoc erase(iterator pos) 348 | */ 349 | iterator erase(const_iterator pos) { return m_ht.erase(pos); } 350 | 351 | /** 352 | * @copydoc erase(iterator pos) 353 | */ 354 | iterator erase(const_iterator first, const_iterator last) { 355 | return m_ht.erase(first, last); 356 | } 357 | 358 | /** 359 | * @copydoc erase(iterator pos) 360 | */ 361 | size_type erase(const key_type& key) { return m_ht.erase(key); } 362 | 363 | /** 364 | * @copydoc erase(iterator pos) 365 | * 366 | * Use the hash value 'precalculated_hash' instead of hashing the key. The 367 | * hash value should be the same as hash_function()(key). Useful to speed-up 368 | * the lookup to the value if you already have the hash. 369 | */ 370 | size_type erase(const key_type& key, std::size_t precalculated_hash) { 371 | return m_ht.erase(key, precalculated_hash); 372 | } 373 | 374 | /** 375 | * @copydoc erase(iterator pos) 376 | * 377 | * This overload only participates in the overload resolution if the typedef 378 | * KeyEqual::is_transparent exists. If so, K must be hashable and comparable 379 | * to Key. 380 | */ 381 | template < 382 | class K, class KE = KeyEqual, 383 | typename std::enable_if::value>::type* = nullptr> 384 | size_type erase(const K& key) { 385 | return m_ht.erase(key); 386 | } 387 | 388 | /** 389 | * @copydoc erase(const key_type& key, std::size_t precalculated_hash) 390 | * 391 | * This overload only participates in the overload resolution if the typedef 392 | * KeyEqual::is_transparent exists. If so, K must be hashable and comparable 393 | * to Key. 394 | */ 395 | template < 396 | class K, class KE = KeyEqual, 397 | typename std::enable_if::value>::type* = nullptr> 398 | size_type erase(const K& key, std::size_t precalculated_hash) { 399 | return m_ht.erase(key, precalculated_hash); 400 | } 401 | 402 | /** 403 | * @copydoc erase(iterator pos) 404 | * 405 | * Erases all elements that satisfy the predicate pred. The method is in 406 | * O(n). Note that the function only has the strong exception guarantee if 407 | * the Predicate, Hash, and Key predicates and moves of keys and values do 408 | * not throw. If an exception is raised, the object is in an invalid state. 409 | * It can still be cleared and destroyed without leaking memory. 410 | */ 411 | template 412 | friend size_type erase_if(ordered_map &map, Predicate pred) { 413 | return map.m_ht.erase_if(pred); 414 | } 415 | 416 | void swap(ordered_map& other) { other.m_ht.swap(m_ht); } 417 | 418 | /* 419 | * Lookup 420 | */ 421 | T& at(const Key& key) { return m_ht.at(key); } 422 | 423 | /** 424 | * Use the hash value 'precalculated_hash' instead of hashing the key. The 425 | * hash value should be the same as hash_function()(key). Useful to speed-up 426 | * the lookup if you already have the hash. 427 | */ 428 | T& at(const Key& key, std::size_t precalculated_hash) { 429 | return m_ht.at(key, precalculated_hash); 430 | } 431 | 432 | const T& at(const Key& key) const { return m_ht.at(key); } 433 | 434 | /** 435 | * @copydoc at(const Key& key, std::size_t precalculated_hash) 436 | */ 437 | const T& at(const Key& key, std::size_t precalculated_hash) const { 438 | return m_ht.at(key, precalculated_hash); 439 | } 440 | 441 | /** 442 | * This overload only participates in the overload resolution if the typedef 443 | * KeyEqual::is_transparent exists. If so, K must be hashable and comparable 444 | * to Key. 445 | */ 446 | template < 447 | class K, class KE = KeyEqual, 448 | typename std::enable_if::value>::type* = nullptr> 449 | T& at(const K& key) { 450 | return m_ht.at(key); 451 | } 452 | 453 | /** 454 | * @copydoc at(const K& key) 455 | * 456 | * Use the hash value 'precalculated_hash' instead of hashing the key. The 457 | * hash value should be the same as hash_function()(key). Useful to speed-up 458 | * the lookup if you already have the hash. 459 | */ 460 | template < 461 | class K, class KE = KeyEqual, 462 | typename std::enable_if::value>::type* = nullptr> 463 | T& at(const K& key, std::size_t precalculated_hash) { 464 | return m_ht.at(key, precalculated_hash); 465 | } 466 | 467 | /** 468 | * @copydoc at(const K& key) 469 | */ 470 | template < 471 | class K, class KE = KeyEqual, 472 | typename std::enable_if::value>::type* = nullptr> 473 | const T& at(const K& key) const { 474 | return m_ht.at(key); 475 | } 476 | 477 | /** 478 | * @copydoc at(const K& key, std::size_t precalculated_hash) 479 | */ 480 | template < 481 | class K, class KE = KeyEqual, 482 | typename std::enable_if::value>::type* = nullptr> 483 | const T& at(const K& key, std::size_t precalculated_hash) const { 484 | return m_ht.at(key, precalculated_hash); 485 | } 486 | 487 | T& operator[](const Key& key) { return m_ht[key]; } 488 | T& operator[](Key&& key) { return m_ht[std::move(key)]; } 489 | 490 | size_type count(const Key& key) const { return m_ht.count(key); } 491 | 492 | /** 493 | * Use the hash value 'precalculated_hash' instead of hashing the key. The 494 | * hash value should be the same as hash_function()(key). Useful to speed-up 495 | * the lookup if you already have the hash. 496 | */ 497 | size_type count(const Key& key, std::size_t precalculated_hash) const { 498 | return m_ht.count(key, precalculated_hash); 499 | } 500 | 501 | /** 502 | * This overload only participates in the overload resolution if the typedef 503 | * KeyEqual::is_transparent exists. If so, K must be hashable and comparable 504 | * to Key. 505 | */ 506 | template < 507 | class K, class KE = KeyEqual, 508 | typename std::enable_if::value>::type* = nullptr> 509 | size_type count(const K& key) const { 510 | return m_ht.count(key); 511 | } 512 | 513 | /** 514 | * @copydoc count(const K& key) const 515 | * 516 | * Use the hash value 'precalculated_hash' instead of hashing the key. The 517 | * hash value should be the same as hash_function()(key). Useful to speed-up 518 | * the lookup if you already have the hash. 519 | */ 520 | template < 521 | class K, class KE = KeyEqual, 522 | typename std::enable_if::value>::type* = nullptr> 523 | size_type count(const K& key, std::size_t precalculated_hash) const { 524 | return m_ht.count(key, precalculated_hash); 525 | } 526 | 527 | iterator find(const Key& key) { return m_ht.find(key); } 528 | 529 | /** 530 | * Use the hash value 'precalculated_hash' instead of hashing the key. The 531 | * hash value should be the same as hash_function()(key). Useful to speed-up 532 | * the lookup if you already have the hash. 533 | */ 534 | iterator find(const Key& key, std::size_t precalculated_hash) { 535 | return m_ht.find(key, precalculated_hash); 536 | } 537 | 538 | const_iterator find(const Key& key) const { return m_ht.find(key); } 539 | 540 | /** 541 | * @copydoc find(const Key& key, std::size_t precalculated_hash) 542 | */ 543 | const_iterator find(const Key& key, std::size_t precalculated_hash) const { 544 | return m_ht.find(key, precalculated_hash); 545 | } 546 | 547 | /** 548 | * This overload only participates in the overload resolution if the typedef 549 | * KeyEqual::is_transparent exists. If so, K must be hashable and comparable 550 | * to Key. 551 | */ 552 | template < 553 | class K, class KE = KeyEqual, 554 | typename std::enable_if::value>::type* = nullptr> 555 | iterator find(const K& key) { 556 | return m_ht.find(key); 557 | } 558 | 559 | /** 560 | * @copydoc find(const K& key) 561 | * 562 | * Use the hash value 'precalculated_hash' instead of hashing the key. The 563 | * hash value should be the same as hash_function()(key). Useful to speed-up 564 | * the lookup if you already have the hash. 565 | */ 566 | template < 567 | class K, class KE = KeyEqual, 568 | typename std::enable_if::value>::type* = nullptr> 569 | iterator find(const K& key, std::size_t precalculated_hash) { 570 | return m_ht.find(key, precalculated_hash); 571 | } 572 | 573 | /** 574 | * @copydoc find(const K& key) 575 | */ 576 | template < 577 | class K, class KE = KeyEqual, 578 | typename std::enable_if::value>::type* = nullptr> 579 | const_iterator find(const K& key) const { 580 | return m_ht.find(key); 581 | } 582 | 583 | /** 584 | * @copydoc find(const K& key) 585 | * 586 | * Use the hash value 'precalculated_hash' instead of hashing the key. The 587 | * hash value should be the same as hash_function()(key). Useful to speed-up 588 | * the lookup if you already have the hash. 589 | */ 590 | template < 591 | class K, class KE = KeyEqual, 592 | typename std::enable_if::value>::type* = nullptr> 593 | const_iterator find(const K& key, std::size_t precalculated_hash) const { 594 | return m_ht.find(key, precalculated_hash); 595 | } 596 | 597 | bool contains(const Key& key) const { return m_ht.contains(key); } 598 | 599 | /** 600 | * Use the hash value 'precalculated_hash' instead of hashing the key. The 601 | * hash value should be the same as hash_function()(key). Useful to speed-up 602 | * the lookup if you already have the hash. 603 | */ 604 | bool contains(const Key& key, std::size_t precalculated_hash) const { 605 | return m_ht.contains(key, precalculated_hash); 606 | } 607 | 608 | /** 609 | * This overload only participates in the overload resolution if the typedef 610 | * KeyEqual::is_transparent exists. If so, K must be hashable and comparable 611 | * to Key. 612 | */ 613 | template < 614 | class K, class KE = KeyEqual, 615 | typename std::enable_if::value>::type* = nullptr> 616 | bool contains(const K& key) const { 617 | return m_ht.contains(key); 618 | } 619 | 620 | /** 621 | * @copydoc contains(const K& key) const 622 | * 623 | * Use the hash value 'precalculated_hash' instead of hashing the key. The 624 | * hash value should be the same as hash_function()(key). Useful to speed-up 625 | * the lookup if you already have the hash. 626 | */ 627 | template < 628 | class K, class KE = KeyEqual, 629 | typename std::enable_if::value>::type* = nullptr> 630 | bool contains(const K& key, std::size_t precalculated_hash) const { 631 | return m_ht.contains(key, precalculated_hash); 632 | } 633 | 634 | std::pair equal_range(const Key& key) { 635 | return m_ht.equal_range(key); 636 | } 637 | 638 | /** 639 | * Use the hash value 'precalculated_hash' instead of hashing the key. The 640 | * hash value should be the same as hash_function()(key). Useful to speed-up 641 | * the lookup if you already have the hash. 642 | */ 643 | std::pair equal_range(const Key& key, 644 | std::size_t precalculated_hash) { 645 | return m_ht.equal_range(key, precalculated_hash); 646 | } 647 | 648 | std::pair equal_range(const Key& key) const { 649 | return m_ht.equal_range(key); 650 | } 651 | 652 | /** 653 | * @copydoc equal_range(const Key& key, std::size_t precalculated_hash) 654 | */ 655 | std::pair equal_range( 656 | const Key& key, std::size_t precalculated_hash) const { 657 | return m_ht.equal_range(key, precalculated_hash); 658 | } 659 | 660 | /** 661 | * This overload only participates in the overload resolution if the typedef 662 | * KeyEqual::is_transparent exists. If so, K must be hashable and comparable 663 | * to Key. 664 | */ 665 | template < 666 | class K, class KE = KeyEqual, 667 | typename std::enable_if::value>::type* = nullptr> 668 | std::pair equal_range(const K& key) { 669 | return m_ht.equal_range(key); 670 | } 671 | 672 | /** 673 | * @copydoc equal_range(const K& key) 674 | * 675 | * Use the hash value 'precalculated_hash' instead of hashing the key. The 676 | * hash value should be the same as hash_function()(key). Useful to speed-up 677 | * the lookup if you already have the hash. 678 | */ 679 | template < 680 | class K, class KE = KeyEqual, 681 | typename std::enable_if::value>::type* = nullptr> 682 | std::pair equal_range(const K& key, 683 | std::size_t precalculated_hash) { 684 | return m_ht.equal_range(key, precalculated_hash); 685 | } 686 | 687 | /** 688 | * @copydoc equal_range(const K& key) 689 | */ 690 | template < 691 | class K, class KE = KeyEqual, 692 | typename std::enable_if::value>::type* = nullptr> 693 | std::pair equal_range(const K& key) const { 694 | return m_ht.equal_range(key); 695 | } 696 | 697 | /** 698 | * @copydoc equal_range(const K& key, std::size_t precalculated_hash) 699 | */ 700 | template < 701 | class K, class KE = KeyEqual, 702 | typename std::enable_if::value>::type* = nullptr> 703 | std::pair equal_range( 704 | const K& key, std::size_t precalculated_hash) const { 705 | return m_ht.equal_range(key, precalculated_hash); 706 | } 707 | 708 | /* 709 | * Bucket interface 710 | */ 711 | size_type bucket_count() const { return m_ht.bucket_count(); } 712 | size_type max_bucket_count() const { return m_ht.max_bucket_count(); } 713 | 714 | /* 715 | * Hash policy 716 | */ 717 | float load_factor() const { return m_ht.load_factor(); } 718 | float max_load_factor() const { return m_ht.max_load_factor(); } 719 | void max_load_factor(float ml) { m_ht.max_load_factor(ml); } 720 | 721 | void rehash(size_type count) { m_ht.rehash(count); } 722 | void reserve(size_type count) { m_ht.reserve(count); } 723 | 724 | /* 725 | * Observers 726 | */ 727 | hasher hash_function() const { return m_ht.hash_function(); } 728 | key_equal key_eq() const { return m_ht.key_eq(); } 729 | 730 | /* 731 | * Other 732 | */ 733 | 734 | /** 735 | * Convert a const_iterator to an iterator. 736 | */ 737 | iterator mutable_iterator(const_iterator pos) { 738 | return m_ht.mutable_iterator(pos); 739 | } 740 | 741 | /** 742 | * Requires index <= size(). 743 | * 744 | * Return an iterator to the element at index. Return end() if index == 745 | * size(). 746 | */ 747 | iterator nth(size_type index) { return m_ht.nth(index); } 748 | 749 | /** 750 | * @copydoc nth(size_type index) 751 | */ 752 | const_iterator nth(size_type index) const { return m_ht.nth(index); } 753 | 754 | /** 755 | * Return const_reference to the first element. Requires the container to not 756 | * be empty. 757 | */ 758 | const_reference front() const { return m_ht.front(); } 759 | 760 | /** 761 | * Return const_reference to the last element. Requires the container to not 762 | * be empty. 763 | */ 764 | const_reference back() const { return m_ht.back(); } 765 | 766 | /** 767 | * Only available if ValueTypeContainer is a std::vector. Same as calling 768 | * 'values_container().data()'. 769 | */ 770 | template ::value>::type* = nullptr> 773 | const typename values_container_type::value_type* data() const noexcept { 774 | return m_ht.data(); 775 | } 776 | 777 | /** 778 | * Return the container in which the values are stored. The values are in the 779 | * same order as the insertion order and are contiguous in the structure, no 780 | * holes (size() == values_container().size()). 781 | */ 782 | const values_container_type& values_container() const noexcept { 783 | return m_ht.values_container(); 784 | } 785 | 786 | /** 787 | * Release the container in which the values are stored. 788 | * 789 | * The map is empty after this operation. 790 | */ 791 | values_container_type release() { return m_ht.release(); } 792 | 793 | template ::value>::type* = nullptr> 796 | size_type capacity() const noexcept { 797 | return m_ht.capacity(); 798 | } 799 | 800 | void shrink_to_fit() { m_ht.shrink_to_fit(); } 801 | 802 | /** 803 | * Insert the value before pos shifting all the elements on the right of pos 804 | * (including pos) one position to the right. 805 | * 806 | * O(bucket_count()) runtime complexity. 807 | */ 808 | std::pair insert_at_position(const_iterator pos, 809 | const value_type& value) { 810 | return m_ht.insert_at_position(pos, value); 811 | } 812 | 813 | /** 814 | * @copydoc insert_at_position(const_iterator pos, const value_type& value) 815 | */ 816 | std::pair insert_at_position(const_iterator pos, 817 | value_type&& value) { 818 | return m_ht.insert_at_position(pos, std::move(value)); 819 | } 820 | 821 | /** 822 | * @copydoc insert_at_position(const_iterator pos, const value_type& value) 823 | * 824 | * Same as insert_at_position(pos, value_type(std::forward(args)...), 825 | * mainly here for coherence. 826 | */ 827 | template 828 | std::pair emplace_at_position(const_iterator pos, 829 | Args&&... args) { 830 | return m_ht.emplace_at_position(pos, std::forward(args)...); 831 | } 832 | 833 | /** 834 | * @copydoc insert_at_position(const_iterator pos, const value_type& value) 835 | */ 836 | template 837 | std::pair try_emplace_at_position(const_iterator pos, 838 | const key_type& k, 839 | Args&&... args) { 840 | return m_ht.try_emplace_at_position(pos, k, std::forward(args)...); 841 | } 842 | 843 | /** 844 | * @copydoc insert_at_position(const_iterator pos, const value_type& value) 845 | */ 846 | template 847 | std::pair try_emplace_at_position(const_iterator pos, 848 | key_type&& k, 849 | Args&&... args) { 850 | return m_ht.try_emplace_at_position(pos, std::move(k), 851 | std::forward(args)...); 852 | } 853 | 854 | void pop_back() { m_ht.pop_back(); } 855 | 856 | /** 857 | * Faster erase operation with an O(1) average complexity but it doesn't 858 | * preserve the insertion order. 859 | * 860 | * If an erasure occurs, the last element of the map will take the place of 861 | * the erased element. 862 | */ 863 | iterator unordered_erase(iterator pos) { return m_ht.unordered_erase(pos); } 864 | 865 | /** 866 | * @copydoc unordered_erase(iterator pos) 867 | */ 868 | iterator unordered_erase(const_iterator pos) { 869 | return m_ht.unordered_erase(pos); 870 | } 871 | 872 | /** 873 | * @copydoc unordered_erase(iterator pos) 874 | */ 875 | size_type unordered_erase(const key_type& key) { 876 | return m_ht.unordered_erase(key); 877 | } 878 | 879 | /** 880 | * @copydoc unordered_erase(iterator pos) 881 | * 882 | * Use the hash value 'precalculated_hash' instead of hashing the key. The 883 | * hash value should be the same as hash_function()(key). Useful to speed-up 884 | * the lookup if you already have the hash. 885 | */ 886 | size_type unordered_erase(const key_type& key, 887 | std::size_t precalculated_hash) { 888 | return m_ht.unordered_erase(key, precalculated_hash); 889 | } 890 | 891 | /** 892 | * @copydoc unordered_erase(iterator pos) 893 | * 894 | * This overload only participates in the overload resolution if the typedef 895 | * KeyEqual::is_transparent exists. If so, K must be hashable and comparable 896 | * to Key. 897 | */ 898 | template < 899 | class K, class KE = KeyEqual, 900 | typename std::enable_if::value>::type* = nullptr> 901 | size_type unordered_erase(const K& key) { 902 | return m_ht.unordered_erase(key); 903 | } 904 | 905 | /** 906 | * @copydoc unordered_erase(const K& key) 907 | * 908 | * Use the hash value 'precalculated_hash' instead of hashing the key. The 909 | * hash value should be the same as hash_function()(key). Useful to speed-up 910 | * the lookup if you already have the hash. 911 | */ 912 | template < 913 | class K, class KE = KeyEqual, 914 | typename std::enable_if::value>::type* = nullptr> 915 | size_type unordered_erase(const K& key, std::size_t precalculated_hash) { 916 | return m_ht.unordered_erase(key, precalculated_hash); 917 | } 918 | 919 | /** 920 | * Serialize the map through the `serializer` parameter. 921 | * 922 | * The `serializer` parameter must be a function object that supports the 923 | * following call: 924 | * - `template void operator()(const U& value);` where the types 925 | * `std::uint64_t`, `float` and `std::pair` must be supported for U. 926 | * 927 | * The implementation leaves binary compatibility (endianness, IEEE 754 for 928 | * floats, ...) of the types it serializes in the hands of the `Serializer` 929 | * function object if compatibility is required. 930 | */ 931 | template 932 | void serialize(Serializer& serializer) const { 933 | m_ht.serialize(serializer); 934 | } 935 | 936 | /** 937 | * Deserialize a previously serialized map through the `deserializer` 938 | * parameter. 939 | * 940 | * The `deserializer` parameter must be a function object that supports the 941 | * following calls: 942 | * - `template U operator()();` where the types `std::uint64_t`, 943 | * `float` and `std::pair` must be supported for U. 944 | * 945 | * If the deserialized hash map type is hash compatible with the serialized 946 | * map, the deserialization process can be sped up by setting 947 | * `hash_compatible` to true. To be hash compatible, the Hash and KeyEqual 948 | * must behave the same way than the ones used on the serialized map. The 949 | * `std::size_t` must also be of the same size as the one on the platform used 950 | * to serialize the map, the same apply for `IndexType`. If these criteria are 951 | * not met, the behaviour is undefined with `hash_compatible` sets to true. 952 | * 953 | * The behaviour is undefined if the type `Key` and `T` of the `ordered_map` 954 | * are not the same as the types used during serialization. 955 | * 956 | * The implementation leaves binary compatibility (endianness, IEEE 754 for 957 | * floats, size of int, ...) of the types it deserializes in the hands of the 958 | * `Deserializer` function object if compatibility is required. 959 | */ 960 | template 961 | static ordered_map deserialize(Deserializer& deserializer, 962 | bool hash_compatible = false) { 963 | ordered_map map(0); 964 | map.m_ht.deserialize(deserializer, hash_compatible); 965 | 966 | return map; 967 | } 968 | 969 | friend bool operator==(const ordered_map& lhs, const ordered_map& rhs) { 970 | return lhs.m_ht == rhs.m_ht; 971 | } 972 | friend bool operator!=(const ordered_map& lhs, const ordered_map& rhs) { 973 | return lhs.m_ht != rhs.m_ht; 974 | } 975 | friend bool operator<(const ordered_map& lhs, const ordered_map& rhs) { 976 | return lhs.m_ht < rhs.m_ht; 977 | } 978 | friend bool operator<=(const ordered_map& lhs, const ordered_map& rhs) { 979 | return lhs.m_ht <= rhs.m_ht; 980 | } 981 | friend bool operator>(const ordered_map& lhs, const ordered_map& rhs) { 982 | return lhs.m_ht > rhs.m_ht; 983 | } 984 | friend bool operator>=(const ordered_map& lhs, const ordered_map& rhs) { 985 | return lhs.m_ht >= rhs.m_ht; 986 | } 987 | 988 | friend void swap(ordered_map& lhs, ordered_map& rhs) { lhs.swap(rhs); } 989 | 990 | private: 991 | ht m_ht; 992 | }; 993 | 994 | } // end namespace tsl 995 | 996 | #endif 997 | -------------------------------------------------------------------------------- /include/tsl/ordered_set.h: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2017 Thibaut Goetghebuer-Planchon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | #ifndef TSL_ORDERED_SET_H 25 | #define TSL_ORDERED_SET_H 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #include "ordered_hash.h" 38 | 39 | namespace tsl { 40 | 41 | /** 42 | * Implementation of an hash set using open addressing with robin hood with 43 | * backshift delete to resolve collisions. 44 | * 45 | * The particularity of this hash set is that it remembers the order in which 46 | * the elements were added and provide a way to access the structure which 47 | * stores these values through the 'values_container()' method. The used 48 | * container is defined by ValueTypeContainer, by default a std::deque is used 49 | * (grows faster) but a std::vector may be used. In this case the set provides a 50 | * 'data()' method which give a direct access to the memory used to store the 51 | * values (which can be useful to communicate with C API's). 52 | * 53 | * The Key must be copy constructible and/or move constructible. To use 54 | * `unordered_erase` it also must be swappable. 55 | * 56 | * The behaviour of the hash set is undefined if the destructor of Key throws an 57 | * exception. 58 | * 59 | * By default the maximum size of a set is limited to 2^32 - 1 values, if needed 60 | * this can be changed through the IndexType template parameter. Using an 61 | * `uint64_t` will raise this limit to 2^64 - 1 values but each bucket will use 62 | * 16 bytes instead of 8 bytes in addition to the space needed to store the 63 | * values. 64 | * 65 | * Iterators invalidation: 66 | * - clear, operator=, reserve, rehash: always invalidate the iterators (also 67 | * invalidate end()). 68 | * - insert, emplace, emplace_hint, operator[]: when a std::vector is used as 69 | * ValueTypeContainer and if size() < capacity(), only end(). Otherwise all the 70 | * iterators are invalidated if an insert occurs. 71 | * - erase, unordered_erase: when a std::vector is used as ValueTypeContainer 72 | * invalidate the iterator of the erased element and all the ones after the 73 | * erased element (including end()). Otherwise all the iterators are invalidated 74 | * if an erase occurs. 75 | */ 76 | template , 77 | class KeyEqual = std::equal_to, 78 | class Allocator = std::allocator, 79 | class ValueTypeContainer = std::deque, 80 | class IndexType = std::uint_least32_t> 81 | class ordered_set { 82 | private: 83 | template 84 | using has_is_transparent = tsl::detail_ordered_hash::has_is_transparent; 85 | 86 | class KeySelect { 87 | public: 88 | using key_type = Key; 89 | 90 | const key_type& operator()(const Key& key) const noexcept { return key; } 91 | 92 | key_type& operator()(Key& key) noexcept { return key; } 93 | }; 94 | 95 | using ht = detail_ordered_hash::ordered_hash; 98 | 99 | public: 100 | using key_type = typename ht::key_type; 101 | using value_type = typename ht::value_type; 102 | using size_type = typename ht::size_type; 103 | using difference_type = typename ht::difference_type; 104 | using hasher = typename ht::hasher; 105 | using key_equal = typename ht::key_equal; 106 | using allocator_type = typename ht::allocator_type; 107 | using reference = typename ht::reference; 108 | using const_reference = typename ht::const_reference; 109 | using pointer = typename ht::pointer; 110 | using const_pointer = typename ht::const_pointer; 111 | using iterator = typename ht::iterator; 112 | using const_iterator = typename ht::const_iterator; 113 | using reverse_iterator = typename ht::reverse_iterator; 114 | using const_reverse_iterator = typename ht::const_reverse_iterator; 115 | 116 | using values_container_type = typename ht::values_container_type; 117 | 118 | /* 119 | * Constructors 120 | */ 121 | ordered_set() : ordered_set(ht::DEFAULT_INIT_BUCKETS_SIZE) {} 122 | 123 | explicit ordered_set(size_type bucket_count, const Hash& hash = Hash(), 124 | const KeyEqual& equal = KeyEqual(), 125 | const Allocator& alloc = Allocator()) 126 | : m_ht(bucket_count, hash, equal, alloc, ht::DEFAULT_MAX_LOAD_FACTOR) {} 127 | 128 | ordered_set(size_type bucket_count, const Allocator& alloc) 129 | : ordered_set(bucket_count, Hash(), KeyEqual(), alloc) {} 130 | 131 | ordered_set(size_type bucket_count, const Hash& hash, const Allocator& alloc) 132 | : ordered_set(bucket_count, hash, KeyEqual(), alloc) {} 133 | 134 | explicit ordered_set(const Allocator& alloc) 135 | : ordered_set(ht::DEFAULT_INIT_BUCKETS_SIZE, alloc) {} 136 | 137 | template 138 | ordered_set(InputIt first, InputIt last, 139 | size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, 140 | const Hash& hash = Hash(), const KeyEqual& equal = KeyEqual(), 141 | const Allocator& alloc = Allocator()) 142 | : ordered_set(bucket_count, hash, equal, alloc) { 143 | insert(first, last); 144 | } 145 | 146 | template 147 | ordered_set(InputIt first, InputIt last, size_type bucket_count, 148 | const Allocator& alloc) 149 | : ordered_set(first, last, bucket_count, Hash(), KeyEqual(), alloc) {} 150 | 151 | template 152 | ordered_set(InputIt first, InputIt last, size_type bucket_count, 153 | const Hash& hash, const Allocator& alloc) 154 | : ordered_set(first, last, bucket_count, hash, KeyEqual(), alloc) {} 155 | 156 | ordered_set(std::initializer_list init, 157 | size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, 158 | const Hash& hash = Hash(), const KeyEqual& equal = KeyEqual(), 159 | const Allocator& alloc = Allocator()) 160 | : ordered_set(init.begin(), init.end(), bucket_count, hash, equal, 161 | alloc) {} 162 | 163 | ordered_set(std::initializer_list init, size_type bucket_count, 164 | const Allocator& alloc) 165 | : ordered_set(init.begin(), init.end(), bucket_count, Hash(), KeyEqual(), 166 | alloc) {} 167 | 168 | ordered_set(std::initializer_list init, size_type bucket_count, 169 | const Hash& hash, const Allocator& alloc) 170 | : ordered_set(init.begin(), init.end(), bucket_count, hash, KeyEqual(), 171 | alloc) {} 172 | 173 | ordered_set& operator=(std::initializer_list ilist) { 174 | m_ht.clear(); 175 | 176 | m_ht.reserve(ilist.size()); 177 | m_ht.insert(ilist.begin(), ilist.end()); 178 | 179 | return *this; 180 | } 181 | 182 | allocator_type get_allocator() const { return m_ht.get_allocator(); } 183 | 184 | /* 185 | * Iterators 186 | */ 187 | iterator begin() noexcept { return m_ht.begin(); } 188 | const_iterator begin() const noexcept { return m_ht.begin(); } 189 | const_iterator cbegin() const noexcept { return m_ht.cbegin(); } 190 | 191 | iterator end() noexcept { return m_ht.end(); } 192 | const_iterator end() const noexcept { return m_ht.end(); } 193 | const_iterator cend() const noexcept { return m_ht.cend(); } 194 | 195 | reverse_iterator rbegin() noexcept { return m_ht.rbegin(); } 196 | const_reverse_iterator rbegin() const noexcept { return m_ht.rbegin(); } 197 | const_reverse_iterator rcbegin() const noexcept { return m_ht.rcbegin(); } 198 | 199 | reverse_iterator rend() noexcept { return m_ht.rend(); } 200 | const_reverse_iterator rend() const noexcept { return m_ht.rend(); } 201 | const_reverse_iterator rcend() const noexcept { return m_ht.rcend(); } 202 | 203 | /* 204 | * Capacity 205 | */ 206 | bool empty() const noexcept { return m_ht.empty(); } 207 | size_type size() const noexcept { return m_ht.size(); } 208 | size_type max_size() const noexcept { return m_ht.max_size(); } 209 | 210 | /* 211 | * Modifiers 212 | */ 213 | void clear() noexcept { m_ht.clear(); } 214 | 215 | std::pair insert(const value_type& value) { 216 | return m_ht.insert(value); 217 | } 218 | std::pair insert(value_type&& value) { 219 | return m_ht.insert(std::move(value)); 220 | } 221 | 222 | iterator insert(const_iterator hint, const value_type& value) { 223 | return m_ht.insert_hint(hint, value); 224 | } 225 | 226 | iterator insert(const_iterator hint, value_type&& value) { 227 | return m_ht.insert_hint(hint, std::move(value)); 228 | } 229 | 230 | template 231 | void insert(InputIt first, InputIt last) { 232 | m_ht.insert(first, last); 233 | } 234 | void insert(std::initializer_list ilist) { 235 | m_ht.insert(ilist.begin(), ilist.end()); 236 | } 237 | 238 | /** 239 | * Due to the way elements are stored, emplace will need to move or copy the 240 | * key-value once. The method is equivalent to 241 | * insert(value_type(std::forward(args)...)); 242 | * 243 | * Mainly here for compatibility with the std::unordered_map interface. 244 | */ 245 | template 246 | std::pair emplace(Args&&... args) { 247 | return m_ht.emplace(std::forward(args)...); 248 | } 249 | 250 | /** 251 | * Due to the way elements are stored, emplace_hint will need to move or copy 252 | * the key-value once. The method is equivalent to insert(hint, 253 | * value_type(std::forward(args)...)); 254 | * 255 | * Mainly here for compatibility with the std::unordered_map interface. 256 | */ 257 | template 258 | iterator emplace_hint(const_iterator hint, Args&&... args) { 259 | return m_ht.emplace_hint(hint, std::forward(args)...); 260 | } 261 | 262 | /** 263 | * When erasing an element, the insert order will be preserved and no holes 264 | * will be present in the container returned by 'values_container()'. 265 | * 266 | * The method is in O(bucket_count()), if the order is not important 267 | * 'unordered_erase(...)' method is faster with an O(1) average complexity. 268 | */ 269 | iterator erase(iterator pos) { return m_ht.erase(pos); } 270 | 271 | /** 272 | * @copydoc erase(iterator pos) 273 | */ 274 | iterator erase(const_iterator pos) { return m_ht.erase(pos); } 275 | 276 | /** 277 | * @copydoc erase(iterator pos) 278 | */ 279 | iterator erase(const_iterator first, const_iterator last) { 280 | return m_ht.erase(first, last); 281 | } 282 | 283 | /** 284 | * @copydoc erase(iterator pos) 285 | */ 286 | size_type erase(const key_type& key) { return m_ht.erase(key); } 287 | 288 | /** 289 | * @copydoc erase(iterator pos) 290 | * 291 | * Use the hash value 'precalculated_hash' instead of hashing the key. The 292 | * hash value should be the same as hash_function()(key). Useful to speed-up 293 | * the lookup to the value if you already have the hash. 294 | */ 295 | size_type erase(const key_type& key, std::size_t precalculated_hash) { 296 | return m_ht.erase(key, precalculated_hash); 297 | } 298 | 299 | /** 300 | * @copydoc erase(iterator pos) 301 | * 302 | * This overload only participates in the overload resolution if the typedef 303 | * KeyEqual::is_transparent exists. If so, K must be hashable and comparable 304 | * to Key. 305 | */ 306 | template < 307 | class K, class KE = KeyEqual, 308 | typename std::enable_if::value>::type* = nullptr> 309 | size_type erase(const K& key) { 310 | return m_ht.erase(key); 311 | } 312 | 313 | /** 314 | * @copydoc erase(const key_type& key, std::size_t precalculated_hash) 315 | * 316 | * This overload only participates in the overload resolution if the typedef 317 | * KeyEqual::is_transparent exists. If so, K must be hashable and comparable 318 | * to Key. 319 | */ 320 | template < 321 | class K, class KE = KeyEqual, 322 | typename std::enable_if::value>::type* = nullptr> 323 | size_type erase(const K& key, std::size_t precalculated_hash) { 324 | return m_ht.erase(key, precalculated_hash); 325 | } 326 | 327 | /** 328 | * @copydoc erase(iterator pos) 329 | * 330 | * Erases all elements that satisfy the predicate pred. The method is in 331 | * O(n). Note that the function only has the strong exception guarantee if 332 | * the Predicate, Hash, and Key predicates and moves of keys and values do 333 | * not throw. If an exception is raised, the object is in an invalid state. 334 | * It can still be cleared and destroyed without leaking memory. 335 | */ 336 | template 337 | friend size_type erase_if(ordered_set &set, Predicate pred) { 338 | return set.m_ht.erase_if(pred); 339 | } 340 | 341 | void swap(ordered_set& other) { other.m_ht.swap(m_ht); } 342 | 343 | /* 344 | * Lookup 345 | */ 346 | size_type count(const Key& key) const { return m_ht.count(key); } 347 | 348 | /** 349 | * Use the hash value 'precalculated_hash' instead of hashing the key. The 350 | * hash value should be the same as hash_function()(key). Useful to speed-up 351 | * the lookup if you already have the hash. 352 | */ 353 | size_type count(const Key& key, std::size_t precalculated_hash) const { 354 | return m_ht.count(key, precalculated_hash); 355 | } 356 | 357 | /** 358 | * This overload only participates in the overload resolution if the typedef 359 | * KeyEqual::is_transparent exists. If so, K must be hashable and comparable 360 | * to Key. 361 | */ 362 | template < 363 | class K, class KE = KeyEqual, 364 | typename std::enable_if::value>::type* = nullptr> 365 | size_type count(const K& key) const { 366 | return m_ht.count(key); 367 | } 368 | 369 | /** 370 | * @copydoc count(const K& key) const 371 | * 372 | * Use the hash value 'precalculated_hash' instead of hashing the key. The 373 | * hash value should be the same as hash_function()(key). Useful to speed-up 374 | * the lookup if you already have the hash. 375 | */ 376 | template < 377 | class K, class KE = KeyEqual, 378 | typename std::enable_if::value>::type* = nullptr> 379 | size_type count(const K& key, std::size_t precalculated_hash) const { 380 | return m_ht.count(key, precalculated_hash); 381 | } 382 | 383 | iterator find(const Key& key) { return m_ht.find(key); } 384 | 385 | /** 386 | * Use the hash value 'precalculated_hash' instead of hashing the key. The 387 | * hash value should be the same as hash_function()(key). Useful to speed-up 388 | * the lookup if you already have the hash. 389 | */ 390 | iterator find(const Key& key, std::size_t precalculated_hash) { 391 | return m_ht.find(key, precalculated_hash); 392 | } 393 | 394 | const_iterator find(const Key& key) const { return m_ht.find(key); } 395 | 396 | /** 397 | * @copydoc find(const Key& key, std::size_t precalculated_hash) 398 | */ 399 | const_iterator find(const Key& key, std::size_t precalculated_hash) const { 400 | return m_ht.find(key, precalculated_hash); 401 | } 402 | 403 | /** 404 | * This overload only participates in the overload resolution if the typedef 405 | * KeyEqual::is_transparent exists. If so, K must be hashable and comparable 406 | * to Key. 407 | */ 408 | template < 409 | class K, class KE = KeyEqual, 410 | typename std::enable_if::value>::type* = nullptr> 411 | iterator find(const K& key) { 412 | return m_ht.find(key); 413 | } 414 | 415 | /** 416 | * @copydoc find(const K& key) 417 | * 418 | * Use the hash value 'precalculated_hash' instead of hashing the key. The 419 | * hash value should be the same as hash_function()(key). Useful to speed-up 420 | * the lookup if you already have the hash. 421 | */ 422 | template < 423 | class K, class KE = KeyEqual, 424 | typename std::enable_if::value>::type* = nullptr> 425 | iterator find(const K& key, std::size_t precalculated_hash) { 426 | return m_ht.find(key, precalculated_hash); 427 | } 428 | 429 | /** 430 | * @copydoc find(const K& key) 431 | */ 432 | template < 433 | class K, class KE = KeyEqual, 434 | typename std::enable_if::value>::type* = nullptr> 435 | const_iterator find(const K& key) const { 436 | return m_ht.find(key); 437 | } 438 | 439 | /** 440 | * @copydoc find(const K& key) 441 | * 442 | * Use the hash value 'precalculated_hash' instead of hashing the key. The 443 | * hash value should be the same as hash_function()(key). Useful to speed-up 444 | * the lookup if you already have the hash. 445 | */ 446 | template < 447 | class K, class KE = KeyEqual, 448 | typename std::enable_if::value>::type* = nullptr> 449 | const_iterator find(const K& key, std::size_t precalculated_hash) const { 450 | return m_ht.find(key, precalculated_hash); 451 | } 452 | 453 | bool contains(const Key& key) const { return m_ht.contains(key); } 454 | 455 | /** 456 | * Use the hash value 'precalculated_hash' instead of hashing the key. The 457 | * hash value should be the same as hash_function()(key). Useful to speed-up 458 | * the lookup if you already have the hash. 459 | */ 460 | bool contains(const Key& key, std::size_t precalculated_hash) const { 461 | return m_ht.contains(key, precalculated_hash); 462 | } 463 | 464 | /** 465 | * This overload only participates in the overload resolution if the typedef 466 | * KeyEqual::is_transparent exists. If so, K must be hashable and comparable 467 | * to Key. 468 | */ 469 | template < 470 | class K, class KE = KeyEqual, 471 | typename std::enable_if::value>::type* = nullptr> 472 | bool contains(const K& key) const { 473 | return m_ht.contains(key); 474 | } 475 | 476 | /** 477 | * @copydoc contains(const K& key) const 478 | * 479 | * Use the hash value 'precalculated_hash' instead of hashing the key. The 480 | * hash value should be the same as hash_function()(key). Useful to speed-up 481 | * the lookup if you already have the hash. 482 | */ 483 | template < 484 | class K, class KE = KeyEqual, 485 | typename std::enable_if::value>::type* = nullptr> 486 | bool contains(const K& key, std::size_t precalculated_hash) const { 487 | return m_ht.contains(key, precalculated_hash); 488 | } 489 | 490 | std::pair equal_range(const Key& key) { 491 | return m_ht.equal_range(key); 492 | } 493 | 494 | /** 495 | * Use the hash value 'precalculated_hash' instead of hashing the key. The 496 | * hash value should be the same as hash_function()(key). Useful to speed-up 497 | * the lookup if you already have the hash. 498 | */ 499 | std::pair equal_range(const Key& key, 500 | std::size_t precalculated_hash) { 501 | return m_ht.equal_range(key, precalculated_hash); 502 | } 503 | 504 | std::pair equal_range(const Key& key) const { 505 | return m_ht.equal_range(key); 506 | } 507 | 508 | /** 509 | * @copydoc equal_range(const Key& key, std::size_t precalculated_hash) 510 | */ 511 | std::pair equal_range( 512 | const Key& key, std::size_t precalculated_hash) const { 513 | return m_ht.equal_range(key, precalculated_hash); 514 | } 515 | 516 | /** 517 | * This overload only participates in the overload resolution if the typedef 518 | * KeyEqual::is_transparent exists. If so, K must be hashable and comparable 519 | * to Key. 520 | */ 521 | template < 522 | class K, class KE = KeyEqual, 523 | typename std::enable_if::value>::type* = nullptr> 524 | std::pair equal_range(const K& key) { 525 | return m_ht.equal_range(key); 526 | } 527 | 528 | /** 529 | * @copydoc equal_range(const K& key) 530 | * 531 | * Use the hash value 'precalculated_hash' instead of hashing the key. The 532 | * hash value should be the same as hash_function()(key). Useful to speed-up 533 | * the lookup if you already have the hash. 534 | */ 535 | template < 536 | class K, class KE = KeyEqual, 537 | typename std::enable_if::value>::type* = nullptr> 538 | std::pair equal_range(const K& key, 539 | std::size_t precalculated_hash) { 540 | return m_ht.equal_range(key, precalculated_hash); 541 | } 542 | 543 | /** 544 | * @copydoc equal_range(const K& key) 545 | */ 546 | template < 547 | class K, class KE = KeyEqual, 548 | typename std::enable_if::value>::type* = nullptr> 549 | std::pair equal_range(const K& key) const { 550 | return m_ht.equal_range(key); 551 | } 552 | 553 | /** 554 | * @copydoc equal_range(const K& key, std::size_t precalculated_hash) 555 | */ 556 | template < 557 | class K, class KE = KeyEqual, 558 | typename std::enable_if::value>::type* = nullptr> 559 | std::pair equal_range( 560 | const K& key, std::size_t precalculated_hash) const { 561 | return m_ht.equal_range(key, precalculated_hash); 562 | } 563 | 564 | /* 565 | * Bucket interface 566 | */ 567 | size_type bucket_count() const { return m_ht.bucket_count(); } 568 | size_type max_bucket_count() const { return m_ht.max_bucket_count(); } 569 | 570 | /* 571 | * Hash policy 572 | */ 573 | float load_factor() const { return m_ht.load_factor(); } 574 | float max_load_factor() const { return m_ht.max_load_factor(); } 575 | void max_load_factor(float ml) { m_ht.max_load_factor(ml); } 576 | 577 | void rehash(size_type count) { m_ht.rehash(count); } 578 | void reserve(size_type count) { m_ht.reserve(count); } 579 | 580 | /* 581 | * Observers 582 | */ 583 | hasher hash_function() const { return m_ht.hash_function(); } 584 | key_equal key_eq() const { return m_ht.key_eq(); } 585 | 586 | /* 587 | * Other 588 | */ 589 | 590 | /** 591 | * Convert a const_iterator to an iterator. 592 | */ 593 | iterator mutable_iterator(const_iterator pos) { 594 | return m_ht.mutable_iterator(pos); 595 | } 596 | 597 | /** 598 | * Requires index <= size(). 599 | * 600 | * Return an iterator to the element at index. Return end() if index == 601 | * size(). 602 | */ 603 | iterator nth(size_type index) { return m_ht.nth(index); } 604 | 605 | /** 606 | * @copydoc nth(size_type index) 607 | */ 608 | const_iterator nth(size_type index) const { return m_ht.nth(index); } 609 | 610 | /** 611 | * Return const_reference to the first element. Requires the container to not 612 | * be empty. 613 | */ 614 | const_reference front() const { return m_ht.front(); } 615 | 616 | /** 617 | * Return const_reference to the last element. Requires the container to not 618 | * be empty. 619 | */ 620 | const_reference back() const { return m_ht.back(); } 621 | 622 | /** 623 | * Only available if ValueTypeContainer is a std::vector. Same as calling 624 | * 'values_container().data()'. 625 | */ 626 | template ::value>::type* = nullptr> 629 | const typename values_container_type::value_type* data() const noexcept { 630 | return m_ht.data(); 631 | } 632 | 633 | /** 634 | * Return the container in which the values are stored. The values are in the 635 | * same order as the insertion order and are contiguous in the structure, no 636 | * holes (size() == values_container().size()). 637 | */ 638 | const values_container_type& values_container() const noexcept { 639 | return m_ht.values_container(); 640 | } 641 | 642 | /** 643 | * Release the container in which the values are stored. 644 | * 645 | * The set is empty after this operation. 646 | */ 647 | values_container_type release() { return m_ht.release(); } 648 | 649 | template ::value>::type* = nullptr> 652 | size_type capacity() const noexcept { 653 | return m_ht.capacity(); 654 | } 655 | 656 | void shrink_to_fit() { m_ht.shrink_to_fit(); } 657 | 658 | /** 659 | * Insert the value before pos shifting all the elements on the right of pos 660 | * (including pos) one position to the right. 661 | * 662 | * O(bucket_count()) runtime complexity. 663 | */ 664 | std::pair insert_at_position(const_iterator pos, 665 | const value_type& value) { 666 | return m_ht.insert_at_position(pos, value); 667 | } 668 | 669 | /** 670 | * @copydoc insert_at_position(const_iterator pos, const value_type& value) 671 | */ 672 | std::pair insert_at_position(const_iterator pos, 673 | value_type&& value) { 674 | return m_ht.insert_at_position(pos, std::move(value)); 675 | } 676 | 677 | /** 678 | * @copydoc insert_at_position(const_iterator pos, const value_type& value) 679 | * 680 | * Same as insert_at_position(pos, value_type(std::forward(args)...), 681 | * mainly here for coherence. 682 | */ 683 | template 684 | std::pair emplace_at_position(const_iterator pos, 685 | Args&&... args) { 686 | return m_ht.emplace_at_position(pos, std::forward(args)...); 687 | } 688 | 689 | void pop_back() { m_ht.pop_back(); } 690 | 691 | /** 692 | * Faster erase operation with an O(1) average complexity but it doesn't 693 | * preserve the insertion order. 694 | * 695 | * If an erasure occurs, the last element of the map will take the place of 696 | * the erased element. 697 | */ 698 | iterator unordered_erase(iterator pos) { return m_ht.unordered_erase(pos); } 699 | 700 | /** 701 | * @copydoc unordered_erase(iterator pos) 702 | */ 703 | iterator unordered_erase(const_iterator pos) { 704 | return m_ht.unordered_erase(pos); 705 | } 706 | 707 | /** 708 | * @copydoc unordered_erase(iterator pos) 709 | */ 710 | size_type unordered_erase(const key_type& key) { 711 | return m_ht.unordered_erase(key); 712 | } 713 | 714 | /** 715 | * @copydoc unordered_erase(iterator pos) 716 | * 717 | * Use the hash value 'precalculated_hash' instead of hashing the key. The 718 | * hash value should be the same as hash_function()(key). Useful to speed-up 719 | * the lookup if you already have the hash. 720 | */ 721 | size_type unordered_erase(const key_type& key, 722 | std::size_t precalculated_hash) { 723 | return m_ht.unordered_erase(key, precalculated_hash); 724 | } 725 | 726 | /** 727 | * @copydoc unordered_erase(iterator pos) 728 | * 729 | * This overload only participates in the overload resolution if the typedef 730 | * KeyEqual::is_transparent exists. If so, K must be hashable and comparable 731 | * to Key. 732 | */ 733 | template < 734 | class K, class KE = KeyEqual, 735 | typename std::enable_if::value>::type* = nullptr> 736 | size_type unordered_erase(const K& key) { 737 | return m_ht.unordered_erase(key); 738 | } 739 | 740 | /** 741 | * @copydoc unordered_erase(const K& key) 742 | * 743 | * Use the hash value 'precalculated_hash' instead of hashing the key. The 744 | * hash value should be the same as hash_function()(key). Useful to speed-up 745 | * the lookup if you already have the hash. 746 | */ 747 | template < 748 | class K, class KE = KeyEqual, 749 | typename std::enable_if::value>::type* = nullptr> 750 | size_type unordered_erase(const K& key, std::size_t precalculated_hash) { 751 | return m_ht.unordered_erase(key, precalculated_hash); 752 | } 753 | 754 | /** 755 | * Serialize the set through the `serializer` parameter. 756 | * 757 | * The `serializer` parameter must be a function object that supports the 758 | * following call: 759 | * - `void operator()(const U& value);` where the types `std::uint64_t`, 760 | * `float` and `Key` must be supported for U. 761 | * 762 | * The implementation leaves binary compatibility (endianness, IEEE 754 for 763 | * floats, ...) of the types it serializes in the hands of the `Serializer` 764 | * function object if compatibility is required. 765 | */ 766 | template 767 | void serialize(Serializer& serializer) const { 768 | m_ht.serialize(serializer); 769 | } 770 | 771 | /** 772 | * Deserialize a previously serialized set through the `deserializer` 773 | * parameter. 774 | * 775 | * The `deserializer` parameter must be a function object that supports the 776 | * following calls: 777 | * - `template U operator()();` where the types `std::uint64_t`, 778 | * `float` and `Key` must be supported for U. 779 | * 780 | * If the deserialized hash set type is hash compatible with the serialized 781 | * set, the deserialization process can be sped up by setting 782 | * `hash_compatible` to true. To be hash compatible, the Hash and KeyEqual 783 | * must behave the same way than the ones used on the serialized map. The 784 | * `std::size_t` must also be of the same size as the one on the platform used 785 | * to serialize the map, the same apply for `IndexType`. If these criteria are 786 | * not met, the behaviour is undefined with `hash_compatible` sets to true. 787 | * 788 | * The behaviour is undefined if the type `Key` of the `ordered_set` is not 789 | * the same as the type used during serialization. 790 | * 791 | * The implementation leaves binary compatibility (endianness, IEEE 754 for 792 | * floats, size of int, ...) of the types it deserializes in the hands of the 793 | * `Deserializer` function object if compatibility is required. 794 | */ 795 | template 796 | static ordered_set deserialize(Deserializer& deserializer, 797 | bool hash_compatible = false) { 798 | ordered_set set(0); 799 | set.m_ht.deserialize(deserializer, hash_compatible); 800 | 801 | return set; 802 | } 803 | 804 | friend bool operator==(const ordered_set& lhs, const ordered_set& rhs) { 805 | return lhs.m_ht == rhs.m_ht; 806 | } 807 | friend bool operator!=(const ordered_set& lhs, const ordered_set& rhs) { 808 | return lhs.m_ht != rhs.m_ht; 809 | } 810 | friend bool operator<(const ordered_set& lhs, const ordered_set& rhs) { 811 | return lhs.m_ht < rhs.m_ht; 812 | } 813 | friend bool operator<=(const ordered_set& lhs, const ordered_set& rhs) { 814 | return lhs.m_ht <= rhs.m_ht; 815 | } 816 | friend bool operator>(const ordered_set& lhs, const ordered_set& rhs) { 817 | return lhs.m_ht > rhs.m_ht; 818 | } 819 | friend bool operator>=(const ordered_set& lhs, const ordered_set& rhs) { 820 | return lhs.m_ht >= rhs.m_ht; 821 | } 822 | 823 | friend void swap(ordered_set& lhs, ordered_set& rhs) { lhs.swap(rhs); } 824 | 825 | private: 826 | ht m_ht; 827 | }; 828 | 829 | } // end namespace tsl 830 | 831 | #endif 832 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | 3 | project(tsl_ordered_map_tests) 4 | 5 | add_executable(tsl_ordered_map_tests "main.cpp" 6 | "custom_allocator_tests.cpp" 7 | "ordered_map_tests.cpp" 8 | "ordered_set_tests.cpp") 9 | 10 | target_compile_features(tsl_ordered_map_tests PRIVATE cxx_std_11) 11 | 12 | if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") 13 | target_compile_options(tsl_ordered_map_tests PRIVATE -Werror -Wall -Wextra -Wold-style-cast -DTSL_DEBUG -UNDEBUG) 14 | elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") 15 | target_compile_options(tsl_ordered_map_tests PRIVATE /bigobj /WX /W3 /DTSL_DEBUG /UNDEBUG) 16 | endif() 17 | 18 | # Boost::unit_test_framework 19 | set(Boost_USE_STATIC_LIBS ON) 20 | find_package(Boost 1.54.0 REQUIRED COMPONENTS unit_test_framework) 21 | target_link_libraries(tsl_ordered_map_tests PRIVATE Boost::unit_test_framework) 22 | 23 | # tsl::ordered_map 24 | add_subdirectory(../ ${CMAKE_CURRENT_BINARY_DIR}/tsl) 25 | target_link_libraries(tsl_ordered_map_tests PRIVATE tsl::ordered_map) 26 | -------------------------------------------------------------------------------- /tests/custom_allocator_tests.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2017 Thibaut Goetghebuer-Planchon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include "utils.h" 35 | 36 | static std::size_t nb_custom_allocs = 0; 37 | 38 | template 39 | class custom_allocator { 40 | public: 41 | using value_type = T; 42 | using pointer = T*; 43 | using const_pointer = const T*; 44 | using reference = T&; 45 | using const_reference = const T&; 46 | using size_type = std::size_t; 47 | using difference_type = std::ptrdiff_t; 48 | using propagate_on_container_move_assignment = std::true_type; 49 | 50 | template 51 | struct rebind { 52 | using other = custom_allocator; 53 | }; 54 | 55 | custom_allocator() = default; 56 | custom_allocator(const custom_allocator&) = default; 57 | 58 | template 59 | custom_allocator(const custom_allocator&) {} 60 | 61 | pointer address(reference x) const noexcept { return &x; } 62 | 63 | const_pointer address(const_reference x) const noexcept { return &x; } 64 | 65 | pointer allocate(size_type n, const void* /*hint*/ = 0) { 66 | nb_custom_allocs++; 67 | 68 | pointer ptr = static_cast(std::malloc(n * sizeof(T))); 69 | if (ptr == nullptr) { 70 | #ifdef TSL_OH_NO_EXCEPTIONS 71 | std::abort(); 72 | #else 73 | throw std::bad_alloc(); 74 | #endif 75 | } 76 | 77 | return ptr; 78 | } 79 | 80 | void deallocate(T* p, size_type /*n*/) { std::free(p); } 81 | 82 | size_type max_size() const noexcept { 83 | return std::numeric_limits::max() / sizeof(value_type); 84 | } 85 | 86 | template 87 | void construct(U* p, Args&&... args) { 88 | ::new (static_cast(p)) U(std::forward(args)...); 89 | } 90 | 91 | template 92 | void destroy(U* p) { 93 | p->~U(); 94 | } 95 | }; 96 | 97 | template 98 | bool operator==(const custom_allocator&, const custom_allocator&) { 99 | return true; 100 | } 101 | 102 | template 103 | bool operator!=(const custom_allocator&, const custom_allocator&) { 104 | return false; 105 | } 106 | 107 | // TODO Avoid overloading new to check number of global new. 108 | // How can we check we only go through the allocator for allocation? 109 | 110 | // static std::size_t nb_global_new = 0; 111 | // void* operator new(std::size_t sz) { 112 | // nb_global_new++; 113 | // return std::malloc(sz); 114 | // } 115 | // 116 | // void operator delete(void* ptr) noexcept { 117 | // std::free(ptr); 118 | // } 119 | 120 | BOOST_AUTO_TEST_SUITE(test_custom_allocator) 121 | 122 | BOOST_AUTO_TEST_CASE(test_custom_allocator_1) { 123 | // nb_global_new = 0; 124 | nb_custom_allocs = 0; 125 | 126 | tsl::ordered_map, std::equal_to, 127 | custom_allocator>> 128 | map; 129 | 130 | const int nb_elements = 1000; 131 | for (int i = 0; i < nb_elements; i++) { 132 | map.insert({i, i * 2}); 133 | } 134 | 135 | BOOST_CHECK_NE(nb_custom_allocs, 0u); 136 | // BOOST_CHECK_EQUAL(nb_global_new, 0); 137 | } 138 | 139 | BOOST_AUTO_TEST_SUITE_END() 140 | -------------------------------------------------------------------------------- /tests/main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2017 Thibaut Goetghebuer-Planchon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | #define BOOST_TEST_MODULE ordered_map_tests 25 | 26 | #include 27 | -------------------------------------------------------------------------------- /tests/ordered_set_tests.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2017 Thibaut Goetghebuer-Planchon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include "tsl/ordered_set.h" 36 | #include "utils.h" 37 | 38 | BOOST_AUTO_TEST_SUITE(test_ordered_set) 39 | 40 | using test_types = boost::mpl::list< 41 | tsl::ordered_set, 42 | tsl::ordered_set, 43 | std::equal_to, std::allocator, 44 | std::vector>, 45 | tsl::ordered_set>, tsl::ordered_set, 46 | tsl::ordered_set>, 47 | tsl::ordered_set>>; 48 | 49 | /** 50 | * insert 51 | */ 52 | BOOST_AUTO_TEST_CASE_TEMPLATE(test_insert, HSet, test_types) { 53 | // insert x values, insert them again, check values through find, check order 54 | // through iterator 55 | using key_t = typename HSet::key_type; 56 | 57 | const std::size_t nb_values = 1000; 58 | 59 | HSet set; 60 | typename HSet::iterator it; 61 | bool inserted; 62 | 63 | for (std::size_t i = 0; i < nb_values; i++) { 64 | const std::size_t insert_val = (i % 2 == 0) ? i : nb_values + i; 65 | std::tie(it, inserted) = set.insert(utils::get_key(insert_val)); 66 | 67 | BOOST_CHECK_EQUAL(*it, utils::get_key(insert_val)); 68 | BOOST_CHECK(inserted); 69 | } 70 | BOOST_CHECK_EQUAL(set.size(), nb_values); 71 | 72 | for (std::size_t i = 0; i < nb_values; i++) { 73 | const std::size_t insert_val = (i % 2 == 0) ? i : nb_values + i; 74 | std::tie(it, inserted) = set.insert(utils::get_key(insert_val)); 75 | 76 | BOOST_CHECK_EQUAL(*it, utils::get_key(insert_val)); 77 | BOOST_CHECK(!inserted); 78 | } 79 | BOOST_CHECK_EQUAL(set.size(), nb_values); 80 | 81 | for (std::size_t i = 0; i < nb_values; i++) { 82 | const std::size_t insert_val = (i % 2 == 0) ? i : nb_values + i; 83 | it = set.find(utils::get_key(insert_val)); 84 | 85 | BOOST_CHECK_EQUAL(*it, utils::get_key(insert_val)); 86 | } 87 | 88 | std::size_t i = 0; 89 | for (const auto& value : set) { 90 | const std::size_t insert_val = (i % 2 == 0) ? i : nb_values + i; 91 | 92 | BOOST_CHECK_EQUAL(value, utils::get_key(insert_val)); 93 | 94 | i++; 95 | } 96 | } 97 | 98 | BOOST_AUTO_TEST_CASE(test_compare) { 99 | const tsl::ordered_set map = {"D", "L", "A"}; 100 | 101 | BOOST_ASSERT(map == (tsl::ordered_set{"D", "L", "A"})); 102 | BOOST_ASSERT(map != (tsl::ordered_set{"L", "D", "A"})); 103 | 104 | BOOST_ASSERT(map < (tsl::ordered_set{"D", "L", "B"})); 105 | BOOST_ASSERT(map <= (tsl::ordered_set{"D", "L", "B"})); 106 | BOOST_ASSERT(map <= (tsl::ordered_set{"D", "L", "A"})); 107 | 108 | BOOST_ASSERT(map > (tsl::ordered_set{"D", {"K", 2}, "A"})); 109 | BOOST_ASSERT(map >= (tsl::ordered_set{"D", {"K", 2}, "A"})); 110 | BOOST_ASSERT(map >= (tsl::ordered_set{"D", "L", "A"})); 111 | } 112 | 113 | BOOST_AUTO_TEST_CASE(test_insert_pointer) { 114 | // Test added mainly to be sure that the code compiles with MSVC 115 | std::string value; 116 | std::string* value_ptr = &value; 117 | 118 | tsl::ordered_set set; 119 | set.insert(value_ptr); 120 | set.emplace(value_ptr); 121 | 122 | BOOST_CHECK_EQUAL(set.size(), 1u); 123 | BOOST_CHECK_EQUAL(**set.begin(), value); 124 | } 125 | 126 | BOOST_AUTO_TEST_CASE(test_erase_if) { 127 | tsl::ordered_set set{1, 2, 3, 4, 5}; 128 | auto num = erase_if(set, [](int x) { return 2 <= x && x <= 4; }); 129 | 130 | BOOST_CHECK_EQUAL(num, 3); 131 | BOOST_CHECK_EQUAL(set.size(), 2); 132 | BOOST_CHECK_EQUAL(set.front(), 1); 133 | BOOST_CHECK_EQUAL(set.back(), 5); 134 | } 135 | 136 | /** 137 | * serialize and deserialize 138 | */ 139 | BOOST_AUTO_TEST_CASE(test_serialize_deserialize) { 140 | // insert x values; delete some values; serialize set; deserialize in new set; 141 | // check equal. for deserialization, test it with and without hash 142 | // compatibility. 143 | const std::size_t nb_values = 1000; 144 | 145 | tsl::ordered_set set; 146 | for (std::size_t i = 0; i < nb_values + 40; i++) { 147 | set.insert(utils::get_key(i)); 148 | } 149 | 150 | for (std::size_t i = nb_values; i < nb_values + 40; i++) { 151 | set.erase(utils::get_key(i)); 152 | } 153 | BOOST_CHECK_EQUAL(set.size(), nb_values); 154 | 155 | serializer serial; 156 | set.serialize(serial); 157 | 158 | deserializer dserial(serial.str()); 159 | auto set_deserialized = decltype(set)::deserialize(dserial, true); 160 | BOOST_CHECK(set == set_deserialized); 161 | 162 | deserializer dserial2(serial.str()); 163 | set_deserialized = decltype(set)::deserialize(dserial2, false); 164 | BOOST_CHECK(set_deserialized == set); 165 | } 166 | 167 | BOOST_AUTO_TEST_SUITE_END() 168 | -------------------------------------------------------------------------------- /tests/utils.h: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2017 Thibaut Goetghebuer-Planchon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | #ifndef TSL_UTILS_H 25 | #define TSL_UTILS_H 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include "tsl/ordered_hash.h" 36 | 37 | #ifdef TSL_OH_NO_EXCEPTIONS 38 | #define TSL_OH_CHECK_THROW(S, E) 39 | #else 40 | #define TSL_OH_CHECK_THROW(S, E) BOOST_CHECK_THROW(S, E) 41 | #endif 42 | 43 | template 44 | class identity_hash { 45 | public: 46 | std::size_t operator()(const T& value) const { 47 | return static_cast(value); 48 | } 49 | }; 50 | 51 | template 52 | class mod_hash { 53 | public: 54 | template 55 | std::size_t operator()(const T& value) const { 56 | return std::hash()(value) % MOD; 57 | } 58 | }; 59 | 60 | class move_only_test { 61 | public: 62 | explicit move_only_test(std::int64_t value) 63 | : m_value(new std::int64_t(value)) {} 64 | 65 | move_only_test(const move_only_test&) = delete; 66 | move_only_test(move_only_test&&) = default; 67 | move_only_test& operator=(const move_only_test&) = delete; 68 | move_only_test& operator=(move_only_test&&) = default; 69 | 70 | friend std::ostream& operator<<(std::ostream& stream, 71 | const move_only_test& value) { 72 | if (value.m_value == nullptr) { 73 | stream << "null"; 74 | } else { 75 | stream << *value.m_value; 76 | } 77 | 78 | return stream; 79 | } 80 | 81 | friend bool operator==(const move_only_test& lhs, const move_only_test& rhs) { 82 | if (lhs.m_value == nullptr || rhs.m_value == nullptr) { 83 | return lhs.m_value == nullptr && rhs.m_value == nullptr; 84 | } else { 85 | return *lhs.m_value == *rhs.m_value; 86 | } 87 | } 88 | 89 | friend bool operator!=(const move_only_test& lhs, const move_only_test& rhs) { 90 | return !(lhs == rhs); 91 | } 92 | 93 | std::int64_t value() const { return *m_value; } 94 | 95 | private: 96 | std::unique_ptr m_value; 97 | }; 98 | 99 | class copy_constructible_only_test { 100 | public: 101 | explicit copy_constructible_only_test(std::int64_t value) : m_value(value) {} 102 | 103 | copy_constructible_only_test(const copy_constructible_only_test&) = default; 104 | copy_constructible_only_test(copy_constructible_only_test&&) = delete; 105 | copy_constructible_only_test& operator=(const copy_constructible_only_test&) = 106 | delete; 107 | copy_constructible_only_test& operator=(copy_constructible_only_test&&) = 108 | delete; 109 | 110 | friend std::ostream& operator<<(std::ostream& stream, 111 | const copy_constructible_only_test& value) { 112 | stream << value.m_value; 113 | return stream; 114 | } 115 | 116 | friend bool operator==(const copy_constructible_only_test& lhs, 117 | const copy_constructible_only_test& rhs) { 118 | return lhs.m_value == rhs.m_value; 119 | } 120 | 121 | friend bool operator!=(const copy_constructible_only_test& lhs, 122 | const copy_constructible_only_test& rhs) { 123 | return !(lhs == rhs); 124 | } 125 | 126 | std::int64_t value() const { return m_value; } 127 | 128 | private: 129 | std::int64_t m_value; 130 | }; 131 | 132 | namespace std { 133 | template <> 134 | struct hash { 135 | std::size_t operator()(const move_only_test& val) const { 136 | return std::hash()(val.value()); 137 | } 138 | }; 139 | 140 | template <> 141 | struct hash { 142 | std::size_t operator()(const copy_constructible_only_test& val) const { 143 | return std::hash()(val.value()); 144 | } 145 | }; 146 | } // namespace std 147 | 148 | class utils { 149 | public: 150 | template 151 | static T get_key(std::size_t counter); 152 | 153 | template 154 | static T get_value(std::size_t counter); 155 | 156 | template 157 | static HMap get_filled_hash_map(std::size_t nb_elements); 158 | 159 | /** 160 | * The ordered_map equality operator only compares the m_values structure as 161 | * it is sufficient for ensuring equality. This method do a more extensive 162 | * comparison to ensure that the internal state of the map is coherent. 163 | */ 164 | template 165 | static bool test_is_equal(const HMap& lhs, const HMap& rhs) { 166 | if (lhs != rhs) { 167 | return false; 168 | } 169 | 170 | if(lhs.size() != rhs.size()) { 171 | return false; 172 | } 173 | 174 | for (const auto& val_lhs : lhs) { 175 | auto it_rhs = rhs.find(val_lhs.first); 176 | if (it_rhs == rhs.end()) { 177 | return false; 178 | } 179 | 180 | if (val_lhs.first != it_rhs->first) { 181 | return false; 182 | } 183 | 184 | if (val_lhs.second != it_rhs->second) { 185 | return false; 186 | } 187 | } 188 | 189 | for (const auto& val_rhs : rhs) { 190 | auto it_lhs = lhs.find(val_rhs.first); 191 | if (it_lhs == lhs.end()) { 192 | return false; 193 | } 194 | 195 | if (it_lhs->first != val_rhs.first) { 196 | return false; 197 | } 198 | 199 | if (it_lhs->second != val_rhs.second) { 200 | return false; 201 | } 202 | } 203 | 204 | return true; 205 | } 206 | }; 207 | 208 | template <> 209 | inline std::int64_t utils::get_key(std::size_t counter) { 210 | return tsl::detail_ordered_hash::numeric_cast(counter); 211 | } 212 | 213 | template <> 214 | inline std::string utils::get_key(std::size_t counter) { 215 | return "Key " + std::to_string(counter); 216 | } 217 | 218 | template <> 219 | inline move_only_test utils::get_key(std::size_t counter) { 220 | return move_only_test( 221 | tsl::detail_ordered_hash::numeric_cast(counter)); 222 | } 223 | 224 | template <> 225 | inline std::int64_t utils::get_value(std::size_t counter) { 226 | return tsl::detail_ordered_hash::numeric_cast(counter * 2); 227 | } 228 | 229 | template <> 230 | inline std::string utils::get_value(std::size_t counter) { 231 | return "Value " + std::to_string(counter); 232 | } 233 | 234 | template <> 235 | inline move_only_test utils::get_value(std::size_t counter) { 236 | return move_only_test( 237 | tsl::detail_ordered_hash::numeric_cast(counter * 2)); 238 | } 239 | 240 | template 241 | inline HMap utils::get_filled_hash_map(std::size_t nb_elements) { 242 | using key_tt = typename HMap::key_type; 243 | using value_tt = typename HMap::mapped_type; 244 | 245 | HMap map; 246 | map.reserve(nb_elements); 247 | 248 | for (std::size_t i = 0; i < nb_elements; i++) { 249 | map.insert({utils::get_key(i), utils::get_value(i)}); 250 | } 251 | 252 | return map; 253 | } 254 | 255 | template 256 | struct is_pair : std::false_type {}; 257 | 258 | template 259 | struct is_pair> : std::true_type {}; 260 | 261 | /** 262 | * serializer helper to test serialize(...) and deserialize(...) functions 263 | */ 264 | class serializer { 265 | public: 266 | serializer() { m_ostream.exceptions(m_ostream.badbit | m_ostream.failbit); } 267 | 268 | template 269 | void operator()(const T& val) { 270 | serialize_impl(val); 271 | } 272 | 273 | std::string str() const { return m_ostream.str(); } 274 | 275 | private: 276 | template 277 | void serialize_impl(const std::pair& val) { 278 | serialize_impl(val.first); 279 | serialize_impl(val.second); 280 | } 281 | 282 | void serialize_impl(const std::string& val) { 283 | serialize_impl( 284 | tsl::detail_ordered_hash::numeric_cast(val.size())); 285 | m_ostream.write(val.data(), val.size()); 286 | } 287 | 288 | void serialize_impl(const move_only_test& val) { 289 | serialize_impl(val.value()); 290 | } 291 | 292 | template ::value>::type* = nullptr> 294 | void serialize_impl(const T& val) { 295 | m_ostream.write(reinterpret_cast(&val), sizeof(val)); 296 | } 297 | 298 | private: 299 | std::stringstream m_ostream; 300 | }; 301 | 302 | class deserializer { 303 | public: 304 | explicit deserializer(const std::string& init_str = "") 305 | : m_istream(init_str) { 306 | m_istream.exceptions(m_istream.badbit | m_istream.failbit | 307 | m_istream.eofbit); 308 | } 309 | 310 | template 311 | T operator()() { 312 | return deserialize_impl(); 313 | } 314 | 315 | private: 316 | template ::value>::type* = nullptr> 318 | T deserialize_impl() { 319 | auto first = deserialize_impl(); 320 | return std::make_pair(std::move(first), 321 | deserialize_impl()); 322 | } 323 | 324 | template ::value>::type* = nullptr> 326 | T deserialize_impl() { 327 | const std::size_t str_size = 328 | tsl::detail_ordered_hash::numeric_cast( 329 | deserialize_impl()); 330 | 331 | // TODO std::string::data() return a const pointer pre-C++17. Avoid the 332 | // inefficient double allocation. 333 | std::vector chars(str_size); 334 | m_istream.read(chars.data(), str_size); 335 | 336 | return std::string(chars.data(), chars.size()); 337 | } 338 | 339 | template ::value>::type* = nullptr> 341 | move_only_test deserialize_impl() { 342 | return move_only_test(deserialize_impl()); 343 | } 344 | 345 | template ::value>::type* = nullptr> 347 | T deserialize_impl() { 348 | T val; 349 | m_istream.read(reinterpret_cast(&val), sizeof(val)); 350 | 351 | return val; 352 | } 353 | 354 | private: 355 | std::stringstream m_istream; 356 | }; 357 | 358 | #endif 359 | -------------------------------------------------------------------------------- /tsl-ordered-map.natvis: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {m_ht.m_values} 8 | 9 | m_ht.m_buckets_data._Mypair._Myval2._Mylast - m_ht.m_buckets_data._Mypair._Myval2._Myfirst 10 | m_ht.m_max_load_factor 11 | m_ht.m_values 12 | 13 | 14 | 15 | 16 | {m_iterator} 17 | 18 | m_iterator 19 | 20 | 21 | 22 | --------------------------------------------------------------------------------