diff --git a/doc/unordered/unordered_flat_map.adoc b/doc/unordered/unordered_flat_map.adoc index a7f4c7fe..27af35cd 100644 --- a/doc/unordered/unordered_flat_map.adoc +++ b/doc/unordered/unordered_flat_map.adoc @@ -98,9 +98,8 @@ namespace boost { xref:#unordered_flat_map_destructor[~unordered_flat_map](); unordered_flat_map& xref:#unordered_flat_map_copy_assignment[operator++=++](const unordered_flat_map& other); unordered_flat_map& xref:#unordered_flat_map_move_assignment[operator++=++](unordered_flat_map&& other) - noexcept(boost::allocator_traits::is_always_equal::value && - boost::is_nothrow_move_assignable_v && - boost::is_nothrow_move_assignable_v); + noexcept(boost::allocator_traits::is_always_equal::value || + boost::allocator_traits::propagate_on_container_move_assignment::value); unordered_flat_map& xref:#unordered_flat_map_initializer_list_assignment[operator++=++](std::initializer_list); allocator_type xref:#unordered_flat_map_get_allocator[get_allocator]() const noexcept; @@ -154,9 +153,8 @@ namespace boost { template size_type xref:#unordered_flat_map_transparent_erase_by_key[erase](K&& k); iterator xref:#unordered_flat_map_erase_range[erase](const_iterator first, const_iterator last); void xref:#unordered_flat_map_swap[swap](unordered_flat_map& other) - noexcept(boost::allocator_traits::is_always_equal::value && - boost::is_nothrow_swappable_v && - boost::is_nothrow_swappable_v); + noexcept(boost::allocator_traits::is_always_equal::value || + boost::allocator_traits::propagate_on_container_swap::value); void xref:#unordered_flat_map_clear[clear]() noexcept; template @@ -606,11 +604,10 @@ Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInse ==== Move Assignment ```c++ unordered_flat_map& operator=(unordered_flat_map&& other) - noexcept(boost::allocator_traits::is_always_equal::value && - boost::is_nothrow_move_assignable_v && - boost::is_nothrow_move_assignable_v); + noexcept(boost::allocator_traits::is_always_equal::value || + boost::allocator_traits::propagate_on_container_move_assignment::value); ``` -The move assignment operator. Destroys previously existing elements, move-assigns the hash function and predicate from `other`, +The move assignment operator. Destroys previously existing elements, swaps the hash function and predicate from `other`, and move-assigns the allocator from `other` if `Alloc::propagate_on_container_move_assignment` exists and `Alloc::propagate_on_container_move_assignment::value` is `true`. If at this point the allocator is equal to `other.get_allocator()`, the internal bucket array of `other` is transferred directly to the new container; otherwise, inserts move-constructed copies of the elements of `other`. @@ -1043,9 +1040,8 @@ Throws:;; Nothing in this implementation (neither the `hasher` nor the `key_equa ==== swap ```c++ void swap(unordered_flat_map& other) - noexcept(boost::allocator_traits::is_always_equal::value && - boost::is_nothrow_swappable_v && - boost::is_nothrow_swappable_v); + noexcept(boost::allocator_traits::is_always_equal::value || + boost::allocator_traits::propagate_on_container_swap::value); ``` Swaps the contents of the container with the parameter. diff --git a/doc/unordered/unordered_flat_set.adoc b/doc/unordered/unordered_flat_set.adoc index 33fe5dfa..cd22733a 100644 --- a/doc/unordered/unordered_flat_set.adoc +++ b/doc/unordered/unordered_flat_set.adoc @@ -93,9 +93,8 @@ namespace boost { xref:#unordered_flat_set_destructor[~unordered_flat_set](); unordered_flat_set& xref:#unordered_flat_set_copy_assignment[operator++=++](const unordered_flat_set& other); unordered_flat_set& xref:#unordered_flat_set_move_assignment[operator++=++](unordered_flat_set&& other) - noexcept(boost::allocator_traits::is_always_equal::value && - boost::is_nothrow_move_assignable_v && - boost::is_nothrow_move_assignable_v); + noexcept(boost::allocator_traits::is_always_equal::value || + boost::allocator_traits::propagate_on_container_move_assignment::value); unordered_flat_set& xref:#unordered_flat_set_initializer_list_assignment[operator++=++](std::initializer_list); allocator_type xref:#unordered_flat_set_get_allocator[get_allocator]() const noexcept; @@ -128,9 +127,8 @@ namespace boost { template size_type xref:#unordered_flat_set_transparent_erase_by_key[erase](K&& k); iterator xref:#unordered_flat_set_erase_range[erase](const_iterator first, const_iterator last); void xref:#unordered_flat_set_swap[swap](unordered_flat_set& other) - noexcept(boost::allocator_traits::is_always_equal::value && - boost::is_nothrow_swappable_v && - boost::is_nothrow_swappable_v); + noexcept(boost::allocator_traits::is_always_equal::value || + boost::allocator_traits::propagate_on_container_swap::value); void xref:#unordered_flat_set_clear[clear]() noexcept; template @@ -565,11 +563,10 @@ Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInse ==== Move Assignment ```c++ unordered_flat_set& operator=(unordered_flat_set&& other) - noexcept(boost::allocator_traits::is_always_equal::value && - boost::is_nothrow_move_assignable_v && - boost::is_nothrow_move_assignable_v); + noexcept(boost::allocator_traits::is_always_equal::value || + boost::allocator_traits::propagate_on_container_move_assignment::value); ``` -The move assignment operator. Destroys previously existing elements, move-assigns the hash function and predicate from `other`, +The move assignment operator. Destroys previously existing elements, swaps the hash function and predicate from `other`, and move-assigns the allocator from `other` if `Alloc::propagate_on_container_move_assignment` exists and `Alloc::propagate_on_container_move_assignment::value` is `true`. If at this point the allocator is equal to `other.get_allocator()`, the internal bucket array of `other` is transferred directly to the new container; otherwise, inserts move-constructed copies of the elements of `other`. @@ -863,9 +860,8 @@ Throws:;; Nothing in this implementation (neither the `hasher` nor the `key_equa ==== swap ```c++ void swap(unordered_flat_set& other) - noexcept(boost::allocator_traits::is_always_equal::value && - boost::is_nothrow_swappable_v && - boost::is_nothrow_swappable_v); + noexcept(boost::allocator_traits::is_always_equal::value || + boost::allocator_traits::propagate_on_container_swap::value); ``` Swaps the contents of the container with the parameter. diff --git a/include/boost/unordered/detail/foa.hpp b/include/boost/unordered/detail/foa.hpp index 2441b400..e861c87e 100644 --- a/include/boost/unordered/detail/foa.hpp +++ b/include/boost/unordered/detail/foa.hpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -69,6 +70,12 @@ }while(0) #endif +#define BOOST_UNORDERED_STATIC_ASSERT_HASH_PRED(Hash, Pred) \ + static_assert(boost::is_nothrow_swappable::value, \ + "Template parameter Hash is required to be nothrow Swappable."); \ + static_assert(boost::is_nothrow_swappable::value, \ + "Template parameter Pred is required to be nothrow Swappable"); + namespace boost{ namespace unordered{ namespace detail{ @@ -1062,6 +1069,47 @@ inline void prefetch(const void* p) struct try_emplace_args_t{}; +template +struct is_std_allocator:std::false_type{}; + +template +struct is_std_allocator>:std::true_type{}; + +/* std::allocator::construct marked as deprecated */ +#if defined(_LIBCPP_SUPPRESS_DEPRECATED_PUSH) +_LIBCPP_SUPPRESS_DEPRECATED_PUSH +#elif defined(_STL_DISABLE_DEPRECATED_WARNING) +_STL_DISABLE_DEPRECATED_WARNING +#elif defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4996) +#endif + +template +struct alloc_has_construct +{ +private: + template + static decltype( + std::declval().construct( + std::declval(),std::declval()...), + std::true_type{} + ) check(int); + + template static std::false_type check(...); + +public: + static constexpr bool value=decltype(check(0))::value; +}; + +#if defined(_LIBCPP_SUPPRESS_DEPRECATED_POP) +_LIBCPP_SUPPRESS_DEPRECATED_POP +#elif defined(_STL_RESTORE_DEPRECATED_WARNING) +_STL_RESTORE_DEPRECATED_WARNING +#elif defined(_MSC_VER) +#pragma warning(pop) +#endif + #if defined(BOOST_GCC) /* GCC's -Wshadow triggers at scenarios like this: * @@ -1255,13 +1303,28 @@ public: table& operator=(const table& x) { + BOOST_UNORDERED_STATIC_ASSERT_HASH_PRED(Hash, Pred) + static constexpr auto pocca= alloc_traits::propagate_on_container_copy_assignment::value; if(this!=std::addressof(x)){ - clear(); - h()=x.h(); - pred()=x.pred(); + // if copy construction here winds up throwing, the container is still + // left intact so we perform these operations first + hasher tmp_h=x.h(); + key_equal tmp_p=x.pred(); + + // already noexcept, clear() before we swap the Hash, Pred just in case + // the clear() impl relies on them at some point in the future + clear(); + + // because we've asserted at compile-time that Hash and Pred are nothrow + // swappable, we can safely mutate our source container and maintain + // consistency between the Hash, Pred compatibility + using std::swap; + swap(h(),tmp_h); + swap(pred(),tmp_p); + if_constexpr([&,this]{ if(al()!=x.al())reserve(0); copy_assign_if(al(),x.al()); @@ -1280,19 +1343,32 @@ public: table& operator=(table&& x) noexcept( - alloc_traits::is_always_equal::value&& - std::is_nothrow_move_assignable::value&& - std::is_nothrow_move_assignable::value) + alloc_traits::propagate_on_container_move_assignment::value|| + alloc_traits::is_always_equal::value) { + BOOST_UNORDERED_STATIC_ASSERT_HASH_PRED(Hash, Pred) + static constexpr auto pocma= alloc_traits::propagate_on_container_move_assignment::value; if(this!=std::addressof(x)){ + /* Given ambiguity in implementation strategies briefly discussed here: + * https://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#2227 + * + * we opt into requiring nothrow swappability and eschew the move + * operations associated with Hash, Pred. + * + * To this end, we ensure that the user never has to consider the + * moved-from state of their Hash, Pred objects + */ + + using std::swap; + clear(); - h()=std::move(x.h()); - pred()=std::move(x.pred()); + swap(h(),x.h()); + swap(pred(),x.pred()); + if(pocma||al()==x.al()){ - using std::swap; reserve(0); move_assign_if(al(),x.al()); swap(size_,x.size_); @@ -1407,16 +1483,15 @@ public: void swap(table& x) noexcept( - alloc_traits::is_always_equal::value&& - boost::is_nothrow_swappable::value&& - boost::is_nothrow_swappable::value) + alloc_traits::propagate_on_container_swap::value|| + alloc_traits::is_always_equal::value) { + BOOST_UNORDERED_STATIC_ASSERT_HASH_PRED(Hash, Pred) + static constexpr auto pocs= alloc_traits::propagate_on_container_swap::value; using std::swap; - swap(h(),x.h()); - swap(pred(),x.pred()); if_constexpr([&,this]{ swap_if(al(),x.al()); }, @@ -1424,6 +1499,9 @@ public: BOOST_ASSERT(al()==x.al()); (void)this; /* makes sure captured this is used */ }); + + swap(h(),x.h()); + swap(pred(),x.pred()); swap(size_,x.size_); swap(arrays,x.arrays); swap(ml,x.ml); @@ -1627,6 +1705,9 @@ private: #else std::is_trivially_copy_constructible::value #endif + &&( + is_std_allocator::value|| + !alloc_has_construct::value) >{} ); } @@ -2067,6 +2148,7 @@ private: #undef BOOST_UNORDERED_ASSUME #undef BOOST_UNORDERED_HAS_BUILTIN +#undef BOOST_UNORDERED_STATIC_ASSERT_HASH_PRED #ifdef BOOST_UNORDERED_LITTLE_ENDIAN_NEON #undef BOOST_UNORDERED_LITTLE_ENDIAN_NEON #endif diff --git a/test/exception/assign_exception_tests.cpp b/test/exception/assign_exception_tests.cpp index c036da52..e96e8465 100644 --- a/test/exception/assign_exception_tests.cpp +++ b/test/exception/assign_exception_tests.cpp @@ -60,6 +60,9 @@ template struct assign_base : public test::exception_base test::random_values x_values, y_values; T x, y; + int t1; + int t2; + typedef typename T::hasher hasher; typedef typename T::key_equal key_equal; typedef typename T::allocator_type allocator_type; @@ -67,7 +70,10 @@ template struct assign_base : public test::exception_base assign_base(int tag1, int tag2, float mlf1 = 1.0, float mlf2 = 1.0) : x_values(), y_values(), x(0, hasher(tag1), key_equal(tag1), allocator_type(tag1)), - y(0, hasher(tag2), key_equal(tag2), allocator_type(tag2)) + y(0, hasher(tag2), key_equal(tag2), allocator_type(tag2)), + t1(tag1), + t2(tag2) + { x.max_load_factor(mlf1); y.max_load_factor(mlf2); @@ -89,6 +95,22 @@ template struct assign_base : public test::exception_base { test::check_equivalent_keys(x1); + if (x1.hash_function() == hasher(t1)) { + BOOST_TEST(x1.key_eq() == key_equal(t1)); + } + + if (x1.hash_function() == hasher(t2)) { + BOOST_TEST(x1.key_eq() == key_equal(t2)); + } + + if (x1.key_eq() == key_equal(t1)) { + BOOST_TEST(x1.hash_function() == hasher(t1)); + } + + if (x1.key_eq() == key_equal(t2)) { + BOOST_TEST(x1.hash_function() == hasher(t2)); + } + // If the container is empty at the point of the exception, the // internal structure is hidden, this exposes it, at the cost of // messing up the data. diff --git a/test/exception/move_assign_exception_tests.cpp b/test/exception/move_assign_exception_tests.cpp index 6beb6820..14feed03 100644 --- a/test/exception/move_assign_exception_tests.cpp +++ b/test/exception/move_assign_exception_tests.cpp @@ -20,6 +20,7 @@ template struct move_assign_base : public test::exception_base { test::random_values x_values, y_values; T x, y; + int t1, t2; typedef typename T::hasher hasher; typedef typename T::key_equal key_equal; @@ -28,7 +29,9 @@ template struct move_assign_base : public test::exception_base move_assign_base(int tag1, int tag2, float mlf1 = 1.0, float mlf2 = 1.0) : x_values(), y_values(), x(0, hasher(tag1), key_equal(tag1), allocator_type(tag1)), - y(0, hasher(tag2), key_equal(tag2), allocator_type(tag2)) + y(0, hasher(tag2), key_equal(tag2), allocator_type(tag2)), + t1(tag1), + t2(tag2) { x.max_load_factor(mlf1); y.max_load_factor(mlf2); @@ -52,6 +55,22 @@ template struct move_assign_base : public test::exception_base { test::check_equivalent_keys(x1); + if (x1.hash_function() == hasher(t1)) { + BOOST_TEST(x1.key_eq() == key_equal(t1)); + } + + if (x1.hash_function() == hasher(t2)) { + BOOST_TEST(x1.key_eq() == key_equal(t2)); + } + + if (x1.key_eq() == key_equal(t1)) { + BOOST_TEST(x1.hash_function() == hasher(t1)); + } + + if (x1.key_eq() == key_equal(t2)) { + BOOST_TEST(x1.hash_function() == hasher(t2)); + } + // If the container is empty at the point of the exception, the // internal structure is hidden, this exposes it, at the cost of // messing up the data. diff --git a/test/exception/swap_exception_tests.cpp b/test/exception/swap_exception_tests.cpp index 545d607a..e60ab939 100644 --- a/test/exception/swap_exception_tests.cpp +++ b/test/exception/swap_exception_tests.cpp @@ -4,15 +4,40 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -#include "./containers.hpp" +#define BOOST_ENABLE_ASSERT_HANDLER +#include -#if defined(BOOST_UNORDERED_FOA_TESTS) -#define BOOST_UNORDERED_FOA_WEAK_GUARANTEE_SWAP_EXCEPTIONS_TESTS -#endif +#include "./containers.hpp" #include "../helpers/invariants.hpp" #include "../helpers/random_values.hpp" #include "../helpers/tracker.hpp" +#include "../objects/test.hpp" + +#include + +namespace boost { + void assertion_failed( + char const* expr, char const* function, char const* file, long line) + { + std::stringstream ss; + ss << expr << "\nin " << function << " failed at : " << file << ", line " + << line; + + throw std::runtime_error(ss.str()); + } + + void assertion_failed_msg(char const* expr, char const* msg, + char const* function, char const* file, long line) + { + std::stringstream ss; + ss << expr << "\nin " << function << " failed at : " << file << ", line " + << line << "\n" + << msg; + + throw std::runtime_error(ss.str()); + } +} // namespace boost #if defined(BOOST_MSVC) #pragma warning(disable : 4512) // assignment operator could not be generated @@ -39,15 +64,10 @@ template struct self_swap_base : public test::exception_base void check BOOST_PREVENT_MACRO_SUBSTITUTION(T const& x) const { - std::string scope(test::scope); + (void)x; - // TODO: In C++11 exceptions are only allowed in the swap function. - BOOST_TEST(scope == "hash::hash(hash)" || - scope == "hash::operator=(hash)" || - scope == "equal_to::equal_to(equal_to)" || - scope == "equal_to::operator=(equal_to)"); - - test::check_equivalent_keys(x); + BOOST_ERROR("An exception leaked when it should not have. Allocator " + "equality assertion must precede all other ops"); } }; @@ -140,11 +160,133 @@ template struct swap_test4 : swap_base swap_test4() : swap_base(10, 10, 1, 2) {} }; +template struct unequal_alloc_swap_base : public test::exception_base +{ + const test::random_values x_values, y_values; + const T initial_x, initial_y; + + typedef typename T::hasher hasher; + typedef typename T::key_equal key_equal; + typedef typename T::allocator_type allocator_type; + + unequal_alloc_swap_base(unsigned int count1, unsigned int count2) + : x_values(count1, test::limited_range), + y_values(count2, test::limited_range), + initial_x(x_values.begin(), x_values.end(), 0, allocator_type(1337)), + initial_y(y_values.begin(), y_values.end(), 0, allocator_type(7331)) + { + } + + struct data_type + { + data_type(T const& x_, T const& y_) : x(x_), y(y_) {} + + T x, y; + }; + + data_type init() const { return data_type(initial_x, initial_y); } + + void run(data_type& d) const + { + bool assert_threw = false; + + BOOST_TEST(d.x.get_allocator() != d.y.get_allocator()); + + try { + d.x.swap(d.y); + } catch (std::runtime_error&) { + assert_threw = true; + } + + DISABLE_EXCEPTIONS; + BOOST_TEST(assert_threw); + test::check_container(d.x, this->x_values); + test::check_equivalent_keys(d.x); + test::check_container(d.y, this->y_values); + test::check_equivalent_keys(d.y); + } + + void check BOOST_PREVENT_MACRO_SUBSTITUTION(data_type const& d) const + { + std::string scope(test::scope); + + // TODO: In C++11 exceptions are only allowed in the swap function. + BOOST_TEST(scope == "hash::hash(hash)" || + scope == "hash::operator=(hash)" || + scope == "equal_to::equal_to(equal_to)" || + scope == "equal_to::operator=(equal_to)"); + + test::check_equivalent_keys(d.x); + test::check_equivalent_keys(d.y); + } +}; + +template struct unequal_alloc_swap_test1 : unequal_alloc_swap_base +{ + unequal_alloc_swap_test1() : unequal_alloc_swap_base(0, 0) {} +}; + +template struct unequal_alloc_swap_test2 : unequal_alloc_swap_base +{ + unequal_alloc_swap_test2() : unequal_alloc_swap_base(0, 10) {} +}; + +template struct unequal_alloc_swap_test3 : unequal_alloc_swap_base +{ + unequal_alloc_swap_test3() : unequal_alloc_swap_base(10, 0) {} +}; + +template struct unequal_alloc_swap_test4 : unequal_alloc_swap_base +{ + unequal_alloc_swap_test4() : unequal_alloc_swap_base(10, 10) {} +}; + +#if defined(BOOST_UNORDERED_FOA_TESTS) + +using unordered_flat_set = boost::unordered_flat_set, + std::equal_to, test::allocator1 >; +using unordered_flat_map = boost::unordered_flat_map, + std::equal_to, test::allocator1 > >; + +#define SWAP_CONTAINER_SEQ (unordered_flat_set)(unordered_flat_map) + +#else + +typedef boost::unordered_set, std::equal_to, + test::allocator1 > + unordered_set; +typedef boost::unordered_map, std::equal_to, + test::allocator1 > > + unordered_map; +typedef boost::unordered_multiset, std::equal_to, + test::allocator1 > + unordered_multiset; +typedef boost::unordered_multimap, + std::equal_to, test::allocator1 > > + unordered_multimap; + +#define SWAP_CONTAINER_SEQ \ + (unordered_set)(unordered_map)(unordered_multiset)(unordered_multimap) +#endif + +// FOA containers deliberately choose to not offer the strong exception +// guarantee so we can't reliably test what happens if swapping one of the data +// members throws +// // clang-format off +#if !defined(BOOST_UNORDERED_FOA_TESTS) EXCEPTION_TESTS( (self_swap_test1)(self_swap_test2) (swap_test1)(swap_test2)(swap_test3)(swap_test4), CONTAINER_SEQ) +#endif + +// want to prove that when assertions are defined as throwing operations that we +// uphold invariants +EXCEPTION_TESTS( + (unequal_alloc_swap_test1)(unequal_alloc_swap_test2) + (unequal_alloc_swap_test3)(unequal_alloc_swap_test4), + SWAP_CONTAINER_SEQ) // clang-format on RUN_TESTS() diff --git a/test/helpers/invariants.hpp b/test/helpers/invariants.hpp index 7fbe25a5..a677a91f 100644 --- a/test/helpers/invariants.hpp +++ b/test/helpers/invariants.hpp @@ -54,20 +54,10 @@ namespace test { if (test::has_unique_keys::value && count != 1) BOOST_ERROR("Non-unique key."); -#if !defined(BOOST_UNORDERED_FOA_WEAK_GUARANTEE_SWAP_EXCEPTIONS_TESTS) - // we conditionally compile this check because our FOA implementation only - // exhibits the weak guarantee when swapping throws - // - // in this case, the hasher may be changed before the predicate and the - // arrays are swapped in which case, we can can find an element by - // iteration but unfortunately, it's in the wrong slot according to the - // new hash function so count(key) can wind up returning nothing when - // there really is something if (x1.count(key) != count) { BOOST_ERROR("Incorrect output of count."); std::cerr << x1.count(key) << "," << count << "\n"; } -#endif #ifndef BOOST_UNORDERED_FOA_TESTS // Check that the keys are in the correct bucket and are diff --git a/test/objects/exception.hpp b/test/objects/exception.hpp index b5570e1d..68595eab 100644 --- a/test/objects/exception.hpp +++ b/test/objects/exception.hpp @@ -227,8 +227,21 @@ namespace test { } return x1.tag_ != x2.tag_; } + +#if defined(BOOST_UNORDERED_FOA_TESTS) + friend void swap(hash&, hash&) noexcept; +#endif }; +#if defined(BOOST_UNORDERED_FOA_TESTS) + void swap(hash& lhs, hash& rhs) noexcept + { + int tag = lhs.tag_; + lhs.tag_ = rhs.tag_; + rhs.tag_ = tag; + } +#endif + class less { int tag_; @@ -364,8 +377,20 @@ namespace test { } friend less create_compare(equal_to x) { return less(x.tag_); } +#if defined(BOOST_UNORDERED_FOA_TESTS) + friend void swap(equal_to&, equal_to&) noexcept; +#endif }; +#if defined(BOOST_UNORDERED_FOA_TESTS) + void swap(equal_to& lhs, equal_to& rhs) noexcept + { + int tag = lhs.tag_; + lhs.tag_ = rhs.tag_; + rhs.tag_ = tag; + } +#endif + template class allocator { public: diff --git a/test/objects/minimal.hpp b/test/objects/minimal.hpp index cb1fe9b0..c2690db3 100644 --- a/test/objects/minimal.hpp +++ b/test/objects/minimal.hpp @@ -206,6 +206,11 @@ namespace test { hash& operator=(hash const&) { return *this; } ~hash() {} +#if defined(BOOST_UNORDERED_FOA_TESTS) + hash(hash&&) = default; + hash& operator=(hash&&) = default; +#endif + std::size_t operator()(T const&) const { return 0; } #if BOOST_UNORDERED_CHECK_ADDR_OPERATOR_NOT_USED ampersand_operator_used operator&() const @@ -224,6 +229,11 @@ namespace test { equal_to& operator=(equal_to const&) { return *this; } ~equal_to() {} +#if defined(BOOST_UNORDERED_FOA_TESTS) + equal_to(equal_to&&) = default; + equal_to& operator=(equal_to&&) = default; +#endif + bool operator()(T const&, T const&) const { return true; } #if BOOST_UNORDERED_CHECK_ADDR_OPERATOR_NOT_USED ampersand_operator_used operator&() const diff --git a/test/objects/test.hpp b/test/objects/test.hpp index 0bcf78e8..b1a253e5 100644 --- a/test/objects/test.hpp +++ b/test/objects/test.hpp @@ -230,18 +230,18 @@ namespace test { std::size_t operator()(int x) const { - int result; + unsigned result; switch (type_) { case 1: - result = x; + result = static_cast(x); break; case 2: - result = x * 7; + result = static_cast(x) * 7; break; default: - result = x * 256; + result = static_cast(x) * 256; } - return static_cast(result); + return result; } friend bool operator==(hash const& x1, hash const& x2) diff --git a/test/unordered/copy_tests.cpp b/test/unordered/copy_tests.cpp index 05c0f97a..91cf3ff2 100644 --- a/test/unordered/copy_tests.cpp +++ b/test/unordered/copy_tests.cpp @@ -225,11 +225,227 @@ namespace copy_tests { } } + template + void copy_construct_tests_std_allocator1( + T*, test::random_generator const& generator) + { + typename T::hasher hf; + typename T::key_equal eq; + typename T::allocator_type al; + + { + test::check_instances check_; + + T x; + T y(x); + BOOST_TEST(y.empty()); + BOOST_TEST(test::equivalent(y.hash_function(), hf)); + BOOST_TEST(test::equivalent(y.key_eq(), eq)); + BOOST_TEST(test::equivalent(y.get_allocator(), al)); + BOOST_TEST(x.max_load_factor() == y.max_load_factor()); + BOOST_TEST(test::detail::tracker.count_allocations == 0); + test::check_equivalent_keys(y); + } + + { + test::check_instances check_; + + T x(0); + T y(x); + BOOST_TEST(y.empty()); + BOOST_TEST(test::equivalent(y.hash_function(), hf)); + BOOST_TEST(test::equivalent(y.key_eq(), eq)); + BOOST_TEST(test::equivalent(y.get_allocator(), al)); + BOOST_TEST(x.max_load_factor() == y.max_load_factor()); + BOOST_TEST(test::detail::tracker.count_allocations == 0); + test::check_equivalent_keys(y); + } + + { + test::check_instances check_; + + test::random_values v(1000, generator); + + T x(v.begin(), v.end()); + T y(x); + test::unordered_equivalence_tester equivalent(x); + BOOST_TEST(equivalent(y)); + test::check_equivalent_keys(y); + } + + { + test::check_instances check_; + + // In this test I drop the original containers max load factor, so it + // is much lower than the load factor. The hash table is not allowed + // to rehash, but the destination container should probably allocate + // enough buckets to decrease the load factor appropriately. + test::random_values v(1000, generator); + T x(v.begin(), v.end()); + x.max_load_factor(x.load_factor() / 4); + T y(x); + test::unordered_equivalence_tester equivalent(x); + BOOST_TEST(equivalent(y)); + // This isn't guaranteed: + BOOST_TEST(y.load_factor() < y.max_load_factor()); + test::check_equivalent_keys(y); + } + } + + template + void copy_construct_tests_std_allocator2( + T*, test::random_generator const& generator) + { + typename T::hasher hf(1); + typename T::key_equal eq(1); + typename T::allocator_type al; + + { + test::check_instances check_; + + T x(0, hf, eq, al); + T y(x); + BOOST_TEST(y.empty()); + BOOST_TEST(test::equivalent(y.hash_function(), hf)); + BOOST_TEST(test::equivalent(y.key_eq(), eq)); + BOOST_TEST(test::equivalent(y.get_allocator(), al)); + BOOST_TEST(x.max_load_factor() == y.max_load_factor()); + BOOST_TEST(test::detail::tracker.count_allocations == 0); + test::check_equivalent_keys(y); + } + + { + test::check_instances check_; + + T x(10000, hf, eq, al); + T y(x); + BOOST_TEST(y.empty()); + BOOST_TEST(test::equivalent(y.hash_function(), hf)); + BOOST_TEST(test::equivalent(y.key_eq(), eq)); + BOOST_TEST(test::equivalent(y.get_allocator(), al)); + BOOST_TEST(x.max_load_factor() == y.max_load_factor()); + test::check_equivalent_keys(y); + } + + { + test::check_instances check_; + + T x(0, hf, eq, al); + T y(x, al); + BOOST_TEST(y.empty()); + BOOST_TEST(test::equivalent(y.hash_function(), hf)); + BOOST_TEST(test::equivalent(y.key_eq(), eq)); + BOOST_TEST(test::equivalent(y.get_allocator(), al)); + BOOST_TEST(x.max_load_factor() == y.max_load_factor()); + BOOST_TEST(test::selected_count(y.get_allocator()) == 0); + BOOST_TEST(test::detail::tracker.count_allocations == 0); + test::check_equivalent_keys(y); + } + + { + test::check_instances check_; + + T x(1000, hf, eq, al); + T y(x, al); + BOOST_TEST(y.empty()); + BOOST_TEST(test::equivalent(y.hash_function(), hf)); + BOOST_TEST(test::equivalent(y.key_eq(), eq)); + BOOST_TEST(test::equivalent(y.get_allocator(), al)); + BOOST_TEST(x.max_load_factor() == y.max_load_factor()); + BOOST_TEST(test::selected_count(y.get_allocator()) == 0); + test::check_equivalent_keys(y); + } + + { + test::check_instances check_; + + test::random_values v; + + T x(v.begin(), v.end(), 0, hf, eq, al); + T y(x); + test::unordered_equivalence_tester equivalent(x); + BOOST_TEST(equivalent(y)); + test::check_equivalent_keys(y); + BOOST_TEST(test::equivalent(y.get_allocator(), al)); + BOOST_TEST(test::detail::tracker.count_allocations == 0); + } + + { + test::check_instances check_; + + test::random_values v(1000, generator); + + T x(v.begin(), v.end(), 0, hf, eq, al); + T y(x); + test::unordered_equivalence_tester equivalent(x); + BOOST_TEST(equivalent(y)); + test::check_equivalent_keys(y); + BOOST_TEST(test::equivalent(y.get_allocator(), al)); + } + + { + test::check_instances check_; + + test::random_values v; + + T x(v.begin(), v.end(), 0, hf, eq, al); + T y(x, al); + test::unordered_equivalence_tester equivalent(x); + BOOST_TEST(equivalent(y)); + test::check_equivalent_keys(y); + BOOST_TEST(test::selected_count(y.get_allocator()) == 0); + BOOST_TEST(test::equivalent(y.get_allocator(), al)); + BOOST_TEST(test::detail::tracker.count_allocations == 0); + } + + { + test::check_instances check_; + + test::random_values v(500, generator); + + T x(v.begin(), v.end(), 0, hf, eq, al); + T y(x, al); + test::unordered_equivalence_tester equivalent(x); + BOOST_TEST(equivalent(y)); + test::check_equivalent_keys(y); + BOOST_TEST(test::selected_count(y.get_allocator()) == 0); + BOOST_TEST(test::equivalent(y.get_allocator(), al)); + } + } + using test::default_generator; using test::generate_collisions; using test::limited_range; #ifdef BOOST_UNORDERED_FOA_TESTS + template struct allocator + { + using value_type = T; + + allocator() = default; + allocator(allocator const&) = default; + allocator(allocator&&) = default; + + template allocator(allocator const&) {} + + T* allocate(std::size_t n) + { + return static_cast(::operator new(sizeof(value_type) * n)); + } + + void deallocate(T* p, std::size_t) { ::operator delete(p); } + + friend inline bool operator==(allocator const&, allocator const&) + { + return true; + } + + friend inline bool operator!=(allocator const&, allocator const&) + { + return false; + } + }; + boost::unordered_flat_set >* test_set; boost::unordered_flat_map >* test_map_no_select_copy; + boost::unordered_flat_set >* test_set_trivially_copyable; + boost::unordered_flat_map > >* test_map_trivially_copyable; + + boost::unordered_flat_set >* test_set_trivially_copyable_std_allocator; + boost::unordered_flat_map > >* + test_map_trivially_copyable_std_allocator; + + boost::unordered_flat_set >* + test_set_trivially_copyable_no_construct; + boost::unordered_flat_map > >* + test_map_trivially_copyable_no_construct; + + // clang-format off UNORDERED_TEST(copy_construct_tests1, - ((test_set)(test_map)(test_set_select_copy)(test_map_select_copy)(test_set_no_select_copy)(test_map_no_select_copy))( - (default_generator)(generate_collisions)(limited_range))) + ((test_set)(test_map)(test_set_select_copy)(test_map_select_copy) + (test_set_no_select_copy)(test_map_no_select_copy) + (test_set_trivially_copyable)(test_map_trivially_copyable)) + ((default_generator)(generate_collisions)(limited_range))) UNORDERED_TEST(copy_construct_tests2, - ((test_set)(test_map)(test_set_select_copy)(test_map_select_copy)(test_set_no_select_copy)(test_map_no_select_copy))( - (default_generator)(generate_collisions)(limited_range))) + ((test_set)(test_map)(test_set_select_copy)(test_map_select_copy) + (test_set_no_select_copy)(test_map_no_select_copy) + (test_set_trivially_copyable)(test_map_trivially_copyable)) + ((default_generator)(generate_collisions)(limited_range))) + + UNORDERED_TEST(copy_construct_tests_std_allocator1, + ((test_set_trivially_copyable_std_allocator) + (test_map_trivially_copyable_std_allocator) + (test_set_trivially_copyable_no_construct) + (test_map_trivially_copyable_no_construct)) + ((default_generator)(generate_collisions)(limited_range))) + + UNORDERED_TEST(copy_construct_tests_std_allocator2, + ((test_set_trivially_copyable_std_allocator) + (test_map_trivially_copyable_std_allocator) + (test_set_trivially_copyable_no_construct) + (test_map_trivially_copyable_no_construct)) + ((default_generator)(generate_collisions)(limited_range))) + // clang-format on #else boost::unordered_set >* test_set; diff --git a/test/unordered/noexcept_tests.cpp b/test/unordered/noexcept_tests.cpp index 0cb4b521..72270a1c 100644 --- a/test/unordered/noexcept_tests.cpp +++ b/test/unordered/noexcept_tests.cpp @@ -437,13 +437,11 @@ UNORDERED_AUTO_TEST (prelim_allocator_checks) { using test::default_generator; #ifdef BOOST_UNORDERED_FOA_TESTS -boost::unordered_flat_set >* - throwing_set_alloc1; +boost::unordered_flat_set >* throwing_set_alloc1; -boost::unordered_flat_set >* - throwing_set_alloc2; +boost::unordered_flat_set >* throwing_set_alloc2; UNORDERED_TEST(test_nothrow_move_assign_when_noexcept, ((throwing_set_alloc1)(throwing_set_alloc2))((default_generator)))