diff --git a/include/tsl/boost_offset_pointer.h b/include/tsl/boost_offset_pointer.h new file mode 100644 index 0000000..a94afc1 --- /dev/null +++ b/include/tsl/boost_offset_pointer.h @@ -0,0 +1,24 @@ +#ifndef TSL_SPARSE_MAP_TESTS_BOOST_OFFSET_POINTER_H +#define TSL_SPARSE_MAP_TESTS_BOOST_OFFSET_POINTER_H + +#include "sparse_hash.h" //needed, so the basic template is already included +#include + +namespace tsl { +/* Template specialisation for a "const_cast" of a boost offset_ptr. + * @tparam PT PointedType + * @tparam DT DifferenceType + * @tparam OT OffsetType + * @tparam OA OffsetAlignment + */ +template +struct Remove_Const> { + template + static boost::interprocess::offset_ptr + remove(T const &const_iter) { + return boost::interprocess::const_pointer_cast(const_iter); + } +}; +} // namespace tsl + +#endif // TSL_SPARSE_MAP_TESTS_BOOST_OFFSET_POINTER_H diff --git a/include/tsl/sparse_hash.h b/include/tsl/sparse_hash.h index 96f754c..e383b4e 100644 --- a/include/tsl/sparse_hash.h +++ b/include/tsl/sparse_hash.h @@ -176,7 +176,58 @@ inline int popcount(unsigned int x) { return fallback_popcount(x); } #endif } // namespace detail_popcount + +/* Replacement for const_cast in sparse_array. + * Can be overloaded for specific fancy pointers + * (see: include/tsl/boost_offset_pointer.h). + * This is just a workaround. + * The clean way would be to change the implementation to stop using const_cast. + */ + template + struct Remove_Const { + template + static T remove(V iter) { + return const_cast(iter); + } + }; + namespace detail_sparse_hash { + /* to_address can convert any raw or fancy pointer into a raw pointer. + * It is needed for the allocator construct and destroy calls. + * This specific implementation is based on boost 1.71.0. + */ +#if __cplusplus >= 201400L // with 14-features + template + T *to_address(T *v) noexcept { return v; } + + namespace fancy_ptr_detail { + template + inline T *ptr_address(T *v, int) noexcept { return v; } + + template + inline auto ptr_address(const T &v, int) noexcept + -> decltype(std::pointer_traits::to_address(v)) { + return std::pointer_traits::to_address(v); + } + template + inline auto ptr_address(const T &v, long) noexcept { + return fancy_ptr_detail::ptr_address(v.operator->(), 0); + } + } // namespace detail + + template inline auto to_address(const T &v) noexcept { + return fancy_ptr_detail::ptr_address(v, 0); + } +#else // without 14-features + template + inline T *to_address(T *v) noexcept { return v; } + + template + inline typename std::pointer_traits::element_type * to_address(const T &v) noexcept { + return detail_sparse_hash::to_address(v.operator->()); + } +#endif + template struct make_void { @@ -293,8 +344,11 @@ class sparse_array { using value_type = T; using size_type = std::uint_least8_t; using allocator_type = Allocator; - using iterator = value_type *; - using const_iterator = const value_type *; + using allocator_traits = std::allocator_traits; + using pointer = typename allocator_traits::pointer; + using const_pointer = typename allocator_traits::const_pointer; + using iterator = pointer; + using const_iterator = const_pointer; private: static const size_type CAPACITY_GROWTH_STEP = @@ -585,7 +639,7 @@ class sparse_array { } static iterator mutable_iterator(const_iterator pos) { - return const_cast(pos); + return ::tsl::Remove_Const::template remove(pos); } template @@ -673,18 +727,18 @@ class sparse_array { private: template - static void construct_value(allocator_type &alloc, value_type *value, + static void construct_value(allocator_type &alloc, pointer value, Args &&... value_args) { std::allocator_traits::construct( - alloc, value, std::forward(value_args)...); + alloc, detail_sparse_hash::to_address(value), std::forward(value_args)...); } - static void destroy_value(allocator_type &alloc, value_type *value) noexcept { - std::allocator_traits::destroy(alloc, value); + static void destroy_value(allocator_type &alloc, pointer value) noexcept { + std::allocator_traits::destroy(alloc, detail_sparse_hash::to_address(value)); } static void destroy_and_deallocate_values( - allocator_type &alloc, value_type *values, size_type nb_values, + allocator_type &alloc, pointer values, size_type nb_values, size_type capacity_values) noexcept { for (size_type i = 0; i < nb_values; i++) { destroy_value(alloc, values + i); @@ -808,7 +862,7 @@ class sparse_array { size_type new_capacity, Args &&... value_args) { tsl_sh_assert(new_capacity > m_nb_elements); - value_type *new_values = alloc.allocate(new_capacity); + pointer new_values = alloc.allocate(new_capacity); // Allocate should throw if there is a failure tsl_sh_assert(new_values != nullptr); @@ -944,7 +998,7 @@ class sparse_array { } private: - value_type *m_values; + pointer m_values; bitmap_type m_bitmap_vals; bitmap_type m_bitmap_deleted_vals; @@ -1009,15 +1063,15 @@ class sparse_hash : private Allocator, using key_type = typename KeySelect::key_type; using value_type = ValueType; - using size_type = std::size_t; - using difference_type = std::ptrdiff_t; using hasher = Hash; using key_equal = KeyEqual; using allocator_type = Allocator; using reference = value_type &; using const_reference = const value_type &; - using pointer = value_type *; - using const_pointer = const value_type *; + using size_type = typename std::allocator_traits::size_type; + using pointer = typename std::allocator_traits::pointer; + using const_pointer = typename std::allocator_traits::const_pointer; + using difference_type = typename std::allocator_traits::difference_type; using iterator = sparse_iterator; using const_iterator = sparse_iterator; @@ -1064,10 +1118,10 @@ class sparse_hash : private Allocator, public: using iterator_category = std::forward_iterator_tag; - using value_type = const typename sparse_hash::value_type; + using value_type = const sparse_hash::value_type; using difference_type = std::ptrdiff_t; using reference = value_type &; - using pointer = value_type *; + using pointer = sparse_hash::const_pointer; sparse_iterator() noexcept {} @@ -1103,24 +1157,26 @@ class sparse_hash : private Allocator, reference operator*() const { return *m_sparse_array_it; } + //with fancy pointers addressof might be problematic. pointer operator->() const { return std::addressof(*m_sparse_array_it); } sparse_iterator &operator++() { tsl_sh_assert(m_sparse_array_it != nullptr); ++m_sparse_array_it; - if (m_sparse_array_it == m_sparse_buckets_it->end()) { + //vector iterator with fancy pointers have a problem with -> + if (m_sparse_array_it == (*m_sparse_buckets_it).end()) { do { - if (m_sparse_buckets_it->last()) { + if ((*m_sparse_buckets_it).last()) { ++m_sparse_buckets_it; m_sparse_array_it = nullptr; return *this; } ++m_sparse_buckets_it; - } while (m_sparse_buckets_it->empty()); + } while ((*m_sparse_buckets_it).empty()); - m_sparse_array_it = m_sparse_buckets_it->begin(); + m_sparse_array_it = (*m_sparse_buckets_it).begin(); } return *this; @@ -1343,12 +1399,14 @@ class sparse_hash : private Allocator, */ iterator begin() noexcept { auto begin = m_sparse_buckets_data.begin(); - while (begin != m_sparse_buckets_data.end() && begin->empty()) { + //vector iterator with fancy pointers have a problem with -> + while (begin != m_sparse_buckets_data.end() && (*begin).empty()) { ++begin; } + //vector iterator with fancy pointers have a problem with -> return iterator(begin, (begin != m_sparse_buckets_data.end()) - ? begin->begin() + ? (*begin).begin() : nullptr); } @@ -1356,12 +1414,13 @@ class sparse_hash : private Allocator, const_iterator cbegin() const noexcept { auto begin = m_sparse_buckets_data.cbegin(); - while (begin != m_sparse_buckets_data.cend() && begin->empty()) { + //vector iterator with fancy pointers have a problem with -> + while (begin != m_sparse_buckets_data.cend() && (*begin).empty()) { ++begin; } return const_iterator(begin, (begin != m_sparse_buckets_data.cend()) - ? begin->cbegin() + ? (*begin).cbegin() : nullptr); } @@ -1488,23 +1547,24 @@ class sparse_hash : private Allocator, */ iterator erase(iterator pos) { tsl_sh_assert(pos != end() && m_nb_elements > 0); + //vector iterator with fancy pointers have a problem with -> auto it_sparse_array_next = - pos.m_sparse_buckets_it->erase(*this, pos.m_sparse_array_it); + (*pos.m_sparse_buckets_it).erase(*this, pos.m_sparse_array_it); m_nb_elements--; m_nb_deleted_buckets++; - if (it_sparse_array_next == pos.m_sparse_buckets_it->end()) { + if (it_sparse_array_next == (*pos.m_sparse_buckets_it).end()) { auto it_sparse_buckets_next = pos.m_sparse_buckets_it; do { ++it_sparse_buckets_next; } while (it_sparse_buckets_next != m_sparse_buckets_data.end() && - it_sparse_buckets_next->empty()); + (*it_sparse_buckets_next).empty()); if (it_sparse_buckets_next == m_sparse_buckets_data.end()) { return end(); } else { return iterator(it_sparse_buckets_next, - it_sparse_buckets_next->begin()); + (*it_sparse_buckets_next).begin()); } } else { return iterator(pos.m_sparse_buckets_it, it_sparse_array_next); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 26cea96..cb1047f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -2,12 +2,17 @@ cmake_minimum_required(VERSION 3.8) project(tsl_sparse_map_tests) -add_executable(tsl_sparse_map_tests "main.cpp" - "custom_allocator_tests.cpp" - "policy_tests.cpp" - "popcount_tests.cpp" - "sparse_map_tests.cpp" - "sparse_set_tests.cpp") +add_executable(tsl_sparse_map_tests "main.cpp" + "custom_allocator_tests.cpp" + "policy_tests.cpp" + "popcount_tests.cpp" + "sparse_map_tests.cpp" + "sparse_set_tests.cpp" + "fancy_pointer/sparse_array_tests.cpp" + "fancy_pointer/sparse_hash_set_tests.cpp" + "fancy_pointer/CustomAllocator.h" + "fancy_pointer/sparse_hash_map_tests.cpp" + "../include/tsl/boost_offset_pointer.h") target_compile_features(tsl_sparse_map_tests PRIVATE cxx_std_11) @@ -19,8 +24,8 @@ endif() # Boost::unit_test_framework find_package(Boost 1.54.0 REQUIRED COMPONENTS unit_test_framework) -target_link_libraries(tsl_sparse_map_tests PRIVATE Boost::unit_test_framework) +target_link_libraries(tsl_sparse_map_tests PRIVATE Boost::unit_test_framework) # tsl::sparse_map add_subdirectory(../ ${CMAKE_CURRENT_BINARY_DIR}/tsl) -target_link_libraries(tsl_sparse_map_tests PRIVATE tsl::sparse_map) +target_link_libraries(tsl_sparse_map_tests PRIVATE tsl::sparse_map) \ No newline at end of file diff --git a/tests/fancy_pointer/CustomAllocator.h b/tests/fancy_pointer/CustomAllocator.h new file mode 100644 index 0000000..7340fef --- /dev/null +++ b/tests/fancy_pointer/CustomAllocator.h @@ -0,0 +1,46 @@ +/** @file + * @bief Home of a custom allocator for testing with fancy pointers. + */ + +#ifndef TSL_SPARSE_MAP_TESTS_CUSTOMALLOCATOR_H +#define TSL_SPARSE_MAP_TESTS_CUSTOMALLOCATOR_H + +#include + +/** A custom allocator that simply wraps all pointers into boost offset ptr. +* It is used to check whether the implementation can handle Allocators using fancy pointers. +* @tparam T Typical Allocator parameter. +*/ +template +struct OffsetAllocator { + using value_type = T; + template using offset_ptr = boost::interprocess::offset_ptr

