// Copyright 2013 Daniel James. // Copyright 2022-2023 Christian Mazakas. // 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 "../helpers/unordered.hpp" #include "../helpers/fwd.hpp" #include "../helpers/test.hpp" #if defined(BOOST_MSVC) #pragma warning(push) // conditional expression is constant #pragma warning(disable : 4127) #endif namespace noexcept_tests { // Test the noexcept is set correctly for the move constructor. 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 // an exception when it claims to. struct test_exception { }; bool throwing_test_exception = false; void test_throw(char const* name) { if (throwing_test_exception) { BOOST_LIGHTWEIGHT_TEST_OSTREAM << "Throw exception in: " << name << std::endl; throw test_exception(); } } template class hash_nothrow : boost::hash { typedef boost::hash base; public: hash_nothrow(hash_nothrow&&) noexcept(nothrow_move_construct) { if (!nothrow_move_construct) { test_throw("Move Constructor"); } } hash_nothrow() { test_throw("Constructor"); } hash_nothrow(hash_nothrow const&) { test_throw("Copy"); } hash_nothrow& operator=(hash_nothrow const&) { test_throw("Assign"); return *this; } hash_nothrow& operator=(hash_nothrow&&) noexcept(nothrow_move_assign) { if (!nothrow_move_assign) { test_throw("Move Assign"); } return *this; } std::size_t operator()(int x) const { test_throw("Operator"); return static_cast(*this)(x); } friend void swap(hash_nothrow&, hash_nothrow&) noexcept(nothrow_swap) { if (!nothrow_swap) { test_throw("Swap"); } } }; typedef hash_nothrow hash_nothrow_move_construct; typedef hash_nothrow hash_nothrow_move_assign; typedef hash_nothrow hash_nothrow_swap; template class equal_to_nothrow { typedef boost::hash base; public: equal_to_nothrow(equal_to_nothrow&&) noexcept(nothrow_move_construct) { if (!nothrow_move_construct) { test_throw("Move Constructor"); } } equal_to_nothrow() { test_throw("Constructor"); } equal_to_nothrow(equal_to_nothrow const&) { test_throw("Copy"); } equal_to_nothrow& operator=(equal_to_nothrow const&) { test_throw("Assign"); return *this; } equal_to_nothrow& operator=(equal_to_nothrow&&) noexcept(nothrow_move_assign) { if (!nothrow_move_assign) { test_throw("Move Assign"); } return *this; } std::size_t operator()(int x, int y) const { test_throw("Operator"); return x == y; } friend void swap(equal_to_nothrow&, equal_to_nothrow&) noexcept(nothrow_swap) { if (!nothrow_swap) { test_throw("Swap"); } } }; 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::is_nothrow_swappable::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::is_nothrow_swappable< equal_to_nothrow >::value)); have_is_nothrow_move = boost::is_nothrow_move_constructible::value; have_is_nothrow_move_assign = boost::is_nothrow_move_assignable::value; have_is_nothrow_swap = boost::is_nothrow_swappable::value; // Check that the traits work when expected. BOOST_TEST(have_is_nothrow_move); BOOST_TEST(have_is_nothrow_move_assign); BOOST_TEST(have_is_nothrow_swap); BOOST_LIGHTWEIGHT_TEST_OSTREAM << "have_is_nothrow_move: " << have_is_nothrow_move << std::endl << "have_is_nothrow_swap: " << have_is_nothrow_swap << std::endl; } UNORDERED_AUTO_TEST (test_noexcept) { if (have_is_nothrow_move) { #ifdef BOOST_UNORDERED_FOA_TESTS BOOST_TEST((boost::is_nothrow_move_constructible< boost::unordered_flat_set >::value)); BOOST_TEST((boost::is_nothrow_move_constructible< boost::unordered_flat_map >::value)); BOOST_TEST((boost::is_nothrow_move_constructible< boost::unordered_node_set >::value)); BOOST_TEST((boost::is_nothrow_move_constructible< boost::unordered_node_map >::value)); #else BOOST_TEST((boost::is_nothrow_move_constructible< boost::unordered_set >::value)); BOOST_TEST((boost::is_nothrow_move_constructible< boost::unordered_multiset >::value)); BOOST_TEST((boost::is_nothrow_move_constructible< boost::unordered_map >::value)); BOOST_TEST((boost::is_nothrow_move_constructible< boost::unordered_multimap >::value)); #endif } #ifdef BOOST_UNORDERED_FOA_TESTS BOOST_TEST( (!boost::is_nothrow_move_constructible< boost::unordered_flat_set >::value)); BOOST_TEST( (!boost::is_nothrow_move_constructible, equal_to_possible_exception> >::value)); BOOST_TEST( (!boost::is_nothrow_move_constructible< boost::unordered_node_set >::value)); BOOST_TEST( (!boost::is_nothrow_move_constructible, equal_to_possible_exception> >::value)); #else BOOST_TEST((!boost::is_nothrow_move_constructible< boost::unordered_set >::value)); BOOST_TEST( (!boost::is_nothrow_move_constructible, equal_to_possible_exception> >::value)); #endif } template static void test_nothrow_move_when_noexcept(X*) { if (have_is_nothrow_move) { BOOST_TEST(boost::is_nothrow_move_constructible::value); } throwing_test_exception = false; X x1; x1.insert(10); x1.insert(50); try { throwing_test_exception = true; X x2 = std::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; } template void test_nothrow_move_assign_when_noexcept(T*, test::random_generator const&) { { if (have_is_nothrow_move_assign) { BOOST_TEST(boost::is_nothrow_move_assignable::value); } throwing_test_exception = false; T x1; T x2; x1.insert(10); x1.insert(50); for (int i = 0; i < 100; ++i) { x2.insert(i); } try { throwing_test_exception = true; x2 = std::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; } { if (have_is_nothrow_move_assign) { BOOST_TEST(boost::is_nothrow_move_assignable::value); } throwing_test_exception = false; T x1; T x2; x1.insert(10); x1.insert(50); for (int i = 0; i < 100; ++i) { x2.insert(i); } try { throwing_test_exception = true; x1 = std::move(x2); BOOST_TEST(x1.size() == 100); BOOST_TEST(have_is_nothrow_move_assign); } catch (test_exception) { BOOST_TEST(!have_is_nothrow_move_assign); } throwing_test_exception = false; } } template static void test_nothrow_swap_when_noexcept(X*) { if (have_is_nothrow_swap) { BOOST_TEST(boost::is_nothrow_swappable::value); } throwing_test_exception = false; X x1; X x2; x1.insert(10); x1.insert(50); for (int i = 0; i < 100; ++i) { x2.insert(i); } try { throwing_test_exception = true; x1.swap(x2); BOOST_TEST(x1.size() == 100); BOOST_TEST(x2.size() == 2); BOOST_TEST(*x2.begin() == 10 || *x2.begin() == 50); BOOST_TEST(have_is_nothrow_swap); } catch (test_exception) { BOOST_TEST(!have_is_nothrow_swap); } throwing_test_exception = false; } } // namespace noexcept_tests #if defined(BOOST_MSVC) #pragma warning(pop) #endif template class allocator1 { allocator1 operator=(allocator1 const&); allocator1 operator=(allocator1&&); public: typedef T value_type; allocator1() {} allocator1(allocator1 const&) {} template allocator1(allocator1 const&) {} T* allocate(std::size_t n) { noexcept_tests::test_throw("Allocate"); return static_cast(::operator new(n * sizeof(T))); } void deallocate(T* p, std::size_t) { ::operator delete(p); } friend bool operator==(allocator1 const&, allocator1 const&) { return true; } friend bool operator!=(allocator1 const&, allocator1 const&) { return false; } }; template class allocator2 { allocator2 operator=(allocator2 const&); public: typedef T value_type; typedef boost::true_type propagate_on_container_move_assignment; allocator2() {} allocator2(allocator2 const&) {} template allocator2(allocator2 const&) {} allocator2& operator=(allocator2&&) { return *this; } T* allocate(std::size_t n) { noexcept_tests::test_throw("Allocate"); return static_cast(::operator new(n * sizeof(T))); } void deallocate(T* p, std::size_t) { ::operator delete(p); } friend bool operator==(allocator2 const&, allocator2 const&) { return true; } friend bool operator!=(allocator2 const&, allocator2 const&) { return false; } }; UNORDERED_AUTO_TEST (prelim_allocator_checks) { BOOST_TEST(boost::allocator_is_always_equal >::type::value); BOOST_TEST(!boost::allocator_propagate_on_container_move_assignment< allocator1 >::type::value); BOOST_TEST(boost::allocator_is_always_equal >::type::value); BOOST_TEST(boost::allocator_propagate_on_container_move_assignment< allocator2 >::type::value); } using test::default_generator; #ifdef BOOST_UNORDERED_FOA_TESTS boost::unordered_flat_set* throwing_set; boost::unordered_flat_set* throwing_set2; boost::unordered_flat_set >* throwing_set_alloc1; boost::unordered_flat_set >* throwing_set_alloc2; boost::unordered_node_set* throwing_node_set; boost::unordered_node_set* throwing_node_set2; boost::unordered_node_set >* throwing_node_set_alloc1; boost::unordered_node_set >* throwing_node_set_alloc2; // clang-format off UNORDERED_TEST( test_nothrow_move_when_noexcept, ((throwing_set)(throwing_node_set))) UNORDERED_TEST( test_nothrow_swap_when_noexcept, ((throwing_set2)(throwing_node_set2))) UNORDERED_TEST(test_nothrow_move_assign_when_noexcept, ((throwing_set_alloc1)(throwing_set_alloc2) (throwing_node_set_alloc1)(throwing_node_set_alloc2))( (default_generator))) // clang-format on #else boost::unordered_set* throwing_set; boost::unordered_set* throwing_set2; boost::unordered_set >* throwing_set_alloc1; boost::unordered_set >* throwing_set_alloc2; UNORDERED_TEST(test_nothrow_move_when_noexcept, ((throwing_set))) UNORDERED_TEST(test_nothrow_swap_when_noexcept, ((throwing_set2))) UNORDERED_TEST(test_nothrow_move_assign_when_noexcept, ((throwing_set_alloc1)(throwing_set_alloc2))((default_generator))) #endif RUN_TESTS()