diff --git a/include/boost/unordered/detail/implementation.hpp b/include/boost/unordered/detail/implementation.hpp index 4d411b31..b084bbc0 100644 --- a/include/boost/unordered/detail/implementation.hpp +++ b/include/boost/unordered/detail/implementation.hpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -722,6 +723,21 @@ namespace boost { #endif +//////////////////////////////////////////////////////////////////////////// +// Type checkers used for the transparent member functions added by C++20 and up + + template + struct is_transparent : public false_type + { + }; + + template + struct is_transparent::type> + : public true_type + { + }; + //////////////////////////////////////////////////////////////////////////// // Explicitly call a destructor diff --git a/include/boost/unordered/unordered_map.hpp b/include/boost/unordered/unordered_map.hpp index a9387ef3..3535fc7e 100644 --- a/include/boost/unordered/unordered_map.hpp +++ b/include/boost/unordered/unordered_map.hpp @@ -757,6 +757,22 @@ namespace boost { size_type count(const key_type&) const; + template + typename boost::enable_if_c::value && + detail::is_transparent::value, + size_type>::type + count(const Key& k) const + { + std::size_t const key_hash = + table::policy::apply_hash(this->hash_function(), k); + + P const& eq = this->key_eq(); + + node_pointer p = table_.find_node_impl(key_hash, k, eq); + + return (p ? 1 : 0); + } + std::pair equal_range(const key_type&); std::pair equal_range( const key_type&) const; diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index e87886aa..e91cfc98 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -65,6 +65,7 @@ test-suite unordered [ run unordered/detail_tests.cpp ] [ run unordered/deduction_tests.cpp ] [ run unordered/scoped_allocator.cpp /boost/filesystem//boost_filesystem ] + [ run unordered/transparent_tests.cpp ] [ run unordered/compile_set.cpp : : : BOOST_UNORDERED_USE_MOVE diff --git a/test/unordered/transparent_tests.cpp b/test/unordered/transparent_tests.cpp new file mode 100644 index 00000000..a0289c94 --- /dev/null +++ b/test/unordered/transparent_tests.cpp @@ -0,0 +1,170 @@ +// Copyright 2021 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) + +// clang-format off +#include "../helpers/prefix.hpp" +#include +#include +#include "../helpers/postfix.hpp" +// clang-format on + +#include "../helpers/test.hpp" + +#include + +struct key +{ + int x_; + static int count_; + + key(int x) : x_(x) { ++count_; } + key(key const& k) : x_(k.x_) { ++count_; } +}; + +int key::count_; + +struct transparent_hasher +{ + typedef void is_transparent; + + std::size_t operator()(key const& k) const + { + return boost::hash()(k.x_); + } + + std::size_t operator()(int const k) const { return boost::hash()(k); } +}; + +struct transparent_key_equal +{ + typedef void is_transparent; + + static bool was_called_; + + bool operator()(key const& k1, key const& k2) const { return k1.x_ == k2.x_; } + bool operator()(int const x, key const& k1) const + { + was_called_ = true; + return k1.x_ == x; + } +}; + +bool transparent_key_equal::was_called_; + +struct hasher +{ + std::size_t operator()(key const& k) const + { + return boost::hash()(k.x_); + } +}; + +struct key_equal +{ + static bool was_called_; + + bool operator()(key const& k1, key const& k2) const { return k1.x_ == k2.x_; } + bool operator()(int const x, key const& k1) const + { + was_called_ = true; + return k1.x_ == x; + } +}; + +bool key_equal::was_called_; + +void count_reset() +{ + key::count_ = 0; + transparent_key_equal::was_called_ = false; + key_equal::was_called_ = false; +} + +template void test_transparent_count() +{ + count_reset(); + + UnorderedMap map; + + // initial `key(0)` expression increases the count + // then copying into the `unordered_map` increments the count again thus we + // have 2 + // + map[key(0)] = 1337; + BOOST_TEST(key::count_ == 2); + + // now the number of `key` objects created should be a constant and never + // touched again + // + std::size_t count = 0; + count = map.count(0); + + BOOST_TEST(count == 1); + BOOST_TEST(key::count_ == 2); + BOOST_TEST(map.key_eq().was_called_); + + count = map.count(1); + + BOOST_TEST(count == 0); + BOOST_TEST(key::count_ == 2); + + count = map.count(key(0)); + BOOST_TEST(count == 1); + BOOST_TEST(key::count_ == 3); +} + +template void test_non_transparent_count() +{ + count_reset(); + + UnorderedMap map; + + // initial `key(0)` expression increases the count + // then copying into the `unordered_map` increments the count again thus we + // have 2 + // + map[key(0)] = 1337; + BOOST_TEST(key::count_ == 2); + + // rely on the implicit constructor here to spawn a new object which + // increases the count + // + std::size_t count = 0; + count = map.count(0); + + BOOST_TEST(count == 1); + BOOST_TEST(key::count_ == 3); + BOOST_TEST(!map.key_eq().was_called_); + + count = map.count(1); + + BOOST_TEST(count == 0); + BOOST_TEST(key::count_ == 4); + + count = map.count(key(0)); + BOOST_TEST(count == 1); + BOOST_TEST(key::count_ == 5); +} + +UNORDERED_AUTO_TEST (unordered_map_transparent_count) { + test_transparent_count >(); + + // non-transparent Hash, non-transparent KeyEqual + // + test_non_transparent_count< + boost::unordered_map >(); + + // transparent Hash, non-transparent KeyEqual + // + test_non_transparent_count< + boost::unordered_map >(); + + // non-transparent Hash, transparent KeyEqual + // + test_non_transparent_count< + boost::unordered_map >(); +} + +RUN_TESTS()