; + using pointer = offset_ptr; + using const_pointer = offset_ptr; + using void_pointer = offset_ptr; + using const_void_pointer = offset_ptr; + using difference_type = typename offset_ptr::difference_type; + + OffsetAllocator() noexcept = default; + OffsetAllocator(OffsetAllocator const &) noexcept = default; + OffsetAllocator(OffsetAllocator &&) noexcept = default; + OffsetAllocator &operator=(OffsetAllocator const &) noexcept = default; + OffsetAllocator &operator=(OffsetAllocator &&) noexcept = default; + template + OffsetAllocator(OffsetAllocator) noexcept {} + + pointer allocate(std::size_t n) { + return pointer(static_cast(::operator new(n*sizeof(T)))); + } + void deallocate(pointer p, std::size_t) noexcept { + ::operator delete(p.get()); + } + friend bool operator==(OffsetAllocator const &, OffsetAllocator const &) noexcept { + return true; + } + friend bool operator!=(OffsetAllocator const &l, OffsetAllocator const &r) noexcept { + return !(l == r); + } +}; + +#endif //TSL_SPARSE_MAP_TESTS_CUSTOMALLOCATOR_H \ No newline at end of file diff --git a/tests/fancy_pointer/sparse_array_tests.cpp b/tests/fancy_pointer/sparse_array_tests.cpp new file mode 100644 index 0000000..8f992b1 --- /dev/null +++ b/tests/fancy_pointer/sparse_array_tests.cpp @@ -0,0 +1,135 @@ +/** @file + * @brief Checks for fancy pointer support in the sparse_array implementation. + */ + +#include +#include +#include "CustomAllocator.h" + +// Globals +constexpr auto MAX_INDEX = 32; //BITMAP_NB_BITS + +/* Tests are formulated via templates to reduce code duplication. + * The template parameter contains the Allocator type and the shorthand "Array" for the sparse_array + * (with all template parameter already inserted). + */ + +template +void compilation() { + typename T::Array test; +} + +template +void construction() { + typename T::Allocator a; + typename T::Array test(MAX_INDEX, a); + test.clear(a); //needed because destructor asserts +} + +namespace details { + template + typename T::Array generate_test_array(typename T::Allocator &a) { + typename T::Array arr(MAX_INDEX, a); + for (std::size_t i = 0; i < MAX_INDEX; ++i) { + arr.set(a, i, i); + } + return arr; + } + + template + std::vector generate_check_for_test_array() { + std::vector check(MAX_INDEX); + for (std::size_t i = 0; i < MAX_INDEX; ++i) { + check[i] = i; + } + return check; + } +} + +template +void set() { + typename T::Allocator a; + auto test = details::generate_test_array(a); + auto check = details::generate_check_for_test_array(); + BOOST_TEST_REQUIRE(std::equal(test.begin(), test.end(), check.begin()), + "'set' did not create the correct order of items"); + test.clear(a); //needed because destructor asserts +} + +template +void copy_construction() { + typename T::Allocator a; + //needs to be its own line, otherwise the move-construction would take place + auto test = details::generate_test_array(a); + typename T::Array copy(test, a); + auto check = details::generate_check_for_test_array(); + BOOST_TEST_REQUIRE(std::equal(copy.begin(), copy.end(), check.begin()), + "'copy' changed the order of the items"); + test.clear(a); + copy.clear(a); +} + +template +void move_construction() { + typename T::Allocator a; + //two lines needed. Otherwise move/copy elision + auto moved_from = details::generate_test_array(a); + typename T::Array moved_to(std::move(moved_from)); + auto check = details::generate_check_for_test_array(); + BOOST_TEST_REQUIRE(std::equal(moved_to.begin(), moved_to.end(), check.begin()), + "'move' changed the order of the items"); + moved_to.clear(a); +} + +template +void const_iterator() { + typename T::Allocator a; + auto test = details::generate_test_array(a); + auto const_iter = test.cbegin(); + BOOST_TEST_REQUIRE((std::is_same::value), + "const iterator has the wrong type"); + test.clear(a); +} + + +/* + * This are the types you can give the tests as template parameters. + */ +template +struct STD { + using Allocator = std::allocator; + using Array = tsl::detail_sparse_hash::sparse_array, Sparsity>; + using Const_Iterator = T const*; +}; + +template +struct CUSTOM { + using Allocator = OffsetAllocator; + using Array = tsl::detail_sparse_hash::sparse_array, Sparsity>; + using Const_Iterator = boost::interprocess::offset_ptr; +}; + + + +/* The instantiation of the tests. + * I don't use the boost template test cases because with this I can set the title of every test case myself. + */ +BOOST_AUTO_TEST_SUITE(fancy_pointers) +BOOST_AUTO_TEST_SUITE(sparse_array_tests) + +BOOST_AUTO_TEST_CASE(std_alloc_compile) {compilation>();} +BOOST_AUTO_TEST_CASE(std_alloc_construction) {construction>();} +BOOST_AUTO_TEST_CASE(std_alloc_set) {set>();} +BOOST_AUTO_TEST_CASE(std_alloc_copy_construction) {copy_construction>();} +BOOST_AUTO_TEST_CASE(std_alloc_move_construction) {move_construction>();} +BOOST_AUTO_TEST_CASE(std_const_iterator) {const_iterator>();} + +BOOST_AUTO_TEST_CASE(custom_alloc_compile) {compilation>();} +BOOST_AUTO_TEST_CASE(custom_alloc_construction) {construction>();} +BOOST_AUTO_TEST_CASE(custom_alloc_set) {set>();} +BOOST_AUTO_TEST_CASE(custom_alloc_copy_construction) {copy_construction>();} +BOOST_AUTO_TEST_CASE(custom_alloc_move_construction) {move_construction>();} +BOOST_AUTO_TEST_CASE(custom_const_iterator) {const_iterator>();} + +BOOST_AUTO_TEST_SUITE_END() +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/fancy_pointer/sparse_hash_map_tests.cpp b/tests/fancy_pointer/sparse_hash_map_tests.cpp new file mode 100644 index 0000000..f84de72 --- /dev/null +++ b/tests/fancy_pointer/sparse_hash_map_tests.cpp @@ -0,0 +1,176 @@ +/** @file + * @brief Checks for fancy pointer support in the sparse_hash implementation for pair values (maps). + */ + +#include +#include +#include +#include +#include "CustomAllocator.h" + +/* Tests are analogous to the tests in sparse_array_tests.cpp. + * The template parameter now also holds the value_type. + */ +namespace details { + template + struct KeySelect { + using key_type = Key; + const key_type &operator()(std::pair const &key_value) const noexcept { + return key_value.first; + } + key_type &operator()(std::pair &key_value) noexcept { + return key_value.first; + } + }; + + template + struct ValueSelect { + using value_type = T; + const value_type &operator()(std::pair const &key_value) const noexcept { + return key_value.second; + } + value_type &operator()(std::pair &key_value) noexcept { + return key_value.second; + } + }; + + template + using sparse_map= tsl::detail_sparse_hash::sparse_hash< + std::pair, KeySelect, ValueSelect, std::hash, std::equal_to, Alloc, + tsl::sh::power_of_two_growth_policy<2>, + tsl::sh::exception_safety::basic, + tsl::sh::sparsity::medium, + tsl::sh::probing::quadratic>; + + template + typename T::Map default_construct_map() { + using Key = typename T::key_type; + return typename T::Map(T::Map::DEFAULT_INIT_BUCKET_COUNT, + std::hash(), + std::equal_to(), + typename T::Allocator(), + T::Map::DEFAULT_MAX_LOAD_FACTOR); + } + + /** Checks if all values of the map are in the initializer_list and than if the lengths are equal. + * So basically Map \subset l and |Map| == |l|. + * Needs 'map.contains(.)' and 'map.at(.)' to work correctly. + */ + template + bool is_equal(Map const& map, std::initializer_list l) { + auto check_in_map = [&map](typename Map::value_type p) { + return map.contains(p.first) && map.at(p.first) == p.second; + }; + return std::all_of(l.begin(), l.end(), check_in_map) && map.size() == l.size(); + } + template + bool is_equal(Map1 const& custom_map, Map2 const &normal_map) { + auto check_in_map = [&custom_map](typename Map2::value_type const& p) { + return custom_map.count(p.first) == 1 && custom_map.at(p.first) == p.second; + }; + return std::all_of(normal_map.begin(), normal_map.end(), check_in_map) && custom_map.size() == normal_map.size(); + } +} + +template +void construction() { + auto map = details::default_construct_map(); +} + +template +void insert(std::initializer_list l) { + auto map = details::default_construct_map(); + for (auto dataPair : l) map.insert(dataPair); + BOOST_TEST_REQUIRE(details::is_equal(map, l), "'insert' did not create exactly the values needed"); +} + +template +void iterator_insert(std::initializer_list l) { + auto map = details::default_construct_map(); + map.insert(l.begin(), l.end()); + BOOST_TEST_REQUIRE(details::is_equal(map, l), "'insert' with iterators did not create exactly the values needed"); +} + +template +void iterator_access(typename T::value_type single_value) { + auto map = details::default_construct_map(); + map.insert(single_value); + BOOST_TEST_REQUIRE( (*(map.begin()) == single_value), "iterator cannot access single value"); +} + +template +void iterator_access_multi(std::initializer_list l) { + auto map = details::default_construct_map(); + map.insert(l.begin(), l.end()); + std::vector l_sorted = l; + std::vector map_sorted(map.begin(), map.end()); + std::sort(l_sorted.begin(), l_sorted.end()); + std::sort(map_sorted.begin(), map_sorted.end()); + BOOST_TEST_REQUIRE(std::equal(l_sorted.begin(), l_sorted.end(), + map_sorted.begin()), + "iterating over the map didn't work"); +} + +template +void value(std::initializer_list l, typename T::value_type to_change) { + auto map = details::default_construct_map(); + map.insert(l.begin(), l.end()); + map[to_change.first] = to_change.second; + + std::unordered_map check(l.begin(), l.end()); + check[to_change.first] = to_change.second; + + BOOST_TEST_REQUIRE(details::is_equal(map, check), "changing a single value didn't work"); +} + + +template +struct STD { + using key_type = Key; + using value_type = std::pair; + using Allocator = std::allocator; + using Map = details::sparse_map; +}; + +template +struct CUSTOM { + using key_type = Key; + using value_type = std::pair; + using Allocator = OffsetAllocator; + using Map = details::sparse_map; +}; + + +BOOST_AUTO_TEST_SUITE(fancy_pointers) +BOOST_AUTO_TEST_SUITE(sparse_hash_map_tests) + +BOOST_AUTO_TEST_CASE(std_alloc_compiles) {construction>();} +BOOST_AUTO_TEST_CASE(std_alloc_insert) {insert>({{1,2},{3,4},{5,6}});} +BOOST_AUTO_TEST_CASE(std_alloc_iterator_insert) {insert>({{1,2},{3,4},{5,6}});} +BOOST_AUTO_TEST_CASE(std_alloc_iterator_access) {iterator_access>({1,42});} +BOOST_AUTO_TEST_CASE(std_alloc_iterator_access_multi) {iterator_access_multi>({{1,2},{3,4},{5,6}});} +BOOST_AUTO_TEST_CASE(std_alloc_value) {value>({{1,2},{3,4},{5,6}}, {1, 42});} + +BOOST_AUTO_TEST_CASE(custom_alloc_compiles) {construction>();} +BOOST_AUTO_TEST_CASE(custom_alloc_insert) {insert>({{1,2},{3,4},{5,6}});} +BOOST_AUTO_TEST_CASE(custom_alloc_iterator_insert) {insert>({{1,2},{3,4},{5,6}});} +BOOST_AUTO_TEST_CASE(custom_alloc_iterator_access) {iterator_access>({1,42});} +BOOST_AUTO_TEST_CASE(custom_alloc_iterator_access_multi) {iterator_access_multi>({{1,2},{3,4},{5,6}});} +BOOST_AUTO_TEST_CASE(custom_alloc_value) {value>({{1,2},{3,4},{5,6}}, {1, 42});} + +BOOST_AUTO_TEST_CASE(full_map) { + tsl::sparse_map, std::equal_to, OffsetAllocator>> map; + std::vector> data = { + {0,1},{2,3},{4,5},{6,7},{8,9} + }; + map.insert(data.begin(), data.end()); + auto check = [&map](std::pair p) { + if (!map.contains(p.first)) return false; + return map.at(p.first) == p.second; + }; + BOOST_TEST_REQUIRE(data.size() == map.size(), "size did not match"); + BOOST_TEST_REQUIRE(std::all_of(data.begin(), data.end(), check), "map did not contain all values"); +} + +BOOST_AUTO_TEST_SUITE_END() +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/fancy_pointer/sparse_hash_set_tests.cpp b/tests/fancy_pointer/sparse_hash_set_tests.cpp new file mode 100644 index 0000000..4d42353 --- /dev/null +++ b/tests/fancy_pointer/sparse_hash_set_tests.cpp @@ -0,0 +1,185 @@ +/** @file + * @brief Checks for fancy pointer support in the sparse_hash implementation for single values (sets). + */ + +#include +#include +#include +#include +#include "CustomAllocator.h" + +/* Tests are analogous to the tests in sparse_array_tests.cpp. + * The template parameter now also holds the value_type. + */ +namespace details { + template + struct KeySelect { + using key_type = Key; + const key_type &operator()(Key const &key) const noexcept { return key; } + key_type &operator()(Key &key) noexcept { return key; } + }; + + template + using sparse_set = tsl::detail_sparse_hash::sparse_hash< + T, KeySelect, void, std::hash, std::equal_to, Alloc, + tsl::sh::power_of_two_growth_policy<2>, + tsl::sh::exception_safety::basic, + tsl::sh::sparsity::medium, + tsl::sh::probing::quadratic>; + + template + typename T::Set default_construct_set() { + using Type = typename T::value_type; + return typename T::Set(T::Set::DEFAULT_INIT_BUCKET_COUNT, std::hash(), std::equal_to(), + typename T::Allocator(), T::Set::DEFAULT_MAX_LOAD_FACTOR); + } + + /** checks if all values of the set are in the initializer_list and than if the lengths are equal. + * So basically Set \subset l and |Set| == |l|. + * Needs 'set.contains(.)' to work correctly. + */ + template + bool is_equal(Set const& set, std::initializer_list l) { + return std::all_of(l.begin(), l.end(), [&set](typename Set::value_type i){return set.contains(i);}) + and set.size() == l.size(); + } +} + +template +void construction() { + auto set = details::default_construct_set(); +} + +template +void insert(std::initializer_list l) { + auto set = details::default_construct_set(); + for (auto const& i: l) set.insert(i); + BOOST_TEST_REQUIRE(details::is_equal(set, l), "'insert' did not create exactly the values needed"); +} + +template +void iterator_insert(std::initializer_list l) { + auto set = details::default_construct_set(); + set.insert(l.begin(), l.end()); + BOOST_TEST_REQUIRE(details::is_equal(set, l), "'insert' with iterators did not create exactly the values needed"); +} + +template +void iterator_access(typename T::value_type single_value) { + auto set = details::default_construct_set(); + set.insert(single_value); + BOOST_TEST_REQUIRE(*(set.begin()) == single_value, "iterator cannot access single value"); +} + +template +void iterator_access_multi(std::initializer_list l) { + auto set = details::default_construct_set(); + set.insert(l.begin(), l.end()); + std::vector l_sorted = l; + std::vector set_sorted(set.begin(), set.end()); + std::sort(l_sorted.begin(), l_sorted.end()); + std::sort(set_sorted.begin(), set_sorted.end()); + BOOST_TEST_REQUIRE(std::equal(l_sorted.begin(), l_sorted.end(), + set_sorted.begin()), + "iterating over the set didn't work"); +} + + +template +void const_iterator_access_multi(std::initializer_list l) { + auto set = details::default_construct_set(); + set.insert(l.begin(), l.end()); + std::vector l_sorted = l; + std::vector set_sorted(set.cbegin(), set.cend()); + std::sort(l_sorted.begin(), l_sorted.end()); + std::sort(set_sorted.begin(), set_sorted.end()); + BOOST_TEST_REQUIRE(std::equal(l_sorted.begin(), l_sorted.end(), + set_sorted.begin()), + "const iterating over the set didn't work"); +} + +template +void find(std::initializer_list l, typename T::value_type search_value, bool is_in_list) { + auto set = details::default_construct_set(); + set.insert(l.begin(), l.end()); + auto iter = set.find(search_value); + bool found = iter != set.end(); + BOOST_TEST_REQUIRE((found == is_in_list), "find did not work as expected"); +} + +template +void erase(std::initializer_list l, typename T::value_type extra_value) { + auto set = details::default_construct_set(); + set.insert(extra_value); + set.insert(l.begin(), l.end()); + // force non-const iterator + auto iter = set.begin(); + for(; *iter != extra_value; ++iter); + set.erase(iter); + BOOST_TEST_REQUIRE(details::is_equal(set, l), "erase did not work as expected"); +} + +template +void erase_with_const_iter(std::initializer_list l, typename T::value_type extra_value) { + auto set = details::default_construct_set(); + set.insert(extra_value); + set.insert(l.begin(), l.end()); + //force const iterator + auto iter = set.cbegin(); + for(; *iter != extra_value; ++iter); + set.erase(iter); + BOOST_TEST_REQUIRE(details::is_equal(set, l), "erase did not work as expected"); +} + + +template +struct STD { + using value_type = T; + using Allocator = std::allocator; + using Set = details::sparse_set; +}; + +template +struct CUSTOM { + using value_type = T; + using Allocator = OffsetAllocator; + using Set = details::sparse_set; +}; + + +BOOST_AUTO_TEST_SUITE(fancy_pointers) +BOOST_AUTO_TEST_SUITE(sparse_hash_set_tests) + +BOOST_AUTO_TEST_CASE(std_alloc_compiles) {construction>();} +BOOST_AUTO_TEST_CASE(std_alloc_insert) {insert>({1,2,3,4});} +BOOST_AUTO_TEST_CASE(std_alloc_iterator_insert) {iterator_insert>({1,2,3,4});} +BOOST_AUTO_TEST_CASE(std_alloc_iterator_access) {iterator_access>(42);} +BOOST_AUTO_TEST_CASE(std_alloc_iterator_access_multi) {iterator_access_multi>({1,2,3,4});} +BOOST_AUTO_TEST_CASE(std_alloc_const_iterator_access_multi) {const_iterator_access_multi>({1,2,3,4});} +BOOST_AUTO_TEST_CASE(std_find_true) {find>({1,2,3,4}, 4, true);} +BOOST_AUTO_TEST_CASE(std_find_false) {find>({1,2,3,4}, 5, false);} +BOOST_AUTO_TEST_CASE(std_erase) {erase>({1,2,3,4}, 5);} +BOOST_AUTO_TEST_CASE(std_erase_with_const_iter) {erase_with_const_iter>({1,2,3,4}, 5);} + +BOOST_AUTO_TEST_CASE(custom_alloc_compiles) {construction>();} +BOOST_AUTO_TEST_CASE(custom_alloc_insert) {insert>({1,2,3,4});} +BOOST_AUTO_TEST_CASE(custom_alloc_iterator_insert) {iterator_insert>({1,2,3,4});} +BOOST_AUTO_TEST_CASE(custom_alloc_iterator_access) {iterator_access>(42);} +BOOST_AUTO_TEST_CASE(custom_alloc_iterator_access_multi) {iterator_access_multi>({1,2,3,4});} +BOOST_AUTO_TEST_CASE(custom_alloc_const_iterator_access_multi) {const_iterator_access_multi>({1,2,3,4});} +BOOST_AUTO_TEST_CASE(custom_find_true) {find>({1,2,3,4}, 4, true);} +BOOST_AUTO_TEST_CASE(custom_find_false) {find>({1,2,3,4}, 5, false);} +BOOST_AUTO_TEST_CASE(custom_erase) {erase>({1,2,3,4}, 5);} +BOOST_AUTO_TEST_CASE(custom_erase_with_const_iter) {erase_with_const_iter>({1,2,3,4}, 5);} + +BOOST_AUTO_TEST_CASE(full_set) { + tsl::sparse_set, std::equal_to, OffsetAllocator> set; + std::vector data = {1,2,3,4,5,6,7,8,9}; + set.insert(data.begin(), data.end()); + auto check = [&set](int d) {return set.contains(d);}; + BOOST_TEST_REQUIRE(data.size() == set.size(), "size did not match"); + BOOST_TEST_REQUIRE(std::all_of(data.begin(), data.end(), check), "Set did not contain all values"); +} + +BOOST_AUTO_TEST_SUITE_END() +BOOST_AUTO_TEST_SUITE_END()