diff --git a/include/boost/unordered/detail/implementation.hpp b/include/boost/unordered/detail/implementation.hpp index a44a2c27..3e5d95f6 100644 --- a/include/boost/unordered/detail/implementation.hpp +++ b/include/boost/unordered/detail/implementation.hpp @@ -3540,49 +3540,86 @@ namespace boost { move_assign(x, is_unique, boost::unordered::detail::integral_constant:: - propagate_on_container_move_assignment::value>()); + propagate_on_container_move_assignment::value>(), + boost::unordered::detail::integral_constant()); } } + // Propagate allocator, move assign functions throwable template - void move_assign(table& x, UniqueType, true_type) + void move_assign(table& x, UniqueType, true_type, false_type) { - delete_buckets(); set_hash_functions new_func_this(*this, x); + // TODO: Can this throw? If so then breaks noexcept spec. + // Maybe don't do it if allocators are equal. + delete_buckets(); allocators_.move_assign(x.allocators_); - // No throw from here. mlf_ = x.mlf_; move_buckets_from(x); new_func_this.commit(); } + // Propagate allocator, move assign functions noexcept template - void move_assign(table& x, UniqueType is_unique, false_type) + void move_assign(table& x, UniqueType, true_type, true_type) + { + delete_buckets(); + allocators_.move_assign(x.allocators_); + mlf_ = x.mlf_; + move_buckets_from(x); + this->current_functions().move_assign(x.current_functions()); + } + + // Don't propagate allocator + template + void move_assign( + table& x, UniqueType is_unique, false_type, IsNoExcept is_noexcept) { if (node_alloc() == x.node_alloc()) { - delete_buckets(); - set_hash_functions new_func_this(*this, x); - // No throw from here. - mlf_ = x.mlf_; - move_buckets_from(x); - new_func_this.commit(); + move_assign_equal_alloc(x, is_noexcept); } else { - set_hash_functions new_func_this(*this, x); - mlf_ = x.mlf_; - recalculate_max_load(); - - if (x.size_ > max_load_) { - create_buckets(min_buckets_for_size(x.size_)); - } else if (size_) { - clear_buckets(); - } - - new_func_this.commit(); - - move_assign_buckets(x, is_unique); + move_assign_realloc(x, is_unique); } } + // Move assign functions throwable + void move_assign_equal_alloc(table& x, false_type) + { + set_hash_functions new_func_this(*this, x); + delete_buckets(); + mlf_ = x.mlf_; + move_buckets_from(x); + new_func_this.commit(); + } + + // Move assign functions noexcept + void move_assign_equal_alloc(table& x, true_type) + { + delete_buckets(); + mlf_ = x.mlf_; + move_buckets_from(x); + this->current_functions().move_assign(x.current_functions()); + } + + template + void move_assign_realloc(table& x, UniqueType is_unique) + { + set_hash_functions new_func_this(*this, x); + mlf_ = x.mlf_; + recalculate_max_load(); + + if (x.size_ > max_load_) { + create_buckets(min_buckets_for_size(x.size_)); + } else if (size_) { + clear_buckets(); + } + + new_func_this.commit(); + + move_assign_buckets(x, is_unique); + } + // Accessors const_key_type& get_key(node_pointer n) const diff --git a/include/boost/unordered/unordered_map.hpp b/include/boost/unordered/unordered_map.hpp index 58d18944..340bb5ef 100644 --- a/include/boost/unordered/unordered_map.hpp +++ b/include/boost/unordered/unordered_map.hpp @@ -151,10 +151,9 @@ namespace boost { } unordered_map& operator=(BOOST_RV_REF(unordered_map) x) - // C++17 support: BOOST_NOEXCEPT_IF( - // value_allocator_traits::is_always_equal::value && - // is_nothrow_move_assignable_v && - // is_nothrow_move_assignable_v

) + BOOST_NOEXCEPT_IF(value_allocator_traits::is_always_equal::value&& + boost::is_nothrow_move_assignable::value&& + boost::is_nothrow_move_assignable

::value) { table_.move_assign(x.table_, boost::unordered::detail::true_type()); return *this; @@ -168,10 +167,9 @@ namespace boost { #if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES) unordered_map& operator=(unordered_map&& x) - // C++17 support: BOOST_NOEXCEPT_IF( - // value_allocator_traits::is_always_equal::value && - // is_nothrow_move_assignable_v && - // is_nothrow_move_assignable_v

) + BOOST_NOEXCEPT_IF(value_allocator_traits::is_always_equal::value&& + boost::is_nothrow_move_assignable::value&& + boost::is_nothrow_move_assignable

::value) { table_.move_assign(x.table_, boost::unordered::detail::true_type()); return *this; @@ -1023,10 +1021,9 @@ namespace boost { } unordered_multimap& operator=(BOOST_RV_REF(unordered_multimap) x) - // C++17 support: BOOST_NOEXCEPT_IF( - // value_allocator_traits::is_always_equal::value && - // is_nothrow_move_assignable_v && - // is_nothrow_move_assignable_v

) + BOOST_NOEXCEPT_IF(value_allocator_traits::is_always_equal::value&& + boost::is_nothrow_move_assignable::value&& + boost::is_nothrow_move_assignable

::value) { table_.move_assign(x.table_, boost::unordered::detail::false_type()); return *this; @@ -1040,10 +1037,9 @@ namespace boost { #if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES) unordered_multimap& operator=(unordered_multimap&& x) - // C++17 support: BOOST_NOEXCEPT_IF( - // value_allocator_traits::is_always_equal::value && - // is_nothrow_move_assignable_v && - // is_nothrow_move_assignable_v

) + BOOST_NOEXCEPT_IF(value_allocator_traits::is_always_equal::value&& + boost::is_nothrow_move_assignable::value&& + boost::is_nothrow_move_assignable

::value) { table_.move_assign(x.table_, boost::unordered::detail::false_type()); return *this; diff --git a/include/boost/unordered/unordered_set.hpp b/include/boost/unordered/unordered_set.hpp index e119ee76..f83c3526 100644 --- a/include/boost/unordered/unordered_set.hpp +++ b/include/boost/unordered/unordered_set.hpp @@ -149,10 +149,9 @@ namespace boost { } unordered_set& operator=(BOOST_RV_REF(unordered_set) x) - // C++17 support: BOOST_NOEXCEPT_IF( - // value_allocator_traits::is_always_equal::value && - // is_nothrow_move_assignable_v && - // is_nothrow_move_assignable_v

) + BOOST_NOEXCEPT_IF(value_allocator_traits::is_always_equal::value&& + boost::is_nothrow_move_assignable::value&& + boost::is_nothrow_move_assignable

::value) { table_.move_assign(x.table_, boost::unordered::detail::true_type()); return *this; @@ -166,10 +165,9 @@ namespace boost { #if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES) unordered_set& operator=(unordered_set&& x) - // C++17 support: BOOST_NOEXCEPT_IF( - // value_allocator_traits::is_always_equal::value && - // is_nothrow_move_assignable_v && - // is_nothrow_move_assignable_v

) + BOOST_NOEXCEPT_IF(value_allocator_traits::is_always_equal::value&& + boost::is_nothrow_move_assignable::value&& + boost::is_nothrow_move_assignable

::value) { table_.move_assign(x.table_, boost::unordered::detail::true_type()); return *this; @@ -706,10 +704,9 @@ namespace boost { } unordered_multiset& operator=(BOOST_RV_REF(unordered_multiset) x) - // C++17 support: BOOST_NOEXCEPT_IF( - // value_allocator_traits::is_always_equal::value && - // is_nothrow_move_assignable_v && - // is_nothrow_move_assignable_v

) + BOOST_NOEXCEPT_IF(value_allocator_traits::is_always_equal::value&& + boost::is_nothrow_move_assignable::value&& + boost::is_nothrow_move_assignable

::value) { table_.move_assign(x.table_, boost::unordered::detail::false_type()); return *this; @@ -723,10 +720,9 @@ namespace boost { #if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES) unordered_multiset& operator=(unordered_multiset&& x) - // C++17 support: BOOST_NOEXCEPT_IF( - // value_allocator_traits::is_always_equal::value && - // is_nothrow_move_assignable_v && - // is_nothrow_move_assignable_v

) + BOOST_NOEXCEPT_IF(value_allocator_traits::is_always_equal::value&& + boost::is_nothrow_move_assignable::value&& + boost::is_nothrow_move_assignable

::value) { table_.move_assign(x.table_, boost::unordered::detail::false_type()); return *this; diff --git a/test/unordered/noexcept_tests.cpp b/test/unordered/noexcept_tests.cpp index f5a38a19..84bb404e 100644 --- a/test/unordered/noexcept_tests.cpp +++ b/test/unordered/noexcept_tests.cpp @@ -18,11 +18,19 @@ namespace noexcept_tests { struct hash_possible_exception : boost::hash { hash_possible_exception(hash_possible_exception const&) {} + hash_possible_exception& operator=(hash_possible_exception const&) + { + return *this; + } }; struct equal_to_possible_exception : std::equal_to { equal_to_possible_exception(equal_to_possible_exception const&) {} + equal_to_possible_exception& operator=(equal_to_possible_exception const&) + { + return *this; + } }; // Test that the move constructor does actually move without throwing @@ -88,7 +96,8 @@ namespace noexcept_tests { } }; - typedef hash_nothrow hash_nothrow_move; + typedef hash_nothrow hash_nothrow_move_construct; + typedef hash_nothrow hash_nothrow_move_assign; typedef hash_nothrow hash_nothrow_swap; template equal_to_nothrow_move; + typedef equal_to_nothrow equal_to_nothrow_move_construct; + typedef equal_to_nothrow equal_to_nothrow_move_assign; typedef equal_to_nothrow equal_to_nothrow_swap; bool have_is_nothrow_move = false; + bool have_is_nothrow_move_assign = false; bool have_is_nothrow_swap = false; UNORDERED_AUTO_TEST (check_is_nothrow_move) { BOOST_TEST( !boost::is_nothrow_move_constructible::value); + BOOST_TEST( + !boost::is_nothrow_move_assignable::value); + BOOST_TEST(!boost::unordered::detail::is_nothrow_swappable< + hash_possible_exception>::value); + BOOST_TEST((!boost::is_nothrow_move_constructible< + equal_to_nothrow >::value)); + BOOST_TEST((!boost::is_nothrow_move_assignable< + equal_to_nothrow >::value)); + BOOST_TEST((!boost::unordered::detail::is_nothrow_swappable< + equal_to_nothrow >::value)); + have_is_nothrow_move = - boost::is_nothrow_move_constructible::value; + boost::is_nothrow_move_constructible::value; + have_is_nothrow_move_assign = + boost::is_nothrow_move_assignable::value; have_is_nothrow_swap = boost::unordered::detail::is_nothrow_swappable::value; -// Copied from boost::is_nothrow_move_constructible implementation -// to make sure this does actually detect it when expected. -// -// The type trait is also available when BOOST_IS_NOTHROW_MOVE_CONSTRUCT -// is defined (for some versions of Visual C++?) but detects 'throw()', -// not noexcept. +// Check that the traits work when expected. #if !defined(BOOST_NO_CXX11_NOEXCEPT) && !defined(BOOST_NO_SFINAE_EXPR) && \ !BOOST_WORKAROUND(BOOST_GCC_VERSION, < 40800) BOOST_TEST(have_is_nothrow_move); + BOOST_TEST(have_is_nothrow_move_assign); #endif #if !defined(BOOST_NO_SFINAE_EXPR) && !defined(BOOST_NO_CXX11_NOEXCEPT) && \ @@ -193,31 +213,66 @@ namespace noexcept_tests { boost::hash, equal_to_possible_exception> >::value)); } - UNORDERED_AUTO_TEST (test_no_throw_when_noexcept) { - typedef boost::unordered_set + UNORDERED_AUTO_TEST (test_nothrow_move_when_noexcept) { + typedef boost::unordered_set throwing_set; if (have_is_nothrow_move) { BOOST_TEST(boost::is_nothrow_move_constructible::value); - - throwing_test_exception = false; - - throwing_set x1; - x1.insert(10); - x1.insert(50); - - try { - throwing_test_exception = true; - - throwing_set x2 = boost::move(x1); - BOOST_TEST(x2.size() == 2); - BOOST_TEST(*x2.begin() == 10 || *x2.begin() == 50); - } catch (test_exception) { - BOOST_TEST(false); - } - - throwing_test_exception = false; } + + throwing_test_exception = false; + + throwing_set x1; + x1.insert(10); + x1.insert(50); + + try { + throwing_test_exception = true; + + throwing_set x2 = boost::move(x1); + BOOST_TEST(x2.size() == 2); + BOOST_TEST(*x2.begin() == 10 || *x2.begin() == 50); + BOOST_TEST(have_is_nothrow_move); + } catch (test_exception) { + BOOST_TEST(!have_is_nothrow_move); + } + + throwing_test_exception = false; + } + + UNORDERED_AUTO_TEST (test_nothrow_move_assign_when_noexcept) { + typedef boost::unordered_set + throwing_set; + + if (have_is_nothrow_move_assign) { + BOOST_TEST(boost::is_nothrow_move_assignable::value); + } + + throwing_test_exception = false; + + throwing_set x1; + throwing_set x2; + x1.insert(10); + x1.insert(50); + for (int i = 0; i < 100; ++i) { + x2.insert(i); + } + + try { + throwing_test_exception = true; + + x2 = boost::move(x1); + BOOST_TEST(x2.size() == 2); + BOOST_TEST(*x2.begin() == 10 || *x2.begin() == 50); + BOOST_TEST(have_is_nothrow_move_assign); + } catch (test_exception) { + BOOST_TEST(!have_is_nothrow_move_assign); + } + + throwing_test_exception = false; } UNORDERED_AUTO_TEST (test_nothrow_swap_when_noexcept) {