From 7befee3bd6920adc75fff9fff76731f6e2b6a611 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 1 Dec 2022 10:51:04 -0800 Subject: [PATCH 01/10] Update swap_exception_tests to exclude FOA containers and add test for throwing asserts when comparing allocators --- test/exception/swap_exception_tests.cpp | 166 ++++++++++++++++++++++-- 1 file changed, 154 insertions(+), 12 deletions(-) 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() From 534170a942ff3acf90cbf1be24ee122b84f24052 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 1 Dec 2022 10:51:30 -0800 Subject: [PATCH 02/10] Remove foa-related macro used for relaxing invariant checking in check_equivalent_keys --- test/helpers/invariants.hpp | 10 ---------- 1 file changed, 10 deletions(-) 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 From b1d43d3ca575a40fb38debe18259642723956d2f Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 1 Dec 2022 10:52:02 -0800 Subject: [PATCH 03/10] Update FOA containers to require nothrow swappability of Hash, KeyEqual members and ensure that throwing assertions uphold strong guarantee --- include/boost/unordered/detail/foa.hpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/include/boost/unordered/detail/foa.hpp b/include/boost/unordered/detail/foa.hpp index ccf48227..e70c6ce5 100644 --- a/include/boost/unordered/detail/foa.hpp +++ b/include/boost/unordered/detail/foa.hpp @@ -69,6 +69,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{ @@ -1412,16 +1418,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()); }, @@ -1429,6 +1434,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); @@ -2075,6 +2083,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 From 75ea43823eb3f17ac9a233ccca8d5f67b42a5a1f Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 1 Dec 2022 12:02:15 -0800 Subject: [PATCH 04/10] Update test Hash, KeyEqual to be nothrow swappable --- test/objects/exception.hpp | 25 +++++++++++++++++++++++++ test/objects/minimal.hpp | 10 ++++++++++ 2 files changed, 35 insertions(+) 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 From 4ac3dcc90c360fdb56c48d2d1582787996ed2cd4 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 1 Dec 2022 14:31:19 -0800 Subject: [PATCH 05/10] Update assign_exception_tests to assert strong guarantee around Hash, KeyEqual pairing --- test/exception/assign_exception_tests.cpp | 24 ++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) 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. From 260b573d8dddda9e200f062b28a1e81a3da80a06 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 1 Dec 2022 14:32:07 -0800 Subject: [PATCH 06/10] Update FOA implementation to exhibit strong guarantee for Hash, KeyEqual in copy assignment --- include/boost/unordered/detail/foa.hpp | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/include/boost/unordered/detail/foa.hpp b/include/boost/unordered/detail/foa.hpp index e70c6ce5..9b3fec58 100644 --- a/include/boost/unordered/detail/foa.hpp +++ b/include/boost/unordered/detail/foa.hpp @@ -1266,13 +1266,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()); From 2043f98593353310a90660997cbeedfd4549993f Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 1 Dec 2022 15:50:28 -0800 Subject: [PATCH 07/10] Add strong exception guarantees around Hash, KeyEqual for move_assign_exception_tests --- .../exception/move_assign_exception_tests.cpp | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) 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. From c8910e8007c3e476a17f1c177c87f39bf77e6f35 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 1 Dec 2022 15:51:22 -0800 Subject: [PATCH 08/10] Update FOA move assignment operator to uphold the strong guarantee for Hash, KeyEqual --- include/boost/unordered/detail/foa.hpp | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/include/boost/unordered/detail/foa.hpp b/include/boost/unordered/detail/foa.hpp index 9b3fec58..41a1f153 100644 --- a/include/boost/unordered/detail/foa.hpp +++ b/include/boost/unordered/detail/foa.hpp @@ -1306,19 +1306,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_); From 0ab4e125021ae4f92a73e03db3161197e910020d Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 1 Dec 2022 15:51:35 -0800 Subject: [PATCH 09/10] Update noexcept tests for new FOA requirements --- test/unordered/noexcept_tests.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) 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))) From 0ad6ccb0b934289a0d83de3ddb8f5aba722aa4be Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Mon, 5 Dec 2022 13:08:48 -0800 Subject: [PATCH 10/10] Update FOA noexcept docs for move assignment, swap --- doc/unordered/unordered_flat_map.adoc | 22 +++++++++------------- doc/unordered/unordered_flat_set.adoc | 22 +++++++++------------- 2 files changed, 18 insertions(+), 26 deletions(-) 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.