diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 10bfbba5..952cb873 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -39,26 +39,26 @@ #define BOOST_UNORDERED_COMMA , -#define BOOST_UNORDERED_LAST_ARG(Arg, Args) \ -mp11::mp_back> +#define BOOST_UNORDERED_LAST_ARG(Arg, Args) \ + mp11::mp_back > -#define BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg, Args) \ -BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(BOOST_UNORDERED_LAST_ARG(Arg, Args)) +#define BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg, Args) \ + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(BOOST_UNORDERED_LAST_ARG(Arg, Args)) -#define BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg, Args) \ -BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE( \ - BOOST_UNORDERED_LAST_ARG(Arg, Args)) +#define BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg, Args) \ + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE( \ + BOOST_UNORDERED_LAST_ARG(Arg, Args)) namespace boost { namespace unordered { namespace detail { + template - struct is_invocable: - std::is_constructible< - std::function, - std::reference_wrapper::type> - > - {}; + struct is_invocable + : std::is_constructible, + std::reference_wrapper::type> > + { + }; template struct concurrent_map_types { @@ -142,7 +142,11 @@ namespace boost { using const_pointer = typename boost::allocator_const_pointer::type; - concurrent_flat_map() : concurrent_flat_map(0) {} + concurrent_flat_map() + : concurrent_flat_map(detail::foa::default_bucket_count) + { + } + explicit concurrent_flat_map(size_type n, const hasher& hf = hasher(), const key_equal& eql = key_equal(), const allocator_type& a = allocator_type()) @@ -150,6 +154,23 @@ namespace boost { { } + template + concurrent_flat_map(InputIterator f, InputIterator l, + size_type n = detail::foa::default_bucket_count, + const hasher& hf = hasher(), const key_equal& eql = key_equal(), + const allocator_type& a = allocator_type()) + : table_(n, hf, eql, a) + { + this->insert(f, l); + } + + concurrent_flat_map(concurrent_flat_map const& rhs) + : table_(rhs.table_, + boost::allocator_select_on_container_copy_construction( + rhs.get_allocator())) + { + } + /// Capacity /// @@ -276,15 +297,13 @@ namespace boost { template bool insert_or_assign(key_type const& k, M&& obj) { - return table_.try_emplace_or_visit( - k, std::forward(obj), + return table_.try_emplace_or_visit(k, std::forward(obj), [&](value_type& m) { m.second = std::forward(obj); }); } template bool insert_or_assign(key_type&& k, M&& obj) { - return table_.try_emplace_or_visit( - std::move(k), std::forward(obj), + return table_.try_emplace_or_visit(std::move(k), std::forward(obj), [&](value_type& m) { m.second = std::forward(obj); }); } @@ -293,8 +312,8 @@ namespace boost { detail::are_transparent::value, bool>::type insert_or_assign(K&& k, M&& obj) { - return table_.try_emplace_or_visit( - std::forward(k), std::forward(obj), + return table_.try_emplace_or_visit(std::forward(k), + std::forward(obj), [&](value_type& m) { m.second = std::forward(obj); }); } @@ -455,16 +474,16 @@ namespace boost { bool try_emplace_or_visit(K&& k, Arg&& arg, Args&&... args) { BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE(Arg, Args...) - return table_.try_emplace_or_visit( - std::forward(k), std::forward(arg), std::forward(args)...); + return table_.try_emplace_or_visit(std::forward(k), + std::forward(arg), std::forward(args)...); } template bool try_emplace_or_cvisit(K&& k, Arg&& arg, Args&&... args) { BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_CONST_INVOCABLE(Arg, Args...) - return table_.try_emplace_or_cvisit( - std::forward(k), std::forward(arg), std::forward(args)...); + return table_.try_emplace_or_cvisit(std::forward(k), + std::forward(arg), std::forward(args)...); } size_type erase(key_type const& k) { return table_.erase(k); } @@ -508,6 +527,16 @@ namespace boost { /// void rehash(size_type n) { table_.rehash(n); } void reserve(size_type n) { table_.reserve(n); } + + /// Observers + /// + allocator_type get_allocator() const noexcept + { + return table_.get_allocator(); + } + + hasher hash_function() const { return table_.hash_function(); } + key_equal key_eq() const { return table_.key_eq(); } }; } // namespace unordered } // namespace boost diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index ca00ff4f..604eedf5 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -175,7 +175,14 @@ alias foa_tests : foa_merge_exception_tests ; -local CFOA_TESTS = insert_tests erase_tests try_emplace_tests emplace_tests visit_tests ; +local CFOA_TESTS = + insert_tests + erase_tests + try_emplace_tests + emplace_tests + visit_tests + constructor_tests +; for local test in $(CFOA_TESTS) { diff --git a/test/cfoa/constructor_tests.cpp b/test/cfoa/constructor_tests.cpp new file mode 100644 index 00000000..246a641f --- /dev/null +++ b/test/cfoa/constructor_tests.cpp @@ -0,0 +1,256 @@ +// Copyright (C) 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.hpp" + +#include + +test::seed_t initialize_seed(4122023); + +using test::default_generator; +using test::limited_range; +using test::sequential; + +using hasher = stateful_hash; +using key_equal = stateful_key_equal; +using allocator_type = std::allocator >; + +using map_type = boost::unordered::concurrent_flat_map; + +UNORDERED_AUTO_TEST (default_constructor) { + boost::unordered::concurrent_flat_map x; + BOOST_TEST(x.empty()); + BOOST_TEST_EQ(x.size(), 0u); +} + +UNORDERED_AUTO_TEST (bucket_count_with_hasher_key_equal_and_allocator) { + raii::reset_counts(); + { + map_type x(0); + + BOOST_TEST(x.empty()); + BOOST_TEST_EQ(x.size(), 0u); + BOOST_TEST_EQ(x.hash_function(), hasher()); + BOOST_TEST_EQ(x.key_eq(), key_equal()); + } + + { + map_type x(0, hasher(1)); + + BOOST_TEST(x.empty()); + BOOST_TEST_EQ(x.size(), 0u); + BOOST_TEST_EQ(x.hash_function(), hasher(1)); + BOOST_TEST_EQ(x.key_eq(), key_equal()); + } + + { + map_type x(0, hasher(1), key_equal(2)); + + BOOST_TEST(x.empty()); + BOOST_TEST_EQ(x.size(), 0u); + BOOST_TEST_EQ(x.hash_function(), hasher(1)); + BOOST_TEST_EQ(x.key_eq(), key_equal(2)); + } + + { + map_type x(0, hasher(1), key_equal(2), allocator_type{}); + + BOOST_TEST(x.empty()); + BOOST_TEST_EQ(x.size(), 0u); + BOOST_TEST_EQ(x.hash_function(), hasher(1)); + BOOST_TEST_EQ(x.key_eq(), key_equal(2)); + BOOST_TEST(x.get_allocator() == allocator_type{}); + } + raii::reset_counts(); +} + +namespace { + template void from_iterator_range(G gen, test::random_generator rg) + { + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); + auto reference_map = + boost::unordered_flat_map(values.begin(), values.end()); + raii::reset_counts(); + + { + map_type x(values.begin(), values.end()); + + test_matches_reference(x, reference_map); + BOOST_TEST_GT(x.size(), 0u); + BOOST_TEST_LE(x.size(), values.size()); + BOOST_TEST_EQ(x.hash_function(), hasher()); + BOOST_TEST_EQ(x.key_eq(), key_equal()); + BOOST_TEST(x.get_allocator() == allocator_type{}); + if (rg == sequential) { + BOOST_TEST_EQ(x.size(), values.size()); + } + raii::reset_counts(); + } + + { + map_type x(values.begin(), values.end(), 0); + + test_matches_reference(x, reference_map); + BOOST_TEST_GT(x.size(), 0u); + BOOST_TEST_LE(x.size(), values.size()); + BOOST_TEST_EQ(x.hash_function(), hasher()); + BOOST_TEST_EQ(x.key_eq(), key_equal()); + BOOST_TEST(x.get_allocator() == allocator_type{}); + if (rg == sequential) { + BOOST_TEST_EQ(x.size(), values.size()); + } + raii::reset_counts(); + } + + { + map_type x(values.begin(), values.end(), 0, hasher(1)); + + test_matches_reference(x, reference_map); + BOOST_TEST_GT(x.size(), 0u); + BOOST_TEST_LE(x.size(), values.size()); + BOOST_TEST_EQ(x.hash_function(), hasher(1)); + BOOST_TEST_EQ(x.key_eq(), key_equal()); + BOOST_TEST(x.get_allocator() == allocator_type{}); + if (rg == sequential) { + BOOST_TEST_EQ(x.size(), values.size()); + } + raii::reset_counts(); + } + + { + map_type x(values.begin(), values.end(), 0, hasher(1), key_equal(2)); + + test_matches_reference(x, reference_map); + BOOST_TEST_GT(x.size(), 0u); + BOOST_TEST_LE(x.size(), values.size()); + BOOST_TEST_EQ(x.hash_function(), hasher(1)); + BOOST_TEST_EQ(x.key_eq(), key_equal(2)); + BOOST_TEST(x.get_allocator() == allocator_type{}); + if (rg == sequential) { + BOOST_TEST_EQ(x.size(), values.size()); + } + raii::reset_counts(); + } + + { + map_type x(values.begin(), values.end(), 0, hasher(1), key_equal(2), + allocator_type{}); + + test_matches_reference(x, reference_map); + BOOST_TEST_GT(x.size(), 0u); + BOOST_TEST_LE(x.size(), values.size()); + BOOST_TEST_EQ(x.hash_function(), hasher(1)); + BOOST_TEST_EQ(x.key_eq(), key_equal(2)); + BOOST_TEST(x.get_allocator() == allocator_type{}); + if (rg == sequential) { + BOOST_TEST_EQ(x.size(), values.size()); + } + raii::reset_counts(); + } + } + + template void copy_constructor(G gen, test::random_generator rg) + { + { + map_type x(0, hasher(1), key_equal(2), allocator_type{}); + map_type y(x); + + BOOST_TEST_EQ(y.size(), x.size()); + BOOST_TEST_EQ(y.hash_function(), x.hash_function()); + BOOST_TEST_EQ(y.key_eq(), x.key_eq()); + BOOST_TEST(y.get_allocator() == x.get_allocator()); + } + + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); + auto reference_map = + boost::unordered_flat_map(values.begin(), values.end()); + raii::reset_counts(); + + { + map_type x(values.begin(), values.end(), 0, hasher(1), key_equal(2), + allocator_type{}); + + thread_runner( + values, [&x, &reference_map]( + boost::span s) { + (void)s; + map_type y(x); + + test_matches_reference(x, reference_map); + test_matches_reference(y, reference_map); + BOOST_TEST_EQ(y.size(), x.size()); + BOOST_TEST_EQ(y.hash_function(), x.hash_function()); + BOOST_TEST_EQ(y.key_eq(), x.key_eq()); + BOOST_TEST(y.get_allocator() == x.get_allocator()); + }); + } + } + + template + void copy_constructor_with_insertion(G gen, test::random_generator rg) + { + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); + auto reference_map = + boost::unordered_flat_map(values.begin(), values.end()); + raii::reset_counts(); + + { + map_type x(0, hasher(1), key_equal(2), allocator_type{}); + + auto f = [&x, &values] { + std::this_thread::sleep_for(std::chrono::milliseconds(75)); + for (auto const& val : values) { + x.insert(val); + } + }; + + std::thread t1(f); + std::thread t2(f); + + thread_runner( + values, [&x, &reference_map, &values, rg]( + boost::span s) { + (void)s; + map_type y(x); + BOOST_TEST_GT(y.size(), 0u); + BOOST_TEST_LE(y.size(), values.size()); + BOOST_TEST_EQ(y.hash_function(), x.hash_function()); + BOOST_TEST_EQ(y.key_eq(), x.key_eq()); + BOOST_TEST(y.get_allocator() == x.get_allocator()); + + x.visit_all([&reference_map, rg]( + typename map_type::value_type const& val) { + BOOST_TEST(reference_map.contains(val.first)); + if (rg == sequential) { + BOOST_TEST_EQ(val.second, reference_map.find(val.first)->second); + } + }); + }); + + t1.join(); + t2.join(); + } + } + +} // namespace + +// clang-format off +UNORDERED_TEST( + from_iterator_range, + ((value_type_generator)) + ((default_generator)(sequential)(limited_range))) + +UNORDERED_TEST( + copy_constructor, + ((value_type_generator)) + ((default_generator)(sequential)(limited_range))) + +UNORDERED_TEST( + copy_constructor_with_insertion, + ((value_type_generator)) + ((default_generator)(sequential)(limited_range))) +// clang-format on + +RUN_TESTS() diff --git a/test/cfoa/helpers.hpp b/test/cfoa/helpers.hpp index 8fb4fdd3..01f15ccf 100644 --- a/test/cfoa/helpers.hpp +++ b/test/cfoa/helpers.hpp @@ -36,6 +36,67 @@ struct transp_key_equal } }; +struct stateful_hash +{ + int x_ = -1; + + stateful_hash() = default; + stateful_hash(stateful_hash const&) = default; + stateful_hash(stateful_hash&& rhs) noexcept + { + auto tmp = x_; + x_ = rhs.x_; + rhs.x_ = tmp; + } + + stateful_hash(int const x) : x_{x} {} + + template std::size_t operator()(T const& t) const noexcept + { + std::size_t h = static_cast(x_); + boost::hash_combine(h, t); + return h; + } + + bool operator==(stateful_hash const& rhs) const { return x_ == rhs.x_; } + + friend std::ostream& operator<<(std::ostream& os, stateful_hash const& rhs) + { + os << "{ x_: " << rhs.x_ << " }"; + return os; + } +}; + +struct stateful_key_equal +{ + int x_ = -1; + + stateful_key_equal() = default; + stateful_key_equal(stateful_key_equal const&) = default; + stateful_key_equal(stateful_key_equal&& rhs) noexcept + { + auto tmp = x_; + x_ = rhs.x_; + rhs.x_ = tmp; + } + + stateful_key_equal(int const x) : x_{x} {} + + template bool operator()(T const& t, U const& u) const + { + return t == u; + } + + bool operator==(stateful_key_equal const& rhs) const { return x_ == rhs.x_; } + + friend std::ostream& operator<<( + std::ostream& os, stateful_key_equal const& rhs) + { + os << "{ x_: " << rhs.x_ << " }"; + return os; + } +}; + struct raii { static std::atomic default_constructor; @@ -226,4 +287,14 @@ template void thread_runner(std::vector& values, F f) } } +template +void test_matches_reference(X const& x, Y const& reference_map) +{ + using value_type = typename X::value_type; + BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) { + BOOST_TEST(reference_map.contains(kv.first)); + BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second); + })); +} + #endif // BOOST_UNORDERED_TEST_CFOA_HELPERS_HPP \ No newline at end of file