Merge commit 'f1087e81ecdca5a59ba5ffca684c955c5b38f7c2' as 'third_party/unordered_dense'

This commit is contained in:
Siarhei Fedartsou
2024-05-30 19:06:16 +02:00
2383 changed files with 16243 additions and 0 deletions
@@ -0,0 +1,84 @@
#include <ankerl/unordered_dense.h>
#define ENABLE_LOG_LINE
#include <app/print.h>
#include <app/doctest.h>
#include <type_traits> // for remove_reference, remove_referen...
#include <utility> // for move
#include <vector> // for vector
TEST_CASE_MAP("assign_to_moved", int, int) {
auto a = map_t();
a[1] = 2;
auto moved = std::move(a);
REQUIRE(moved.size() == 1U);
auto c = map_t();
c[3] = 4;
// assign to a moved map
a = c;
}
TEST_CASE_MAP("move_to_moved", int, int) {
auto a = map_t();
a[1] = 2;
auto moved = std::move(a);
auto c = map_t();
c[3] = 4;
// assign to a moved map
a = std::move(c);
a[5] = 6;
moved[6] = 7;
REQUIRE(moved[6] == 7);
}
TEST_CASE_MAP("swap", int, int) {
{
auto b = map_t();
{
auto a = map_t();
b[1] = 2;
a.swap(b);
REQUIRE(a.end() != a.find(1));
REQUIRE(b.end() == b.find(1));
}
REQUIRE(b.end() == b.find(1));
b[2] = 3;
REQUIRE(b.end() != b.find(2));
REQUIRE(b.size() == 1);
}
{
auto a = map_t();
{
auto b = map_t();
b[1] = 2;
a.swap(b);
REQUIRE(a.end() != a.find(1));
REQUIRE(b.end() == b.find(1));
}
REQUIRE(a.end() != a.find(1));
a[2] = 3;
REQUIRE(a.end() != a.find(2));
REQUIRE(a.size() == 2);
}
{
auto a = map_t();
{
auto b = map_t();
a.swap(b);
REQUIRE(a.end() == a.find(1));
REQUIRE(b.end() == b.find(1));
}
REQUIRE(a.end() == a.find(1));
}
}
@@ -0,0 +1,167 @@
#include <ankerl/unordered_dense.h>
#define ENABLE_LOG_LINE
#include <app/doctest.h>
#include <app/print.h>
#include <cstdint> // for uint64_t
#include <utility> // for pair
#include <vector> // for vector
TEST_CASE_MAP("assignment_combinations_1", uint64_t, uint64_t) {
map_t a;
map_t b;
b = a;
REQUIRE(b == a);
}
TEST_CASE_MAP("assignment_combinations_2", uint64_t, uint64_t) {
map_t a;
map_t const& a_const = a;
map_t b;
a[123] = 321;
b = a;
REQUIRE(a.find(123)->second == 321);
REQUIRE(a_const.find(123)->second == 321);
REQUIRE(b.find(123)->second == 321);
a[123] = 111;
REQUIRE(a.find(123)->second == 111);
REQUIRE(a_const.find(123)->second == 111);
REQUIRE(b.find(123)->second == 321);
b[123] = 222;
REQUIRE(a.find(123)->second == 111);
REQUIRE(a_const.find(123)->second == 111);
REQUIRE(b.find(123)->second == 222);
}
TEST_CASE_MAP("assignment_combinations_3", uint64_t, uint64_t) {
map_t a;
map_t b;
a[123] = 321;
a.clear();
b = a;
REQUIRE(a.size() == 0);
REQUIRE(b.size() == 0);
}
TEST_CASE_MAP("assignment_combinations_4", uint64_t, uint64_t) {
map_t a;
map_t b;
b[123] = 321;
b = a;
REQUIRE(a.size() == 0);
REQUIRE(b.size() == 0);
}
TEST_CASE_MAP("assignment_combinations_5", uint64_t, uint64_t) {
map_t a;
map_t b;
b[123] = 321;
b.clear();
b = a;
REQUIRE(a.size() == 0);
REQUIRE(b.size() == 0);
}
TEST_CASE_MAP("assignment_combinations_6", uint64_t, uint64_t) {
map_t a;
a[1] = 2;
map_t b;
b[3] = 4;
b = a;
REQUIRE(a.size() == 1);
REQUIRE(b.size() == 1);
REQUIRE(b.find(1)->second == 2);
a[1] = 123;
REQUIRE(a.size() == 1);
REQUIRE(b.size() == 1);
REQUIRE(b.find(1)->second == 2);
}
TEST_CASE_MAP("assignment_combinations_7", uint64_t, uint64_t) {
map_t a;
a[1] = 2;
a.clear();
map_t b;
REQUIRE(a == b);
b[3] = 4;
REQUIRE(a != b);
b = a;
REQUIRE(a == b);
}
TEST_CASE_MAP("assignment_combinations_7", uint64_t, uint64_t) {
map_t a;
a[1] = 2;
map_t b;
REQUIRE(a != b);
b[3] = 4;
b.clear();
REQUIRE(a != b);
b = a;
REQUIRE(a == b);
}
TEST_CASE_MAP("assignment_combinations_8", uint64_t, uint64_t) {
map_t a;
a[1] = 2;
a.clear();
map_t b;
b[3] = 4;
REQUIRE(a != b);
b.clear();
REQUIRE(a == b);
b = a;
REQUIRE(a == b);
}
TEST_CASE_MAP("assignment_combinations_9", uint64_t, uint64_t) {
map_t a;
a[1] = 2;
// self assignment should work too
map_t* b = &a;
a = *b;
REQUIRE(a == a);
REQUIRE(a.size() == 1);
REQUIRE(a.find(1) != a.end());
}
TEST_CASE_MAP("assignment_combinations_10", uint64_t, uint64_t) {
map_t a;
a[1] = 2;
map_t b;
b[2] = 1;
// maps have the same number of elements, but are not equal.
REQUIRE(!(a == b));
REQUIRE(a != b);
REQUIRE(b != a);
REQUIRE(!(a == b));
REQUIRE(!(b == a));
map_t c;
c[1] = 3;
REQUIRE(a != c);
REQUIRE(c != a);
REQUIRE(!(a == c));
REQUIRE(!(c == a));
b.clear();
REQUIRE(a != b);
REQUIRE(b != a);
REQUIRE(!(a == b));
REQUIRE(!(b == a));
map_t empty;
REQUIRE(b == empty);
REQUIRE(empty == b);
REQUIRE(!(b != empty));
REQUIRE(!(empty != b));
}
+32
View File
@@ -0,0 +1,32 @@
#include <ankerl/unordered_dense.h>
#include <app/doctest.h>
#include <stdexcept> // for out_of_range
TEST_CASE_MAP("at", int, int) {
map_t map;
map_t const& cmap = map;
// NOLINTNEXTLINE(llvm-else-after-return,readability-else-after-return)
REQUIRE_THROWS_AS(map.at(123), std::out_of_range);
// NOLINTNEXTLINE(llvm-else-after-return,readability-else-after-return)
REQUIRE_THROWS_AS(static_cast<void>(map.at(0)), std::out_of_range);
// NOLINTNEXTLINE(llvm-else-after-return,readability-else-after-return)
REQUIRE_THROWS_AS(static_cast<void>(cmap.at(123)), std::out_of_range);
// NOLINTNEXTLINE(llvm-else-after-return,readability-else-after-return)
REQUIRE_THROWS_AS(static_cast<void>(cmap.at(0)), std::out_of_range);
map[123] = 333;
REQUIRE(map.at(123) == 333);
REQUIRE(cmap.at(123) == 333);
// NOLINTNEXTLINE(llvm-else-after-return,readability-else-after-return)
REQUIRE_THROWS_AS(static_cast<void>(map.at(0)), std::out_of_range);
// NOLINTNEXTLINE(llvm-else-after-return,readability-else-after-return)
REQUIRE_THROWS_AS(static_cast<void>(cmap.at(0)), std::out_of_range);
}
+82
View File
@@ -0,0 +1,82 @@
#include <ankerl/unordered_dense.h>
#include <app/counter.h>
#include <app/doctest.h>
#include <fmt/format.h>
#include <limits>
#include <stdexcept> // for out_of_range
using map_default_t = ankerl::unordered_dense::map<std::string, size_t>;
// big bucket type allows 2^64 elements, but has more memory & CPU overhead.
using map_big_t = ankerl::unordered_dense::map<std::string,
size_t,
ankerl::unordered_dense::hash<std::string>,
std::equal_to<std::string>,
std::allocator<std::pair<std::string, size_t>>,
ankerl::unordered_dense::bucket_type::big>;
static_assert(sizeof(map_default_t::bucket_type) == 8U);
static_assert(sizeof(map_big_t::bucket_type) == sizeof(size_t) + 4U);
static_assert(map_default_t::max_size() == map_default_t::max_bucket_count());
#if SIZE_MAX == UINT32_MAX
static_assert(map_default_t::max_size() == uint64_t{1} << 31U);
static_assert(map_big_t::max_size() == uint64_t{1} << 31U);
#else
static_assert(map_default_t::max_size() == uint64_t{1} << 32U);
static_assert(map_big_t::max_size() == uint64_t{1} << 63U);
#endif
struct bucket_micro {
static constexpr uint8_t dist_inc = 1U << 1U; // 1 bits for fingerprint
static constexpr uint8_t fingerprint_mask = dist_inc - 1; // 11 bit = 2048 positions for distance
uint8_t m_dist_and_fingerprint;
uint8_t m_value_idx;
};
TYPE_TO_STRING_MAP(counter::obj,
counter::obj,
ankerl::unordered_dense::hash<counter::obj>,
std::equal_to<counter::obj>,
std::allocator<std::pair<counter::obj, counter::obj>>,
bucket_micro);
TEST_CASE_MAP("bucket_micro",
counter::obj,
counter::obj,
ankerl::unordered_dense::hash<counter::obj>,
std::equal_to<counter::obj>,
std::allocator<std::pair<counter::obj, counter::obj>>,
bucket_micro) {
counter counts;
INFO(counts);
auto map = map_t();
INFO("map_t::max_size()=" << map_t::max_size());
for (size_t i = 0; i < map_t::max_size(); ++i) {
if (i == 255) {
INFO("i=" << i);
}
auto const r = map.try_emplace({i, counts}, i, counts);
REQUIRE(r.second);
auto it = map.find({0, counts});
REQUIRE(it != map.end());
}
// NOLINTNEXTLINE(llvm-else-after-return,readability-else-after-return)
REQUIRE_THROWS_AS(map.try_emplace({map_t::max_size(), counts}, map_t::max_size(), counts), std::overflow_error);
// check that all elements are there
REQUIRE(map.size() == map_t::max_size());
for (size_t i = 0; i < map_t::max_size(); ++i) {
INFO(i);
auto it = map.find({i, counts});
REQUIRE(it != map.end());
REQUIRE(it->first.get() == i);
REQUIRE(it->second.get() == i);
}
}
+20
View File
@@ -0,0 +1,20 @@
#include <ankerl/unordered_dense.h>
#include <app/doctest.h>
#include <cstdint> // for uint64_t
TEST_CASE_MAP("contains", uint64_t, uint64_t) {
static_assert(std::is_same_v<bool, decltype(map_t{}.contains(1))>);
auto map = map_t();
REQUIRE(!map.contains(0));
REQUIRE(!map.contains(123));
map[123];
REQUIRE(!map.contains(0));
REQUIRE(map.contains(123));
map.clear();
REQUIRE(!map.contains(0));
REQUIRE(!map.contains(123));
}
@@ -0,0 +1,81 @@
#include <ankerl/unordered_dense.h>
#define ENABLE_LOG_LINE
#include <app/doctest.h>
#include <app/print.h>
#include <utility> // for pair
#include <vector> // for vector
// creates a map with some data in it
template <class M>
[[nodiscard]] auto create_map(int num_elements) -> M {
M m;
for (int i = 0; i < num_elements; ++i) {
m[static_cast<typename M::key_type>((i + 123) * 7)] = static_cast<typename M::mapped_type>(i);
}
return m;
}
TEST_CASE_MAP("copy_and_assign_maps_1", int, int) {
auto a = create_map<map_t>(15);
}
TEST_CASE_MAP("copy_and_assign_maps_2", int, int) {
auto a = create_map<map_t>(100);
}
TEST_CASE_MAP("copy_and_assign_maps_3", int, int) {
auto a = create_map<map_t>(1);
// NOLINTNEXTLINE(performance-unnecessary-copy-initialization)
auto b = a;
REQUIRE(a == b);
}
TEST_CASE_MAP("copy_and_assign_maps_4", int, int) {
map_t a;
REQUIRE(a.empty());
a.clear();
REQUIRE(a.empty());
}
TEST_CASE_MAP("copy_and_assign_maps_5", int, int) {
auto a = create_map<map_t>(100);
// NOLINTNEXTLINE(performance-unnecessary-copy-initialization)
auto b = a;
REQUIRE(b == a);
}
TEST_CASE_MAP("copy_and_assign_maps_6", int, int) {
map_t a;
a[123] = 321;
a.clear();
auto const maps = std::vector<map_t>(10, a);
for (auto const& map : maps) {
REQUIRE(map.empty());
}
}
TEST_CASE_MAP("copy_and_assign_maps_7", int, int) {
auto const maps = std::vector<map_t>(10);
REQUIRE(maps.size() == 10U);
}
TEST_CASE_MAP("copy_and_assign_maps_8", int, int) {
map_t a;
auto const maps = std::vector<map_t>(12, a);
REQUIRE(maps.size() == 12U);
}
TEST_CASE_MAP("copy_and_assign_maps_9", int, int) {
map_t a;
a[123] = 321;
auto const maps = std::vector<map_t>(10, a);
a[123] = 1;
for (auto const& map : maps) {
REQUIRE(map.size() == 1);
REQUIRE(map.find(123)->second == 321);
}
}
@@ -0,0 +1,28 @@
#include <ankerl/unordered_dense.h>
#include <app/doctest.h>
#include <string>
TEST_CASE_MAP("copyassignment", std::string, std::string) {
auto map = map_t();
auto tmp = map_t();
map.emplace("a", "b");
map = tmp;
map.emplace("c", "d");
REQUIRE(map.size() == 1);
REQUIRE(map["c"] == "d");
REQUIRE(map.size() == 1);
REQUIRE(tmp.size() == 0);
map["e"] = "f";
REQUIRE(map.size() == 2);
REQUIRE(tmp.size() == 0);
tmp["g"] = "h";
REQUIRE(map.size() == 2);
REQUIRE(tmp.size() == 1);
}
+12
View File
@@ -0,0 +1,12 @@
#include <ankerl/unordered_dense.h>
#include <app/doctest.h>
TEST_CASE_MAP("count", int, int) {
auto map = map_t();
REQUIRE(map.count(123) == 0);
REQUIRE(map.count(0) == 0);
map[123];
REQUIRE(map.count(123) == 1);
REQUIRE(map.count(0) == 0);
}
+89
View File
@@ -0,0 +1,89 @@
#include <ankerl/unordered_dense.h>
#include <app/counter.h>
#include <app/doctest.h>
#include <cstddef> // for size_t
#include <utility> // for pair
// very minimal input iterator
// https://en.cppreference.com/w/cpp/named_req/InputIterator#Concept
class it {
std::pair<counter::obj, counter::obj> m_kv;
public:
it(size_t val, counter& counts)
: m_kv({val, counts}, {val, counts}) {}
auto operator++() -> it& {
++m_kv.first.get();
++m_kv.second.get();
return *this;
}
auto operator*() -> std::pair<counter::obj, counter::obj> const& {
return m_kv;
}
auto operator!=(it const& other) const -> bool {
return other.m_kv.first.get() != m_kv.first.get() || other.m_kv.second.get() != m_kv.second.get();
}
};
TEST_CASE_MAP("ctors_map", counter::obj, counter::obj) {
using alloc_t = typename map_t::allocator_type;
using hash_t = typename map_t::hasher;
using key_eq_t = typename map_t::key_equal;
auto counts = counter();
INFO(counts);
{ auto m = map_t{}; }
{ auto m = map_t{0, alloc_t{}}; }
{ auto m = map_t{0, hash_t{}, alloc_t{}}; }
{ auto m = map_t{alloc_t{}}; }
REQUIRE(counts.dtor() == 0);
{
auto begin_it = it{size_t{0}, counts};
auto end_it = it{size_t{10}, counts};
auto m = map_t{begin_it, end_it};
REQUIRE(m.size() == 10);
}
{
auto begin_it = it{size_t{0}, counts};
auto end_it = it{size_t{10}, counts};
auto m = map_t{begin_it, end_it, 0, alloc_t{}};
REQUIRE(m.size() == 10);
}
{
auto begin_it = it{size_t{0}, counts};
auto end_it = it{size_t{10}, counts};
auto m = map_t{begin_it, end_it, 0, hash_t{}, alloc_t{}};
REQUIRE(m.size() == 10);
}
{
auto begin_it = it{size_t{0}, counts};
auto end_it = it{size_t{10}, counts};
auto m = map_t{begin_it, end_it, 0, hash_t{}, key_eq_t{}};
REQUIRE(m.size() == 10);
}
}
TEST_CASE_MAP("ctor_bucket_count_map", counter::obj, counter::obj) {
{
auto m = map_t{};
// depends on initial bucket count, could also be 0
// REQUIRE(m.bucket_count() == 2U);
}
{
auto m = map_t{150U};
REQUIRE(m.bucket_count() == 256U);
}
}
TEST_CASE_SET("ctor_bucket_count_set", int) {
auto m = ankerl::unordered_dense::set<int>{{1, 2, 3, 4}, 300U};
REQUIRE(m.size() == 4U);
REQUIRE(m.bucket_count() == 512U);
}
@@ -0,0 +1,34 @@
#include <ankerl/unordered_dense.h>
#include <app/doctest.h>
#include <deque>
static_assert(
ankerl::unordered_dense::detail::is_detected_v<ankerl::unordered_dense::detail::detect_iterator, std::deque<int>>);
static_assert(
!ankerl::unordered_dense::detail::is_detected_v<ankerl::unordered_dense::detail::detect_iterator, std::allocator<int>>);
TEST_CASE_MAP("custom_container",
int,
std::string,
ankerl::unordered_dense::hash<int>,
std::equal_to<int>,
std::deque<std::pair<int, std::string>>) {
auto map = map_t();
for (int i = 0; i < 10; ++i) {
map[i] = std::to_string(i);
}
REQUIRE(std::is_same_v<std::deque<std::pair<int, std::string>>, typename map_t::value_container_type>);
std::deque<std::pair<int, std::string>> const container = std::move(map).extract();
auto m2 = map_t();
// we allow use-after-move
m2 = map; // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved)
auto map2 = map;
std::swap(map2, map);
}
@@ -0,0 +1,78 @@
#if ANKERL_UNORDERED_DENSE_HAS_BOOST
# if __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wold-style-cast"
# endif
# include <ankerl/unordered_dense.h>
# include <app/doctest.h>
# include <boost/container/vector.hpp>
# include <boost/interprocess/allocators/allocator.hpp>
# include <boost/interprocess/allocators/node_allocator.hpp>
# include <boost/interprocess/containers/vector.hpp>
# include <boost/interprocess/managed_shared_memory.hpp>
# include <deque>
// Alias an STL-like allocator of ints that allocates ints from the segment
using shmem_allocator =
boost::interprocess::allocator<std::pair<int, std::string>, boost::interprocess::managed_shared_memory::segment_manager>;
using shmem_vector = boost::interprocess::vector<std::pair<int, std::string>, shmem_allocator>;
// Remove shared memory on construction and destruction
struct shm_remove {
shm_remove() {
boost::interprocess::shared_memory_object::remove("MySharedMemory");
}
~shm_remove() {
boost::interprocess::shared_memory_object::remove("MySharedMemory");
}
shm_remove(shm_remove const&) = delete;
shm_remove(shm_remove&&) = delete;
auto operator=(shm_remove const&) -> shm_remove = delete;
auto operator=(shm_remove&&) -> shm_remove = delete;
};
TYPE_TO_STRING_MAP(int, std::string, ankerl::unordered_dense::hash<int>, std::equal_to<int>, shmem_vector);
// See https://www.boost.org/doc/libs/1_80_0/doc/html/interprocess/allocators_containers.html
TEST_CASE_TEMPLATE(
"boost_container_vector",
map_t,
ankerl::unordered_dense::map<int, std::string, ankerl::unordered_dense::hash<int>, std::equal_to<int>, shmem_vector>,
ankerl::unordered_dense::
segmented_map<int, std::string, ankerl::unordered_dense::hash<int>, std::equal_to<int>, shmem_allocator>) {
auto remover = shm_remove();
// Create shared memory
auto segment = boost::interprocess::managed_shared_memory(boost::interprocess::create_only, "MySharedMemory", 1024 * 1024);
auto map = map_t{shmem_allocator{segment.get_segment_manager()}};
int total = 10000;
for (int i = 0; i < total; ++i) {
map.try_emplace(i, std::to_string(i));
}
REQUIRE(map.size() == static_cast<size_t>(total));
for (int i = 0; i < total; ++i) {
auto it = map.find(i);
REQUIRE(it != map.end());
REQUIRE(it->first == i);
REQUIRE(it->second == std::to_string(i));
}
map.erase(total + 123);
REQUIRE(map.size() == static_cast<size_t>(total));
map.erase(29);
REQUIRE(map.size() == static_cast<size_t>(total - 1));
map.emplace(std::pair<int, std::string>(9999999, "hello"));
REQUIRE(map.size() == static_cast<size_t>(total));
}
#endif // ANKERL_UNORDERED_DENSE_HAS_BOOST
+94
View File
@@ -0,0 +1,94 @@
#include <ankerl/unordered_dense.h>
#include <app/doctest.h>
#include <type_traits>
namespace {
struct id {
uint64_t value{}; // NOLINT
auto operator==(id const& other) const -> bool {
return value == other.value;
}
};
struct custom_hash_simple {
[[nodiscard]] auto operator()(id const& x) const noexcept -> uint64_t {
return x.value;
}
};
struct custom_hash_avalanching {
using is_avalanching = void;
auto operator()(id const& x) const noexcept -> uint64_t {
return ankerl::unordered_dense::detail::wyhash::hash(x.value);
}
};
struct point {
int x{}; // NOLINT
int y{}; // NOLINT
auto operator==(point const& other) const -> bool {
return x == other.x && y == other.y;
}
};
struct custom_hash_unique_object_representation {
using is_avalanching = void;
[[nodiscard]] auto operator()(point const& f) const noexcept -> uint64_t {
static_assert(std::has_unique_object_representations_v<point>);
return ankerl::unordered_dense::detail::wyhash::hash(&f, sizeof(f));
}
};
} // namespace
template <>
struct ankerl::unordered_dense::hash<id> {
using is_avalanching = void;
[[nodiscard]] auto operator()(id const& x) const noexcept -> uint64_t {
return detail::wyhash::hash(x.value);
}
};
TYPE_TO_STRING_SET(id);
TYPE_TO_STRING_SET(id, custom_hash_simple);
TYPE_TO_STRING_SET(id, custom_hash_avalanching);
TYPE_TO_STRING_SET(point, custom_hash_unique_object_representation);
TEST_CASE_SET("custom_hash_simple", id, custom_hash_simple) {
auto set = set_t();
set.insert(id{124});
}
TEST_CASE_SET("custom_hash_avalanching", id, custom_hash_avalanching) {
auto set = set_t();
set.insert(id{124});
}
TEST_CASE_SET("custom_hash_unique", point, custom_hash_unique_object_representation) {
auto set = set_t();
set.insert(point{123, 321});
}
TEST_CASE_SET("custom_hash_default", id) {
auto set = set_t();
set.insert(id{124});
}
static_assert(
!ankerl::unordered_dense::detail::is_detected_v<ankerl::unordered_dense::detail::detect_avalanching, custom_hash_simple>);
static_assert(ankerl::unordered_dense::detail::is_detected_v<ankerl::unordered_dense::detail::detect_avalanching,
custom_hash_avalanching>);
static_assert(ankerl::unordered_dense::detail::is_detected_v<ankerl::unordered_dense::detail::detect_avalanching,
custom_hash_unique_object_representation>);
static_assert(!ankerl::unordered_dense::detail::is_detected_v<ankerl::unordered_dense::detail::detect_avalanching,
ankerl::unordered_dense::hash<point>>);
@@ -0,0 +1,7 @@
#include <doctest.h> // for TestCase, skip, TEST_CASE, test_...
TEST_CASE("deduction_guide") {
// TODO(martinus) not yet possible, only in c++20. See
// https://stackoverflow.com/a/41008415
// https://en.cppreference.com/w/cpp/language/class_template_argument_deduction
}
+27
View File
@@ -0,0 +1,27 @@
#include <ankerl/unordered_dense.h>
#include <app/doctest.h>
#include <cstddef> // for size_t
#include <vector> // for vector
// struct that provides both hash and equals operator
struct hash_with_equal {
auto operator()(int x) const -> size_t {
return static_cast<size_t>(x);
}
auto operator()(int a, int b) const -> bool {
return a == b;
}
};
TYPE_TO_STRING_MAP(int, int, hash_with_equal, hash_with_equal);
// make sure the map works with the same type (check that it handles the diamond problem)
TEST_CASE_MAP("diamond_problem", int, int, hash_with_equal, hash_with_equal) {
auto map = map_t();
map[1] = 2;
REQUIRE(map.size() == 1);
REQUIRE(map.find(1) != map.end());
}
+44
View File
@@ -0,0 +1,44 @@
#include <ankerl/unordered_dense.h>
#include <app/doctest.h>
TEST_CASE_MAP("empty_map_operations", int, int) {
map_t m;
REQUIRE(m.end() == m.find(123));
REQUIRE(m.end() == m.begin());
m[32];
REQUIRE(m.end() != m.begin());
REQUIRE(m.end() == m.find(123));
REQUIRE(m.end() != m.find(32));
m = map_t();
REQUIRE(m.end() == m.begin());
REQUIRE(m.end() == m.find(123));
REQUIRE(m.end() == m.find(32));
map_t m2(m);
REQUIRE(m2.end() == m2.begin());
REQUIRE(m2.end() == m2.find(123));
REQUIRE(m2.end() == m2.find(32));
m2[32];
REQUIRE(m2.end() != m2.begin());
REQUIRE(m2.end() == m2.find(123));
REQUIRE(m2.end() != m2.find(32));
map_t empty;
map_t m3(empty);
REQUIRE(m3.end() == m3.begin());
REQUIRE(m3.end() == m3.find(123));
REQUIRE(m3.end() == m3.find(32));
m3[32];
REQUIRE(m3.end() != m3.begin());
REQUIRE(m3.end() == m3.find(123));
REQUIRE(m3.end() != m3.find(32));
map_t m4(std::move(empty));
REQUIRE(m4.count(123) == 0);
REQUIRE(m4.end() == m4.begin());
REQUIRE(m4.end() == m4.find(123));
REQUIRE(m4.end() == m4.find(32));
}
+34
View File
@@ -0,0 +1,34 @@
#include <ankerl/unordered_dense.h>
#include <app/doctest.h>
#include <type_traits> // for add_const_t
#include <utility> // for pair, as_const
#include <vector> // for vector
TEST_CASE_MAP("equal_range", int, int) {
auto map = map_t();
// auto map = std::unordered_map<int, int>();
auto range = map.equal_range(123);
REQUIRE(range.first == map.end());
REQUIRE(range.second == map.end());
map.try_emplace(1, 1);
range = map.equal_range(123);
REQUIRE(range.first == map.end());
REQUIRE(range.second == map.end());
int const x = 1;
auto const_range = std::as_const(map).equal_range(x);
REQUIRE(const_range.first == map.begin());
REQUIRE(const_range.second == map.end());
for (int i = 0; i < 100; ++i) {
map.try_emplace(i, i);
}
range = map.equal_range(50);
auto after_first = ++range.first;
REQUIRE(range.second == after_first);
REQUIRE(range.second != map.end());
}
+49
View File
@@ -0,0 +1,49 @@
#include <ankerl/unordered_dense.h>
#include <third-party/nanobench.h>
#include <app/doctest.h>
#include <algorithm> // for all_of
#include <cstddef> // for size_t
#include <cstdint> // for uint32_t
#include <unordered_set> // for unordered_set, operator!=
#include <vector> // for vector
template <typename A, typename B>
[[nodiscard]] auto is_eq(A const& a, B const& b) -> bool {
if (a.size() != b.size()) {
return false;
}
return std::all_of(a.begin(), a.end(), [&b](auto const& k) {
return b.end() != b.find(k);
});
}
TEST_CASE_SET("insert_erase_random", uint32_t) {
auto uds = set_t();
auto us = std::unordered_set<uint32_t>();
auto rng = ankerl::nanobench::Rng(123);
for (size_t i = 0; i < 10000; ++i) {
auto key = rng.bounded(1000);
uds.insert(key);
us.insert(key);
REQUIRE(uds.size() == us.size());
key = rng.bounded(1000);
REQUIRE(uds.erase(key) == us.erase(key));
REQUIRE(uds.size() == us.size());
}
REQUIRE(is_eq(uds, us));
auto k = *uds.begin();
uds.erase(k);
REQUIRE(!is_eq(uds, us));
us.erase(k);
REQUIRE(is_eq(uds, us));
}
TEST_CASE_MAP("erase", int, int) {
auto map = ankerl::unordered_dense::map<int, int>();
REQUIRE(0 == map.erase(123));
REQUIRE(0 == map.count(0));
}
+54
View File
@@ -0,0 +1,54 @@
#include <ankerl/unordered_dense.h>
#include <app/counter.h>
#include <app/doctest.h>
#include <cstddef> // for size_t
#include <cstdint> // for uint64_t
#include <utility> // for pair
TEST_CASE_SET("erase_if_set", counter::obj) {
auto counts = counter();
INFO(counts);
auto set = set_t();
for (size_t i = 0; i < 1000; ++i) {
set.emplace(i, counts);
}
REQUIRE(set.size() == 1000);
auto num_erased = std::erase_if(set, [](counter::obj const& obj) {
return 0 == obj.get() % 3;
});
REQUIRE(num_erased == 334);
REQUIRE(set.size() == 666);
for (size_t i = 0; i < 1000; ++i) {
if (0 == i % 3) {
REQUIRE(!set.contains({i, counts}));
} else {
REQUIRE(set.contains({i, counts}));
}
}
}
TEST_CASE_MAP("erase_if_map", uint64_t, uint64_t) {
auto map = map_t();
for (size_t i = 0; i < 1000; ++i) {
map.try_emplace(i, i);
}
REQUIRE(map.size() == 1000);
auto num_erased = std::erase_if(map, [](std::pair<uint64_t, uint64_t> const& x) {
return 0 == x.second % 2;
});
REQUIRE(num_erased == 500);
REQUIRE(map.size() == 500);
for (size_t i = 0; i < 1000; ++i) {
if (0 == i % 2) {
REQUIRE(!map.contains(i));
} else {
REQUIRE(map.contains(i));
}
}
}
+32
View File
@@ -0,0 +1,32 @@
#include <ankerl/unordered_dense.h>
#include <app/counter.h>
#include <app/doctest.h>
#include <third-party/nanobench.h>
#include <cstddef> // for size_t
#include <vector> // for vector
TEST_CASE_MAP("erase_range", counter::obj, counter::obj) {
int const num_elements = 10;
for (int first_pos = 0; first_pos <= num_elements; ++first_pos) {
for (int last_pos = first_pos; last_pos <= num_elements; ++last_pos) {
auto counts = counter();
INFO(counts);
auto map = map_t();
for (size_t i = 0; i < num_elements; ++i) {
auto key = i;
auto val = i * 1000;
map.try_emplace({key, counts}, val, counts);
}
REQUIRE(map.size() == num_elements);
auto it_ret = map.erase(map.cbegin() + first_pos, map.cbegin() + last_pos);
REQUIRE(map.size() == static_cast<size_t>(num_elements - (last_pos - first_pos)));
REQUIRE(it_ret == map.begin() + first_pos);
}
}
}
+33
View File
@@ -0,0 +1,33 @@
#include <ankerl/unordered_dense.h>
#include <app/doctest.h>
#include <unordered_map>
#include <vector>
struct texture {
int m_width;
int m_height;
void* m_data;
};
struct per_image {
std::unordered_map<void*, texture*> m_texture_index;
};
template <typename Map>
struct scene {
std::vector<per_image> m_per_image;
Map m_textures_per_key;
};
template <typename Map>
struct app_state {
scene<Map> m_scene;
};
TYPE_TO_STRING_MAP(void*, texture*);
TEST_CASE_MAP("unit_create_AppState_issue_97", void*, texture*) {
app_state<map_t> const app_state{};
}
+62
View File
@@ -0,0 +1,62 @@
#include <ankerl/unordered_dense.h>
#include <app/counter.h>
#include <app/doctest.h>
#include <fmt/format.h>
TEST_CASE_MAP("extract", counter::obj, counter::obj) {
auto counts = counter();
INFO(counts);
auto container = typename map_t::value_container_type();
{
auto map = map_t();
for (size_t i = 0; i < 100; ++i) {
map.try_emplace(counter::obj{i, counts}, i, counts);
}
container = std::move(map).extract();
}
REQUIRE(container.size() == 100U);
for (size_t i = 0; i < container.size(); ++i) {
REQUIRE(container[i].first.get() == i);
REQUIRE(container[i].second.get() == i);
}
}
TEST_CASE_MAP("extract_element", counter::obj, counter::obj) {
auto counts = counter();
INFO(counts);
counts("init");
auto map = map_t();
for (size_t i = 0; i < 100; ++i) {
map.try_emplace(counter::obj{i, counts}, i, counts);
}
// extract(key)
for (size_t i = 0; i < 20; ++i) {
auto query = counter::obj{i, counts};
counts("before remove 1");
auto opt = map.extract(query);
counts("after remove 1");
REQUIRE(opt);
REQUIRE(opt->first.get() == i);
REQUIRE(opt->second.get() == i);
}
REQUIRE(map.size() == 80);
// extract iterator
for (size_t i = 20; i < 100; ++i) {
auto query = counter::obj{i, counts};
auto it = map.find(query);
REQUIRE(it != map.end());
auto opt = map.extract(it);
REQUIRE(opt.first.get() == i);
REQUIRE(opt.second.get() == i);
}
REQUIRE(map.empty());
}
+121
View File
@@ -0,0 +1,121 @@
#include <ankerl/unordered_dense.h>
#include <app/doctest.h>
#include <fuzz/provider.h>
#include <fuzz/run.h>
#include <unordered_map>
template <typename Map>
void do_fuzz_api(fuzz::provider p) {
auto counts = counter();
auto map = Map();
p.repeat_oneof(
[&] {
auto key = p.integral<size_t>();
auto it = map.try_emplace(counter::obj(key, counts), counter::obj(key, counts)).first;
REQUIRE(it != map.end());
REQUIRE(it->first.get() == key);
},
[&] {
auto key = p.integral<size_t>();
map.emplace(std::piecewise_construct, std::forward_as_tuple(key, counts), std::forward_as_tuple(key + 77, counts));
},
[&] {
auto key = p.integral<size_t>();
map[counter::obj(key, counts)] = counter::obj(key + 123, counts);
},
[&] {
auto key = p.integral<size_t>();
map.insert(std::pair<counter::obj, counter::obj>(counter::obj(key, counts), counter::obj(key, counts)));
},
[&] {
auto key = p.integral<size_t>();
map.insert_or_assign(counter::obj(key, counts), counter::obj(key + 1, counts));
},
[&] {
auto key = p.integral<size_t>();
map.erase(counter::obj(key, counts));
},
[&] {
map = Map{};
},
[&] {
auto m = Map{};
m.swap(map);
},
[&] {
map.clear();
},
[&] {
auto s = p.bounded<size_t>(1025);
map.rehash(s);
},
[&] {
auto s = p.bounded<size_t>(1025);
map.reserve(s);
},
[&] {
auto key = p.integral<size_t>();
auto it = map.find(counter::obj(key, counts));
auto d = std::distance(map.begin(), it);
REQUIRE(0 <= d);
REQUIRE(d <= static_cast<std::ptrdiff_t>(map.size()));
},
[&] {
if (!map.empty()) {
auto idx = p.bounded(static_cast<int>(map.size()));
auto it = map.cbegin() + idx;
auto const& key = it->first;
auto found_it = map.find(key);
REQUIRE(it == found_it);
}
},
[&] {
if (!map.empty()) {
auto it = map.begin() + p.bounded(static_cast<int>(map.size()));
map.erase(it);
}
},
[&] {
auto tmp = Map();
std::swap(tmp, map);
},
[&] {
map = std::initializer_list<std::pair<counter::obj, counter::obj>>{
{{1, counts}, {2, counts}},
{{3, counts}, {4, counts}},
{{5, counts}, {6, counts}},
};
REQUIRE(map.size() == 3);
},
[&] {
auto first_idx = 0;
auto last_idx = 0;
if (!map.empty()) {
first_idx = p.bounded(static_cast<int>(map.size()));
last_idx = p.bounded(static_cast<int>(map.size()));
if (first_idx > last_idx) {
std::swap(first_idx, last_idx);
}
}
map.erase(map.cbegin() + first_idx, map.cbegin() + last_idx);
},
[&] {
map.~Map();
counts.check_all_done();
new (&map) Map();
},
[&] {
std::erase_if(map, [&](typename Map::value_type const& /*v*/) {
return p.integral<bool>();
});
});
}
TEST_CASE("fuzz_api" * doctest::test_suite("fuzz")) {
fuzz::run([](fuzz::provider p) {
do_fuzz_api<ankerl::unordered_dense::map<counter::obj, counter::obj>>(p.copy());
do_fuzz_api<ankerl::unordered_dense::segmented_map<counter::obj, counter::obj>>(p.copy());
do_fuzz_api<deque_map<counter::obj, counter::obj>>(p.copy());
});
}
@@ -0,0 +1,52 @@
#include <ankerl/unordered_dense.h>
#include <fuzz/run.h>
#include <app/doctest.h>
#include <unordered_map>
namespace {
// Using DummyHash to make it easier for the fuzzer
struct dummy_hash {
using is_avalanching = void;
auto operator()(uint64_t x) const noexcept -> size_t {
return static_cast<size_t>(x);
}
};
template <typename Map>
void insert_erase(fuzz::provider p) {
auto ank = Map();
auto ref = std::unordered_map<uint64_t, uint64_t>();
auto c = uint64_t();
while (p.has_remaining_bytes()) {
auto key = p.integral<uint64_t>();
ank[key] = c;
ref[key] = c;
++c;
key = p.integral<uint64_t>();
REQUIRE(ank.erase(key) == ref.erase(key));
REQUIRE(ank.size() == ref.size());
}
auto cpy = std::unordered_map(ank.begin(), ank.end());
REQUIRE(cpy == ref);
}
} // namespace
TEST_CASE("fuzz_insert_erase" * doctest::test_suite("fuzz")) {
fuzz::run([](fuzz::provider p) {
// try all 3 different map styles with the same input
insert_erase<ankerl::unordered_dense::map<uint64_t, uint64_t, dummy_hash>>(p.copy());
insert_erase<ankerl::unordered_dense::segmented_map<uint64_t, uint64_t, dummy_hash>>(p.copy());
insert_erase<ankerl::unordered_dense::map<uint64_t,
uint64_t,
ankerl::unordered_dense::hash<uint64_t>,
std::equal_to<uint64_t>,
std::deque<std::pair<uint64_t, uint64_t>>>>(p.copy());
});
}
@@ -0,0 +1,71 @@
#include <ankerl/unordered_dense.h>
#include <app/doctest.h>
#include <fuzz/run.h>
#include <unordered_map>
namespace {
template <typename Map>
void replace_map(fuzz::provider p) {
auto counts = counter{};
using map_t = Map;
auto initial_size = p.bounded<size_t>(100);
auto map = map_t{};
for (size_t i = 0; i < initial_size; ++i) {
map.try_emplace(counter::obj{i, counts}, counter::obj{i, counts});
}
// create a container with data in it provided by fuzzer
auto container = typename map_t::value_container_type{};
auto comparison_container = std::vector<std::pair<size_t, size_t>>();
auto v = size_t{};
while (p.has_remaining_bytes()) {
auto key = p.integral<size_t>();
container.emplace_back(counter::obj{key, counts}, counter::obj{v, counts});
comparison_container.emplace_back(key, v);
++v;
}
// create comparison map with the same move-back-forward algorithm
auto comparison_map = std::unordered_map<size_t, size_t>{};
size_t idx = 0;
while (idx != comparison_container.size()) {
auto [key, val] = comparison_container[idx];
if (comparison_map.try_emplace(key, val).second) {
++idx;
} else {
comparison_container[idx] = comparison_container.back();
comparison_container.pop_back();
}
}
map.replace(std::move(container));
// now check if the data in the map is exactly what we expect
REQUIRE(map.size() == comparison_map.size());
for (auto [key, val] : comparison_map) {
auto key_obj = counter::obj{key, counts};
auto val_obj = counter::obj{val, counts};
auto it = map.find(key_obj);
REQUIRE(it != map.end());
REQUIRE(it->first == key_obj);
REQUIRE(it->second == val_obj);
}
}
} // namespace
TEST_CASE("fuzz_replace_map" * doctest::test_suite("fuzz")) {
fuzz::run([](fuzz::provider p) {
replace_map<ankerl::unordered_dense::map<counter::obj, counter::obj>>(p.copy());
replace_map<ankerl::unordered_dense::segmented_map<counter::obj, counter::obj>>(p.copy());
replace_map<ankerl::unordered_dense::map<counter::obj,
counter::obj,
ankerl::unordered_dense::hash<counter::obj>,
std::equal_to<counter::obj>,
std::deque<std::pair<counter::obj, counter::obj>>>>(p.copy());
});
}
+53
View File
@@ -0,0 +1,53 @@
#include <ankerl/unordered_dense.h>
#include <fuzz/provider.h>
#include <fuzz/run.h>
#include <app/doctest.h>
#include <unordered_map>
namespace {
template <typename Map>
void do_string(fuzz::provider p) {
auto ank = Map();
auto ref = std::unordered_map<std::string, std::string>();
while (p.has_remaining_bytes()) {
auto str = p.string(32);
REQUIRE(ank.try_emplace(str, "hello!").second == ref.try_emplace(str, "hello!").second);
str = p.string(32);
auto it_ank = ank.find(str);
auto it_ref = ref.find(str);
REQUIRE((it_ank == ank.end()) == (it_ref == ref.end()));
if (it_ank != ank.end()) {
ank.erase(it_ank);
ref.erase(it_ref);
}
REQUIRE(ank.size() == ref.size());
str = p.string(32);
REQUIRE(ank.try_emplace(str, "huh").second == ref.try_emplace(str, "huh").second);
str = p.string(32);
REQUIRE(ank.erase(str) == ref.erase(str));
}
REQUIRE(std::unordered_map(ank.begin(), ank.end()) == ref);
}
} // namespace
TEST_CASE("fuzz_string" * doctest::test_suite("fuzz")) {
fuzz::run([](fuzz::provider p) {
do_string<ankerl::unordered_dense::map<std::string, std::string>>(p.copy());
do_string<ankerl::unordered_dense::segmented_map<std::string, std::string>>(p.copy());
do_string<ankerl::unordered_dense::map<std::string,
std::string,
ankerl::unordered_dense::hash<std::string>,
std::equal_to<std::string>,
std::deque<std::pair<std::string, std::string>>>>(p.copy());
});
}
+20
View File
@@ -0,0 +1,20 @@
#include <ankerl/unordered_dense.h>
#include <doctest.h> // for ResultBuilder, TestCase, REQUIRE
#include <cstddef> // for size_t
#include <cstdint> // for uint64_t
#include <string> // for string, basic_string
#include <unordered_set> // for unordered_set
TEST_CASE("hash_string") {
auto h = ankerl::unordered_dense::hash<std::string>();
auto set = std::unordered_set<uint64_t>();
auto str = std::string();
for (size_t l = 0; l < 100; ++l) {
set.insert(h(str));
str.push_back('x');
}
REQUIRE(set.size() == 100);
}
@@ -0,0 +1,6 @@
#include <doctest.h>
TEST_CASE("hash_char_types") {
// TODO(martinus) make hash generic?
// REQUIRE(123 != ankerl::hash<wchar_t>{}(123));
}
@@ -0,0 +1,21 @@
#include <ankerl/unordered_dense.h>
#include <doctest.h> // for ResultBuilder, TestCase, REQUIRE
#include <cstdint> // for uint64_t
#include <memory> // for shared_ptr, __unique_ptr_t, make...
#include <type_traits> // for declval
template <typename Ptr>
void check(Ptr const& ptr) {
REQUIRE(ankerl::unordered_dense::hash<Ptr>{}(ptr) ==
ankerl::unordered_dense::hash<decltype(std::declval<Ptr>().get())>{}(ptr.get()));
}
TEST_CASE("hash_smart_ptr") {
check(std::unique_ptr<uint64_t>{});
check(std::shared_ptr<uint64_t>{});
check(std::make_shared<uint64_t>(123U));
check(std::make_unique<uint64_t>(123U));
check(std::make_unique<uint64_t>(uint64_t{123U}));
}
@@ -0,0 +1,24 @@
#include <ankerl/unordered_dense.h>
#include <doctest.h>
#include <string>
#include <string_view>
TEST_CASE("hash_string_view") {
auto const* cstr = "The ships hung in the sky in much the same way that bricks don't.";
REQUIRE(ankerl::unordered_dense::hash<std::string>{}(std::string{cstr}) ==
ankerl::unordered_dense::hash<std::string_view>{}(std::string_view{cstr}));
}
TEST_CASE("unit_hash_u32string") {
auto str = std::u32string{};
str.push_back(1);
str.push_back(2);
str.push_back(3);
str.push_back(4);
str.push_back(5);
REQUIRE(ankerl::unordered_dense::hash<std::u32string>{}(str) == ankerl::unordered_dense::hash<std::u32string_view>{}(str));
REQUIRE(ankerl::unordered_dense::hash<std::u32string>{}(str) != std::hash<std::u32string>{}(str));
}
@@ -0,0 +1 @@
#include <ankerl/unordered_dense.h>
@@ -0,0 +1,81 @@
#include <ankerl/unordered_dense.h>
#include <app/doctest.h>
#include <cstddef> // for size_t
#include <string> // for string, operator==, allocator
#include <string_view> // for basic_string_view, operator""sv
#include <utility> // for pair
#include <vector> // for vector
using namespace std::literals;
template <class Map, class First, class Second>
auto has(Map const& map, First const& first, Second const& second) -> bool {
auto it = map.find(first);
return it != map.end() && it->second == second;
}
TEST_CASE_MAP("insert_initializer_list", int, int) {
auto m = map_t();
m.insert({{1, 2}, {3, 4}, {5, 6}});
REQUIRE(m.size() == 3U);
REQUIRE(m[1] == 2);
REQUIRE(m[3] == 4);
REQUIRE(m[5] == 6);
}
TEST_CASE_MAP("initializerlist_string", std::string, size_t) {
size_t const n1 = 17;
size_t const n2 = 10;
auto m1 = map_t{{"duck", n1}, {"lion", n2}};
auto m2 = map_t{{"duck", n1}, {"lion", n2}};
REQUIRE(m1.size() == 2);
REQUIRE(m1["duck"] == n1);
REQUIRE(m1["lion"] == n2);
REQUIRE(m2.size() == 2);
auto it = m2.find("duck");
REQUIRE((it != m2.end() && it->second == n1));
REQUIRE(m2["lion"] == n2);
}
TEST_CASE_MAP("insert_initializer_list_string", int, std::string) {
auto m = map_t();
m.insert({{1, "a"}, {3, "b"}, {5, "c"}});
REQUIRE(m.size() == 3U);
REQUIRE(m[1] == "a");
REQUIRE(m[3] == "b");
REQUIRE(m[5] == "c");
}
TEST_CASE_MAP("initializer_list_assign", int, char const*) {
auto map = map_t();
map[3] = "nope";
map = {{1, "a"}, {2, "hello"}, {12346, "world!"}};
REQUIRE(map.size() == 3);
REQUIRE(has(map, 1, "a"sv));
REQUIRE(has(map, 2, "hello"sv));
REQUIRE(has(map, 12346, "world!"sv));
}
TEST_CASE_MAP("initializer_list_ctor_alloc", int, char const*) {
using alloc_t = std::allocator<std::pair<int, char const*>>;
auto map = map_t({{1, "a"}, {2, "hello"}, {12346, "world!"}}, 0, alloc_t{});
REQUIRE(map.size() == 3);
REQUIRE(has(map, 1, "a"sv));
REQUIRE(has(map, 2, "hello"sv));
REQUIRE(has(map, 12346, "world!"sv));
}
TEST_CASE_MAP("initializer_list_ctor_hash_alloc", int, char const*) {
using hash_t = ankerl::unordered_dense::hash<int>;
using alloc_t = std::allocator<std::pair<int, char const*>>;
auto map = map_t({{1, "a"}, {2, "hello"}, {12346, "world!"}}, 0, hash_t{}, alloc_t{});
REQUIRE(map.size() == 3);
REQUIRE(has(map, 1, "a"sv));
REQUIRE(has(map, 2, "hello"sv));
REQUIRE(has(map, 12346, "world!"sv));
}
+36
View File
@@ -0,0 +1,36 @@
#include <ankerl/unordered_dense.h>
#include <app/doctest.h>
#include <tuple> // for forward_as_tuple
#include <utility> // for piecewise_construct
#include <vector> // for vector
TEST_CASE_MAP("insert", unsigned int, int) {
auto map = map_t();
auto const val = typename map_t::value_type(123U, 321);
map.insert(val);
REQUIRE(map.size() == 1);
REQUIRE(map[123U] == 321);
}
TEST_CASE_MAP("insert_hint", unsigned int, int) {
auto map = map_t();
auto it = map.insert(map.begin(), {1, 2});
auto vt = typename decltype(map)::value_type{3, 4};
map.insert(it, vt);
REQUIRE(map.size() == 2);
REQUIRE(map[1] == 2);
REQUIRE(map[3] == 4);
auto const vt2 = typename decltype(map)::value_type{10, 11};
map.insert(it, vt2);
REQUIRE(map.size() == 3);
REQUIRE(map[10] == 11);
it = map.emplace_hint(it, std::piecewise_construct, std::forward_as_tuple(123), std::forward_as_tuple(321));
REQUIRE(map.size() == 4);
REQUIRE(map[123] == 321);
}
@@ -0,0 +1,49 @@
#include <ankerl/unordered_dense.h>
#include <app/doctest.h>
#include <string> // for string, operator==, allocator
#include <utility> // for pair
#include <vector> // for vector
TEST_CASE_MAP("insert_or_assign", std::string, std::string) {
auto map = map_t();
auto ret = map.insert_or_assign("a", "b");
REQUIRE(ret.second);
REQUIRE(ret.first == map.find("a"));
REQUIRE(map.size() == 1);
REQUIRE(map["a"] == "b");
ret = map.insert_or_assign("c", "d");
REQUIRE(ret.second);
REQUIRE(ret.first == map.find("c"));
REQUIRE(map.size() == 2);
REQUIRE(map["c"] == "d");
ret = map.insert_or_assign("c", "dd");
REQUIRE_FALSE(ret.second);
REQUIRE(ret.first == map.find("c"));
REQUIRE(map.size() == 2);
REQUIRE(map["c"] == "dd");
std::string key = "a";
std::string value = "bb";
ret = map.insert_or_assign(key, value);
REQUIRE_FALSE(ret.second);
REQUIRE(ret.first == map.find("a"));
REQUIRE(map.size() == 2);
REQUIRE(map["a"] == "bb");
key = "e";
value = "f";
auto pos = map.insert_or_assign(map.end(), key, value);
REQUIRE(pos == map.find("e"));
REQUIRE(map.size() == 3);
REQUIRE(map["e"] == "f");
pos = map.insert_or_assign(map.begin(), "e", "ff");
REQUIRE(pos == map.find("e"));
REQUIRE(map.size() == 3);
REQUIRE(map["e"] == "ff");
}
@@ -0,0 +1,23 @@
#include <ankerl/unordered_dense.h>
#include <app/doctest.h>
#include <cstddef> // for size_t
#include <cstdint> // for uint64_t
#include <vector> // for vector
TEST_CASE_MAP("iterators_empty", size_t, size_t) {
for (size_t i = 0; i < 10; ++i) {
auto m = ankerl::unordered_dense::map<size_t, size_t>();
REQUIRE(m.begin() == m.end());
REQUIRE(m.end() == m.find(132));
m[1];
REQUIRE(m.begin() != m.end());
REQUIRE(++m.begin() == m.end());
m.clear();
REQUIRE(m.begin() == m.end());
}
}
@@ -0,0 +1,44 @@
#include <ankerl/unordered_dense.h>
#include <app/counter.h>
#include <app/doctest.h>
#include <cstddef> // for size_t
#include <cstdint> // for uint64_t
#include <unordered_set> // for unordered_set
#include <utility> // for pair
#include <vector> // for vector
TEST_CASE_MAP("iterators_erase", counter::obj, counter::obj) {
auto counts = counter();
INFO(counts);
{
counts("begin");
auto map = map_t();
for (size_t i = 0; i < 100; ++i) {
map[counter::obj(i * 101, counts)] = counter::obj(i * 101, counts);
}
auto it = map.find(counter::obj(size_t{20} * 101, counts));
REQUIRE(map.size() == 100);
REQUIRE(map.end() != map.find(counter::obj(size_t{20} * 101, counts)));
it = map.erase(it);
REQUIRE(map.size() == 99);
REQUIRE(map.end() == map.find(counter::obj(size_t{20} * 101, counts)));
it = map.begin();
size_t current_size = map.size();
std::unordered_set<uint64_t> keys;
while (it != map.end()) {
REQUIRE(keys.emplace(it->first.get()).second);
it = map.erase(it);
current_size--;
REQUIRE(map.size() == current_size);
}
REQUIRE(map.size() == static_cast<size_t>(0));
counts("done");
}
counts("destructed");
REQUIRE(counts.dtor() ==
counts.ctor() + counts.static_default_ctor + counts.copy_ctor() + counts.default_ctor() + counts.move_ctor());
}
@@ -0,0 +1,23 @@
#include <ankerl/unordered_dense.h>
#include <app/doctest.h>
#include <algorithm> // for max
#include <utility> // for pair
#include <vector> // for vector
TEST_CASE_MAP("iterators_insert", int, int) {
auto v = std::vector<std::pair<int, int>>();
v.reserve(1000);
for (int i = 0; i < 1000; ++i) {
v.emplace_back(i, i);
}
auto map = map_t(v.begin(), v.end());
REQUIRE(map.size() == v.size());
for (auto const& kv : v) {
REQUIRE(map.count(kv.first) == 1);
auto it = map.find(kv.first);
REQUIRE(it != map.end());
}
}
+15
View File
@@ -0,0 +1,15 @@
#include <ankerl/unordered_dense.h>
#include <app/doctest.h>
TEST_CASE_MAP("load_factor", int, int) {
auto m = map_t();
REQUIRE(static_cast<double>(m.load_factor()) == doctest::Approx(0.0));
for (int i = 0; i < 10000; ++i) {
m.emplace(i, i);
REQUIRE(m.load_factor() > 0.0F);
REQUIRE(m.load_factor() <= 0.8F);
}
}
+68
View File
@@ -0,0 +1,68 @@
#include <ankerl/unordered_dense.h>
#include <app/checksum.h>
#include <app/counter.h>
#include <app/doctest.h>
#include <third-party/nanobench.h>
#include <cstddef> // for size_t
#include <cstdint> // for uint64_t
#include <utility> // for move
template <typename Map>
void test() {
auto counts = counter();
INFO(counts);
auto rng = ankerl::nanobench::Rng();
for (size_t trial = 0; trial < 4; ++trial) {
{
counts("start");
auto maps = Map();
for (size_t i = 0; i < 100; ++i) {
auto a = rng.bounded(20);
auto b = rng.bounded(20);
auto x = static_cast<size_t>(rng());
// std::cout << i << ": map[" << a << "][" << b << "] = " << x << std::endl;
maps[counter::obj(a, counts)][counter::obj(b, counts)] = counter::obj(x, counts);
}
counts("filled");
Map maps_copied;
maps_copied = maps;
REQUIRE(checksum::mapmap(maps_copied) == checksum::mapmap(maps));
REQUIRE(maps_copied == maps);
counts("copied");
Map maps_moved;
maps_moved = std::move(maps_copied);
counts("moved");
// move
REQUIRE(checksum::mapmap(maps_moved) == checksum::mapmap(maps));
REQUIRE(maps_copied.size() == 0); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved)
maps_copied = std::move(maps_moved);
counts("moved back");
// move back
REQUIRE(checksum::mapmap(maps_copied) == checksum::mapmap(maps));
REQUIRE(maps_moved.size() == 0); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved)
counts("done");
}
counts("all destructed");
REQUIRE(counts.dtor() ==
counts.ctor() + counts.static_default_ctor + counts.copy_ctor() + counts.default_ctor() + counts.move_ctor());
}
}
TYPE_TO_STRING_MAP(counter::obj, ankerl::unordered_dense::map<counter::obj, counter::obj>);
TYPE_TO_STRING_MAP(counter::obj, ankerl::unordered_dense::segmented_map<counter::obj, counter::obj>);
TEST_CASE_MAP("mapmap_map", counter::obj, ankerl::unordered_dense::map<counter::obj, counter::obj>) {
test<map_t>();
}
TEST_CASE_MAP("mapmap_segmented_map", counter::obj, ankerl::unordered_dense::segmented_map<counter::obj, counter::obj>) {
test<map_t>();
}
+44
View File
@@ -0,0 +1,44 @@
#include <ankerl/unordered_dense.h>
#include <app/doctest.h>
#include <cstdint> // for uint32_t
#include <functional> // for equal_to
#include <limits> // for numeric_limits
TEST_CASE_MAP("max_load_factor", int, int) {
auto map_60 = map_t();
auto map_90 = map_t();
REQUIRE(map_60.max_load_factor() == doctest::Approx(0.8));
map_60.max_load_factor(0.6F);
map_90.max_load_factor(0.9F);
REQUIRE(map_60.max_load_factor() == doctest::Approx(0.6));
REQUIRE(map_90.max_load_factor() == doctest::Approx(0.9));
map_60[0];
map_90[0];
REQUIRE(map_60.bucket_count() == map_90.bucket_count());
auto const old_bucket_count = map_60.bucket_count();
// fill up maps until bucket_count increases
int i = 0;
while (map_60.bucket_count() == old_bucket_count) {
map_60[++i];
}
while (map_90.bucket_count() == old_bucket_count) {
map_90[++i];
}
// 0.9 max_load should fit more than map_60
REQUIRE(map_90.size() > map_60.size());
map_60.max_load_factor(0.9F);
REQUIRE(map_60.max_load_factor() == map_90.max_load_factor());
}
TEST_CASE_MAP("key_eq", int, int) {
auto const map = map_t();
auto eq = map.key_eq();
REQUIRE(eq(5, 5));
}
+24
View File
@@ -0,0 +1,24 @@
#include <ankerl/unordered_dense.h>
#define ENABLE_LOG_LINE
#include <app/doctest.h>
#include <app/print.h>
#include <type_traits> // for remove_reference, remove_referen...
#include <utility> // for move
TEST_CASE_MAP("move_to_moved", int, int) {
auto a = map_t();
a[1] = 2;
auto moved = std::move(a);
auto c = map_t();
c[3] = 4;
// assign to a moved map
a = std::move(c);
a[5] = 6;
moved[6] = 7;
REQUIRE(moved[6] == 7);
}
+107
View File
@@ -0,0 +1,107 @@
#include <ankerl/unordered_dense.h>
#include <app/counter.h>
#include <app/doctest.h>
#include <third-party/nanobench.h>
#include <cstddef> // for size_t
#include <cstdint> // for uint64_t
#include <type_traits> // for __enable_if_t
#include <unordered_map> // for _Node_iterator, unordered_map
#include <utility> // for pair, make_pair
#include <vector> // for vector
TEST_CASE_MAP("multiple_different_APIs" * doctest::test_suite("stochastic"), counter::obj, counter::obj) {
counter counts;
INFO(counts);
map_t map;
REQUIRE(map.size() == static_cast<size_t>(0));
std::pair<typename map_t::iterator, bool> it_outer =
map.insert(typename map_t::value_type{{32145, counts}, {123, counts}});
REQUIRE(it_outer.second);
REQUIRE(it_outer.first->first.get() == 32145);
REQUIRE(it_outer.first->second.get() == 123);
REQUIRE(map.size() == 1);
const size_t times = 10000;
for (size_t i = 0; i < times; ++i) {
INFO(i);
std::pair<typename map_t::iterator, bool> it_inner =
map.insert(typename map_t::value_type({i * 4U, counts}, {i, counts}));
REQUIRE(it_inner.second);
REQUIRE(it_inner.first->first.get() == i * 4);
REQUIRE(it_inner.first->second.get() == i);
auto found = map.find(counter::obj{i * 4, counts});
REQUIRE(map.end() != found);
REQUIRE(found->second.get() == i);
REQUIRE(map.size() == 2 + i);
}
// check if everything can be found
for (size_t i = 0; i < times; ++i) {
auto found = map.find(counter::obj{i * 4, counts});
REQUIRE(map.end() != found);
REQUIRE(found->second.get() == i);
REQUIRE(found->first.get() == i * 4);
}
// check non-elements
for (size_t i = 0; i < times; ++i) {
auto found = map.find(counter::obj{(i + times) * 4U, counts});
REQUIRE(map.end() == found);
}
// random test against std::unordered_map
map.clear();
std::unordered_map<uint64_t, uint64_t> uo;
auto rng = ankerl::nanobench::Rng();
INFO("seed=" << rng.state());
for (uint64_t i = 0; i < times; ++i) {
auto r = static_cast<size_t>(rng.bounded(times / 4));
auto rhh_it = map.insert(typename map_t::value_type({r, counts}, {r * 2, counts}));
auto uo_it = uo.insert(std::make_pair(r, r * 2));
REQUIRE(rhh_it.second == uo_it.second);
REQUIRE(rhh_it.first->first.get() == uo_it.first->first);
REQUIRE(rhh_it.first->second.get() == uo_it.first->second);
REQUIRE(map.size() == uo.size());
r = rng.bounded(times / 4);
auto map_it = map.find(counter::obj{r, counts});
auto uo_it2 = uo.find(r);
REQUIRE((map.end() == map_it) == (uo.end() == uo_it2));
if (map.end() != map_it) {
REQUIRE(map_it->first.get() == uo_it2->first);
REQUIRE(map_it->second.get() == uo_it2->second);
}
}
uo.clear();
map.clear();
for (size_t i = 0; i < times; ++i) {
const auto r = static_cast<size_t>(rng.bounded(times / 4));
map[{r, counts}] = {r * 2, counts};
uo[r] = r * 2;
REQUIRE(map.find(counter::obj{r, counts})->second.get() == uo.find(r)->second);
REQUIRE(map.size() == uo.size());
}
std::size_t num_checks = 0;
for (auto it = map.begin(); it != map.end(); ++it) {
REQUIRE(uo.end() != uo.find(it->first.get()));
++num_checks;
}
REQUIRE(map.size() == num_checks);
num_checks = 0;
map_t const& const_rhhs = map;
for (const typename map_t::value_type& vt : const_rhhs) {
REQUIRE(uo.end() != uo.find(vt.first.get()));
++num_checks;
}
REQUIRE(map.size() == num_checks);
}
+13
View File
@@ -0,0 +1,13 @@
#include <ankerl/unordered_dense.h>
#include <doctest.h>
namespace versioned_namespace = ankerl::unordered_dense::v4_4_0;
static_assert(std::is_same_v<versioned_namespace::map<int, int>, ankerl::unordered_dense::map<int, int>>);
static_assert(std::is_same_v<versioned_namespace::hash<int>, ankerl::unordered_dense::hash<int>>);
TEST_CASE("version_namespace") {
auto map = versioned_namespace::map<int, int>{};
REQUIRE(map.empty());
}
+50
View File
@@ -0,0 +1,50 @@
#include <ankerl/unordered_dense.h>
#include <app/doctest.h>
#include <cstddef> // for size_t
#include <tuple> // for forward_as_tuple
#include <utility> // for move, piecewise_construct
// not copyable, but movable.
class no_copy {
public:
no_copy() noexcept = default;
explicit no_copy(size_t d) noexcept
: m_mData(d) {}
~no_copy() = default;
no_copy(no_copy const&) = delete;
auto operator=(no_copy const&) -> no_copy& = delete;
no_copy(no_copy&&) = default;
auto operator=(no_copy&&) -> no_copy& = default;
[[nodiscard]] auto data() const -> size_t {
return m_mData;
}
private:
size_t m_mData{};
};
TYPE_TO_STRING_MAP(size_t, no_copy);
TEST_CASE_MAP("not_copyable", size_t, no_copy) {
// it's ok because it is movable.
map_t m;
for (size_t i = 0; i < 100; ++i) {
m[i];
m.emplace(std::piecewise_construct, std::forward_as_tuple(i * 100), std::forward_as_tuple(i));
}
REQUIRE(m.size() == 199);
// not copyable, because m is not copyable!
// map_t m2 = m;
// movable works
map_t m2 = std::move(m);
REQUIRE(m2.size() == 199);
m = map_t{};
REQUIRE(m.size() == 0);
}
+51
View File
@@ -0,0 +1,51 @@
#include <ankerl/unordered_dense.h>
#include <app/doctest.h>
#include <cstddef> // for size_t
#include <tuple> // for forward_as_tuple
#include <utility> // for piecewise_construct
class no_move {
public:
no_move() noexcept = default;
explicit no_move(size_t d) noexcept
: m_mData(d) {}
~no_move() = default;
no_move(no_move const&) = default;
auto operator=(no_move const&) -> no_move& = default;
no_move(no_move&&) = delete;
auto operator=(no_move&&) -> no_move& = delete;
[[nodiscard]] auto data() const -> size_t {
return m_mData;
}
private:
size_t m_mData{};
};
TYPE_TO_STRING_MAP(size_t, no_move);
// doesn't work with robin_hood::unordered_flat_map<size_t, NoMove> because not movable and not
// copyable
TEST_CASE_MAP("not_moveable", size_t, no_move) {
// it's ok because it is movable.
auto m = map_t();
for (size_t i = 0; i < 100; ++i) {
m[i];
m.emplace(std::piecewise_construct, std::forward_as_tuple(i * 100), std::forward_as_tuple(i));
}
REQUIRE(m.size() == 199);
// not copyable, because m is not copyable!
// map_t m2 = m;
// not movable
// map_t m2 = std::move(m);
// REQUIRE(m2.size() == 199);
m.clear();
REQUIRE(m.size() == 0);
}
+232
View File
@@ -0,0 +1,232 @@
#include <ankerl/unordered_dense.h>
#include <app/doctest.h>
#include <fmt/core.h>
#include <cstddef> // for size_t
#include <cstdint> // for uint64_t
#include <stdexcept> // thrown in no_null_memory_resource
#include <string_view> // for string_view
#include <utility> // for move
#include <vector> // for vector
#if defined(ANKERL_UNORDERED_DENSE_PMR)
// windows' vector has different allocation behavior, macos has linker errors
# if __linux__
class logging_memory_resource : public ANKERL_UNORDERED_DENSE_PMR::memory_resource {
auto do_allocate(std::size_t bytes, std::size_t alignment) -> void* override {
fmt::print("+ {} bytes, {} alignment\n", bytes, alignment);
return ANKERL_UNORDERED_DENSE_PMR::new_delete_resource()->allocate(bytes, alignment);
}
void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override {
fmt::print("- {} bytes, {} alignment, {} ptr\n", bytes, alignment, p);
return ANKERL_UNORDERED_DENSE_PMR::new_delete_resource()->deallocate(p, bytes, alignment);
}
[[nodiscard]] auto do_is_equal(const ANKERL_UNORDERED_DENSE_PMR::memory_resource& other) const noexcept -> bool override {
return this == &other;
}
};
class no_null_memory_resource : public ANKERL_UNORDERED_DENSE_PMR::memory_resource {
auto do_allocate(std::size_t bytes, std::size_t alignment) -> void* override {
if (bytes == 0) {
throw std::runtime_error("no_null_memory_resource::do_allocate should not do_allocate 0");
}
return ANKERL_UNORDERED_DENSE_PMR::new_delete_resource()->allocate(bytes, alignment);
}
void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override {
if (nullptr == p || 0U == bytes || 0U == alignment) {
throw std::runtime_error("no_null_memory_resource::do_deallocate should not deallocate with any 0 value");
}
return ANKERL_UNORDERED_DENSE_PMR::new_delete_resource()->deallocate(p, bytes, alignment);
}
[[nodiscard]] auto do_is_equal(const ANKERL_UNORDERED_DENSE_PMR::memory_resource& other) const noexcept -> bool override {
return this == &other;
}
};
class track_peak_memory_resource : public ANKERL_UNORDERED_DENSE_PMR::memory_resource {
uint64_t m_peak = 0;
uint64_t m_current = 0;
uint64_t m_num_allocs = 0;
uint64_t m_num_deallocs = 0;
mutable uint64_t m_num_is_equals = 0;
auto do_allocate(std::size_t bytes, std::size_t alignment) -> void* override {
++m_num_allocs;
m_current += bytes;
if (m_current > m_peak) {
m_peak = m_current;
}
return ANKERL_UNORDERED_DENSE_PMR::new_delete_resource()->allocate(bytes, alignment);
}
void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override {
if (p == nullptr) {
return;
}
++m_num_deallocs;
m_current -= bytes;
return ANKERL_UNORDERED_DENSE_PMR::new_delete_resource()->deallocate(p, bytes, alignment);
}
[[nodiscard]] auto do_is_equal(const ANKERL_UNORDERED_DENSE_PMR::memory_resource& other) const noexcept -> bool override {
++m_num_is_equals;
return this == &other;
}
public:
[[nodiscard]] auto current() const -> uint64_t {
return m_current;
}
[[nodiscard]] auto peak() const -> uint64_t {
return m_peak;
}
[[nodiscard]] auto num_allocs() const -> uint64_t {
return m_num_allocs;
}
[[nodiscard]] auto num_deallocs() const -> uint64_t {
return m_num_deallocs;
}
[[nodiscard]] auto num_is_equals() const -> uint64_t {
return m_num_is_equals;
}
};
TYPE_TO_STRING_PMR_MAP(uint64_t, uint64_t);
TEST_CASE_PMR_MAP("pmr", uint64_t, uint64_t) {
auto mr = track_peak_memory_resource();
{
REQUIRE(mr.current() == 0);
auto map = map_t(&mr);
for (size_t i = 0; i < 1; ++i) {
map[i] = i;
}
REQUIRE(mr.current() != 0);
// gets a copy, but it has the same memory resource
auto alloc = map.get_allocator();
REQUIRE(alloc.resource() == &mr);
}
REQUIRE(mr.current() == 0);
}
TEST_CASE_PMR_MAP("pmr_no_null", uint64_t, uint64_t) {
auto mr = no_null_memory_resource();
{ auto map = map_t(&mr); }
{
auto map = map_t(&mr);
for (size_t i = 0; i < 1; ++i) {
map[i] = i;
}
}
{
auto map = map_t(&mr);
for (size_t i = 0; i < 1; ++i) {
map[i] = i;
}
auto map2 = std::move(map);
}
}
void show([[maybe_unused]] track_peak_memory_resource const& mr, [[maybe_unused]] std::string_view name) {
// fmt::print("{}: {} allocs, {} deallocs, {} is_equals\n", name, mr.num_allocs(), mr.num_deallocs(), mr.num_is_equals());
}
// the following tests are vector specific, so don't use segmented_vector
TEST_CASE("pmr_copy") {
using map_t = ankerl::unordered_dense::pmr::map<uint64_t, uint64_t>;
auto mr1 = track_peak_memory_resource();
auto map1 = map_t(&mr1);
map1[1] = 2;
auto mr2 = track_peak_memory_resource();
auto map2 = map_t(&mr2);
map2[3] = 4;
show(mr1, "mr1");
show(mr2, "mr2");
map1 = map2;
REQUIRE(map1.size() == 1);
REQUIRE(map1.find(3) != map1.end());
show(mr1, "mr1");
show(mr2, "mr2");
REQUIRE(mr1.num_allocs() == 3);
REQUIRE(mr1.num_deallocs() == 1);
REQUIRE(mr1.num_is_equals() == 0);
REQUIRE(mr2.num_allocs() == 2);
REQUIRE(mr2.num_deallocs() == 0);
REQUIRE(mr2.num_is_equals() == 0);
}
TEST_CASE("pmr_move_different_mr") {
using map_t = ankerl::unordered_dense::pmr::map<uint64_t, uint64_t>;
auto mr1 = track_peak_memory_resource();
auto map1 = map_t(&mr1);
map1[1] = 2;
auto mr2 = track_peak_memory_resource();
auto map2 = map_t(&mr2);
map2[3] = 4;
show(mr1, "mr1");
show(mr2, "mr2");
map1 = std::move(map2);
REQUIRE(map1.size() == 1);
REQUIRE(map1.find(3) != map1.end());
show(mr1, "mr1");
show(mr2, "mr2");
REQUIRE(mr1.num_allocs() == 3);
REQUIRE(mr1.num_deallocs() == 1);
REQUIRE(mr1.num_is_equals() == 1);
REQUIRE(mr2.num_allocs() == 2);
REQUIRE(mr2.num_deallocs() == 0);
REQUIRE(mr2.num_is_equals() == 1);
}
TEST_CASE("pmr_move_same_mr") {
using map_t = ankerl::unordered_dense::pmr::map<uint64_t, uint64_t>;
auto mr1 = track_peak_memory_resource();
auto map1 = map_t(&mr1);
map1[1] = 2;
REQUIRE(mr1.num_allocs() == 2);
REQUIRE(mr1.num_deallocs() == 0);
REQUIRE(mr1.num_is_equals() == 0);
auto map2 = ankerl::unordered_dense::pmr::map<uint64_t, uint64_t>(&mr1);
map2[3] = 4;
show(mr1, "mr1");
map1 = std::move(map2);
REQUIRE(map1.size() == 1);
REQUIRE(map1.find(3) != map1.end());
show(mr1, "mr1");
REQUIRE(mr1.num_allocs() == 5); // 5 because of the initial allocation
REQUIRE(mr1.num_deallocs() == 2);
REQUIRE(mr1.num_is_equals() == 0);
}
# endif
#endif
@@ -0,0 +1,108 @@
#include <ankerl/unordered_dense.h>
#define ENABLE_LOG_LINE
#include <app/doctest.h>
#include <app/print.h>
#if defined(ANKERL_UNORDERED_DENSE_PMR)
// windows' vector has different allocation behavior, macos has linker errors
# if __linux__
namespace {
// creates a map and moves it out
template <typename Map>
auto return_hello_world(ANKERL_UNORDERED_DENSE_PMR::memory_resource* resource) -> Map {
Map map_default_resource(resource);
map_default_resource[0] = "Hello";
map_default_resource[1] = "World";
return map_default_resource;
}
template <typename Map>
auto doTest() {
ANKERL_UNORDERED_DENSE_PMR::synchronized_pool_resource pool;
{
Map m(ANKERL_UNORDERED_DENSE_PMR::new_delete_resource());
m = return_hello_world<Map>(&pool);
REQUIRE(m.contains(0));
}
{
Map m(ANKERL_UNORDERED_DENSE_PMR::new_delete_resource());
m[0] = "foo";
m = return_hello_world<Map>(&pool);
REQUIRE(m[0] == "Hello");
}
{
Map m(return_hello_world<Map>(&pool), ANKERL_UNORDERED_DENSE_PMR::new_delete_resource());
REQUIRE(m.contains(0));
}
{
Map a(ANKERL_UNORDERED_DENSE_PMR::new_delete_resource());
a[0] = "hello";
Map b(&pool);
b[0] = "world";
// looping here causes lots of memory held up
// in the resources
for (int i = 0; i < 100; ++i) {
std::swap(a, b);
REQUIRE(b[0] == "hello");
REQUIRE(a[0] == "world");
std::swap(a, b);
REQUIRE(a[0] == "hello");
REQUIRE(b[0] == "world");
}
}
{
Map a(ANKERL_UNORDERED_DENSE_PMR::new_delete_resource());
a[0] = "hello";
Map b(&pool);
b[0] = "world";
// looping here causes lots of memory held up
// in the resources
for (int i = 0; i < 100; ++i) {
std::swap(a, b);
REQUIRE(b[0] == "hello");
REQUIRE(a[0] == "world");
std::swap(a, b);
REQUIRE(a[0] == "hello");
REQUIRE(b[0] == "world");
}
}
{
Map a(&pool);
a[0] = "world";
Map tmp(ANKERL_UNORDERED_DENSE_PMR::new_delete_resource());
tmp[0] = "nope";
tmp = std::move(a);
REQUIRE(tmp[0] == "world");
REQUIRE(a.empty());
a[0] = "hey";
REQUIRE(a.size() == 1);
REQUIRE(a[0] == "hey");
}
}
TEST_CASE("move_with_allocators") {
doTest<ankerl::unordered_dense::pmr::map<int, std::string>>();
}
TEST_CASE("move_with_allocators_segmented") {
doTest<ankerl::unordered_dense::pmr::segmented_map<int, std::string>>();
}
} // namespace
# endif
#endif
+24
View File
@@ -0,0 +1,24 @@
#include <ankerl/unordered_dense.h>
#include <app/doctest.h>
#include <cstddef> // for size_t
TEST_CASE_MAP("rehash", size_t, int) {
auto map = map_t();
for (size_t i = 0; i < 1000; ++i) {
map[i];
}
auto old_bucket_size = map.bucket_count();
map.rehash(10000);
REQUIRE(map.bucket_count() >= 10000);
map.rehash(0);
REQUIRE(map.bucket_count() == old_bucket_size);
map.clear();
map.rehash(0);
REQUIRE(map.bucket_count() > 0);
REQUIRE(map.bucket_count() < old_bucket_size);
}
+33
View File
@@ -0,0 +1,33 @@
#include <ankerl/unordered_dense.h>
#include <app/counter.h>
#include <app/doctest.h>
TEST_CASE_MAP("replace", counter::obj, counter::obj) {
auto counts = counter{};
INFO(counts);
auto container = typename map_t::value_container_type{};
for (size_t i = 0; i < 100; ++i) {
container.emplace_back(counter::obj{i, counts}, counter::obj{i, counts});
container.emplace_back(counter::obj{i, counts}, counter::obj{i, counts});
}
for (size_t i = 0; i < 10; ++i) {
container.emplace_back(counter::obj{i, counts}, counter::obj{i, counts});
}
// add some elements
auto map = map_t();
for (size_t i = 0; i < 10; ++i) {
map.try_emplace(counter::obj{i, counts}, counter::obj{i, counts});
}
map.replace(std::move(container));
REQUIRE(map.size() == 100U);
for (size_t i = 0; i < 100; ++i) {
REQUIRE(map.contains(counter::obj{i, counts}));
}
}
+14
View File
@@ -0,0 +1,14 @@
#include <ankerl/unordered_dense.h>
#include <app/doctest.h>
TEST_CASE_MAP("reserve", int, int) {
// there's no capacity() for std::deque
if constexpr (!std::is_same_v<typename map_t::value_container_type, std::deque<std::pair<int, int>>>) {
auto map = map_t();
REQUIRE(map.values().capacity() <= 1000U);
map.reserve(1000);
REQUIRE(map.values().capacity() >= 1000U);
REQUIRE(0U == map.values().size());
}
}
@@ -0,0 +1,88 @@
#include <ankerl/unordered_dense.h>
#include <app/checksum.h>
#include <app/doctest.h>
#include <cstdint> // for uint64_t
#include <string> // for allocator, string
#include <vector> // for vector
TEST_CASE_MAP("reserve_and_assign", std::string, uint64_t) {
map_t a = {
{"button", {}},
{"selectbox-tr", {}},
{"slidertrack-t", {}},
{"sliderarrowinc-hover", {}},
{"text-l", {}},
{"title-bar-l", {}},
{"checkbox-checked-hover", {}},
{"datagridexpand-active", {}},
{"icon-waves", {}},
{"sliderarrowdec-hover", {}},
{"datagridexpand-collapsed", {}},
{"sliderarrowinc-active", {}},
{"radio-active", {}},
{"radio-checked", {}},
{"selectvalue-hover", {}},
{"huditem-l", {}},
{"datagridexpand-collapsed-active", {}},
{"slidertrack-b", {}},
{"selectarrow-hover", {}},
{"window-r", {}},
{"selectbox-tl", {}},
{"icon-score", {}},
{"datagridheader-r", {}},
{"icon-game", {}},
{"sliderbar-c", {}},
{"window-c", {}},
{"datagridexpand-hover", {}},
{"button-hover", {}},
{"icon-hiscore", {}},
{"sliderbar-hover-t", {}},
{"sliderbar-hover-c", {}},
{"selectarrow-active", {}},
{"window-tl", {}},
{"checkbox-active", {}},
{"sliderarrowdec-active", {}},
{"sliderbar-active-b", {}},
{"sliderarrowdec", {}},
{"window-bl", {}},
{"datagridheader-l", {}},
{"sliderbar-t", {}},
{"sliderbar-active-t", {}},
{"text-c", {}},
{"window-br", {}},
{"huditem-c", {}},
{"selectbox-l", {}},
{"icon-flag", {}},
{"sliderbar-hover-b", {}},
{"icon-help", {}},
{"selectvalue", {}},
{"title-bar-r", {}},
{"sliderbar-active-c", {}},
{"huditem-r", {}},
{"radio-checked-active", {}},
{"selectbox-c", {}},
{"selectbox-bl", {}},
{"icon-invader", {}},
{"checkbox-checked-active", {}},
{"slidertrack-c", {}},
{"sliderarrowinc", {}},
{"checkbox", {}},
};
map_t b;
b = a;
REQUIRE(b.find("button") != b.end()); // Passes OK.
map_t c;
c.reserve(51);
c = a;
REQUIRE(checksum::map(a) == checksum::map(c));
REQUIRE(c.find("button") != c.end()); // Fails.
}
TEST_CASE_MAP("unit_reserve_only_flat", std::string, uint64_t) {
auto map = map_t();
map.reserve(51);
}
@@ -0,0 +1,138 @@
#include <ankerl/unordered_dense.h>
#include <app/counter.h>
#include <app/counting_allocator.h>
#include <app/doctest.h>
#include <cstddef>
#include <third-party/nanobench.h>
#include <deque>
TEST_CASE("segmented_vector") {
counter counts;
INFO(counts);
{
auto vec = ankerl::unordered_dense::segmented_vector<counter::obj>();
for (size_t i = 0; i < 1000; ++i) {
vec.emplace_back(i, counts);
REQUIRE(i + 1 == counts.ctor());
}
REQUIRE(0 == counts.move_ctor());
REQUIRE(0 == counts.move_assign());
counts("before dtor");
REQUIRE(counts.data() == counter::data_t{1000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
}
counts.check_all_done();
REQUIRE(0 == counts.move_ctor());
counts("done");
REQUIRE(counts.data() == counter::data_t{1000, 0, 0, 1000, 0, 0, 0, 0, 0, 0, 0, 0, 0});
}
TEST_CASE("segmented_vector_capacity") {
counter counts;
INFO(counts);
auto vec =
ankerl::unordered_dense::segmented_vector<counter::obj, std::allocator<counter::obj>, sizeof(counter::obj) * 4>();
REQUIRE(0 == vec.capacity());
for (size_t i = 0; i < 50; ++i) {
REQUIRE(i == vec.size());
vec.emplace_back(i, counts);
REQUIRE(i + 1 == vec.size());
REQUIRE(vec.capacity() >= vec.size());
REQUIRE(0 == vec.capacity() % 4);
}
}
TEST_CASE("segmented_vector_idx") {
counter counts;
INFO(counts);
auto vec =
ankerl::unordered_dense::segmented_vector<counter::obj, std::allocator<counter::obj>, sizeof(counter::obj) * 4>();
REQUIRE(0 == vec.capacity());
for (size_t i = 0; i < 50; ++i) {
vec.emplace_back(i, counts);
}
for (size_t i = 0; i < vec.size(); ++i) {
REQUIRE(i == vec[i].get());
}
}
TEST_CASE("segmented_vector_iterate") {
counter counts;
INFO(counts);
auto vec =
ankerl::unordered_dense::segmented_vector<counter::obj, std::allocator<counter::obj>, sizeof(counter::obj) * 4>();
for (size_t i = 0; i < 50; ++i) {
auto it = vec.begin();
auto end = vec.end();
REQUIRE(std::distance(it, end) == static_cast<std::ptrdiff_t>(vec.size()));
size_t j = 0;
while (it != end) {
REQUIRE(it->get() == j);
++it;
++j;
}
vec.emplace_back(i, counts);
}
}
TEST_CASE("segmented_vector_reserve") {
auto counts = counts_for_allocator{};
auto vec = ankerl::unordered_dense::segmented_vector<int, counting_allocator<int>, sizeof(int) * 16>(&counts);
REQUIRE(0 == vec.capacity());
REQUIRE(counts.size() < 2);
vec.reserve(1100);
REQUIRE(counts.size() > 63);
counts.reset();
REQUIRE(counts.size() == 0);
REQUIRE(1104 == vec.capacity());
for (size_t i = 0; i < vec.capacity(); ++i) {
vec.emplace_back(0);
}
REQUIRE(counts.size() == 0);
vec.emplace_back(123);
// 3: 2 for std::vector<T*> reallocates, 1 for the new segment
REQUIRE(counts.size() == 3);
}
using vec_t = ankerl::unordered_dense::segmented_vector<counter::obj>;
static_assert(sizeof(vec_t) == sizeof(std::vector<counter::obj*>) + sizeof(size_t));
TEST_CASE("bench_segmented_vector" * doctest::test_suite("bench") * doctest::skip()) {
static constexpr auto num_elements = size_t{21233};
using namespace std::literals;
ankerl::nanobench::Rng rng(123);
auto sv = ankerl::unordered_dense::segmented_vector<size_t>();
for (size_t i = 0; i < num_elements; ++i) {
sv.emplace_back(i);
}
ankerl::nanobench::Bench().minEpochTime(100ms).batch(sv.size()).run("shuffle stable_vector", [&] {
rng.shuffle(sv);
});
auto c = std::deque<size_t>();
for (size_t i = 0; i < num_elements; ++i) {
c.push_back(i);
}
ankerl::nanobench::Bench().minEpochTime(100ms).batch(sv.size()).run("shuffle std::deque", [&] {
rng.shuffle(c);
});
auto v = std::vector<size_t>();
for (size_t i = 0; i < num_elements; ++i) {
v.push_back(i);
}
ankerl::nanobench::Bench().minEpochTime(100ms).batch(sv.size()).run("shuffle std::vector", [&] {
rng.shuffle(v);
});
}
+16
View File
@@ -0,0 +1,16 @@
#include <ankerl/unordered_dense.h>
#include <app/doctest.h>
#include <string> // for operator==, basic_string, operat...
#include <vector> // for vector
TEST_CASE_SET("set", std::string) {
auto set = set_t();
set.insert("asdf");
REQUIRE(set.size() == 1);
auto it = set.find("asdf");
REQUIRE(it != set.end());
REQUIRE(*it == "asdf");
}
@@ -0,0 +1,18 @@
#include <ankerl/unordered_dense.h>
template <typename T>
using detect_has_mapped_type = typename T::mapped_type;
using map1_t = ankerl::unordered_dense::map<int, double>;
static_assert(std::is_same_v<double, map1_t::mapped_type>);
static_assert(ankerl::unordered_dense::detail::is_detected_v<detect_has_mapped_type, map1_t>);
using map2_t = ankerl::unordered_dense::segmented_map<int, double>;
static_assert(std::is_same_v<double, map2_t::mapped_type>);
static_assert(ankerl::unordered_dense::detail::is_detected_v<detect_has_mapped_type, map2_t>);
using set1_t = ankerl::unordered_dense::set<int>;
static_assert(!ankerl::unordered_dense::detail::is_detected_v<detect_has_mapped_type, set1_t>);
using set2_t = ankerl::unordered_dense::segmented_set<int>;
static_assert(!ankerl::unordered_dense::detail::is_detected_v<detect_has_mapped_type, set2_t>);
+33
View File
@@ -0,0 +1,33 @@
#include <ankerl/unordered_dense.h>
#include <doctest.h>
#include <cstddef> // for size_t
#include <string> // for allocator, string, operator==
#include <utility> // for pair, move
#include <vector> // for vector
namespace {
struct foo {
uint64_t m_i;
};
} // namespace
template <>
struct std::hash<foo> {
auto operator()(foo const& foo) const noexcept {
return static_cast<size_t>(foo.m_i + 1);
}
};
TEST_CASE("std_hash") {
auto f = foo{12345};
REQUIRE(std::hash<foo>{}(f) == 12346U);
// unordered_dense::hash blows that up to 64bit!
// Just wraps std::hash
REQUIRE(ankerl::unordered_dense::hash<foo>{}(f) == UINT64_C(12346));
REQUIRE(ankerl::unordered_dense::hash<uint64_t>{}(12346U) == UINT64_C(0x3F645BE4CE24110C));
}
+52
View File
@@ -0,0 +1,52 @@
#include <ankerl/unordered_dense.h>
#define ENABLE_LOG_LINE
#include <app/doctest.h>
#include <app/print.h>
#include <vector>
TEST_CASE_MAP("swap", int, int) {
{
auto b = map_t();
{
auto a = map_t();
b[1] = 2;
a.swap(b);
REQUIRE(a.end() != a.find(1));
REQUIRE(b.end() == b.find(1));
}
REQUIRE(b.end() == b.find(1));
b[2] = 3;
REQUIRE(b.end() != b.find(2));
REQUIRE(b.size() == 1);
}
{
auto a = map_t();
{
auto b = map_t();
b[1] = 2;
a.swap(b);
REQUIRE(a.end() != a.find(1));
REQUIRE(b.end() == b.find(1));
}
REQUIRE(a.end() != a.find(1));
a[2] = 3;
REQUIRE(a.end() != a.find(2));
REQUIRE(a.size() == 2);
}
{
auto a = map_t();
{
auto b = map_t();
a.swap(b);
REQUIRE(a.end() == a.find(1));
REQUIRE(b.end() == b.find(1));
}
REQUIRE(a.end() == a.find(1));
}
}
+420
View File
@@ -0,0 +1,420 @@
#include <ankerl/unordered_dense.h>
#include <app/doctest.h>
#include <app/name_of_type.h>
#include <fmt/format.h>
#include <array> // for array
#include <cstddef> // for size_t
#include <functional> // for equal_to
#include <string> // for basic_string, string, operator""s
#include <string_view> // for basic_string_view, operator""sv
#include <type_traits> // for add_const_t
#include <unordered_map> // for unordered_map
#include <utility> // for pair, as_const
#include <vector> // for vector
using namespace std::literals;
class string_eq {
mutable std::unordered_map<std::string, size_t> m_names_to_counts{};
public:
using is_transparent = void;
template <class T, class U>
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay)
auto operator()(T&& lhs, U&& rhs) const -> decltype(std::forward<T>(lhs) == std::forward<U>(rhs)) {
auto names = fmt::format("{} -> {}", name_of_type<T>(), name_of_type<U>());
++m_names_to_counts[names];
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay)
return std::forward<T>(lhs) == std::forward<U>(rhs);
}
auto counts() const -> std::unordered_map<std::string, size_t> const& {
return m_names_to_counts;
}
string_eq() = default;
~string_eq() = default;
string_eq(string_eq const&) = default;
auto operator=(string_eq const&) -> string_eq& = default;
string_eq(string_eq&&) = delete;
auto operator=(string_eq&&) -> string_eq& = delete;
};
// transparent hash, counts number of calls per operator
class string_hash {
mutable size_t m_num_charstar{};
mutable size_t m_num_stringview{};
mutable size_t m_num_string{};
public:
using hash_type = ankerl::unordered_dense::hash<std::string_view>;
using is_transparent = void;
using is_avalanching = void;
[[nodiscard]] auto operator()(const char* str) const noexcept -> uint64_t {
++m_num_charstar;
return hash_type{}(str);
}
[[nodiscard]] auto operator()(std::string_view str) const noexcept -> uint64_t {
++m_num_stringview;
return hash_type{}(str);
}
[[nodiscard]] auto operator()(std::string const& str) const noexcept -> uint64_t {
++m_num_string;
return hash_type{}(str);
}
auto counts() const -> std::array<size_t, 3> {
return {m_num_charstar, m_num_stringview, m_num_string};
}
};
namespace docs {
struct string_hash {
using is_transparent = void; // enable heterogeneous lookup
using is_avalanching = void; // mark class as high quality avalanching hash
[[nodiscard]] auto operator()(const char* str) const noexcept -> uint64_t {
return ankerl::unordered_dense::hash<std::string_view>{}(str);
}
[[nodiscard]] auto operator()(std::string_view str) const noexcept -> uint64_t {
return ankerl::unordered_dense::hash<std::string_view>{}(str);
}
[[nodiscard]] auto operator()(std::string const& str) const noexcept -> uint64_t {
return ankerl::unordered_dense::hash<std::string_view>{}(str);
}
};
} // namespace docs
template <typename C>
void check(int line, C const& container, size_t num_charstar, size_t num_stringview, size_t num_string) {
auto sh = container.hash_function();
auto counts = sh.counts();
INFO("check line " << line << ": expect (" << num_charstar << ", " << num_stringview << ", " << num_string << ") got ("
<< counts[0] << ", " << counts[1] << ", " << counts[2]
<< ") for (num_charstar, num_stringview, num_string)");
REQUIRE(counts[0] == num_charstar);
REQUIRE(counts[1] == num_stringview);
REQUIRE(counts[2] == num_string);
}
TYPE_TO_STRING_MAP(std::string, size_t, string_hash, std::equal_to<>);
TEST_CASE_MAP("transparent_find", std::string, size_t, string_hash, std::equal_to<>) {
auto map = map_t();
map.try_emplace("hello", 1);
check(__LINE__, map, 1, 0, 0);
auto it = map.find("huh");
check(__LINE__, map, 2, 0, 0);
REQUIRE(it == map.end());
it = map.find("hello");
check(__LINE__, map, 3, 0, 0);
REQUIRE(it != map.end());
auto cit = std::as_const(map).find("huh");
check(__LINE__, map, 4, 0, 0);
REQUIRE(cit == map.end());
REQUIRE(cit == map.cend());
cit = std::as_const(map).find("hello");
check(__LINE__, map, 5, 0, 0);
REQUIRE(cit != map.end());
// string_view
it = map.find("huh"sv);
REQUIRE(it == map.end());
check(__LINE__, map, 5, 1, 0);
it = map.find("hello"sv);
REQUIRE(it != map.end());
check(__LINE__, map, 5, 2, 0);
// string
it = map.find("huh"s);
REQUIRE(it == map.end());
check(__LINE__, map, 5, 2, 1);
it = map.find("hello"s);
REQUIRE(it != map.end());
check(__LINE__, map, 5, 2, 2);
}
TEST_CASE_MAP("transparent_count", std::string, size_t, string_hash, std::equal_to<>) {
auto map = map_t();
map.try_emplace("hello", 1);
check(__LINE__, map, 1, 0, 0);
REQUIRE(0 == map.count("huh"));
check(__LINE__, map, 2, 0, 0);
REQUIRE(1 == map.count("hello"));
check(__LINE__, map, 3, 0, 0);
REQUIRE(0 == map.count("huh"sv));
check(__LINE__, map, 3, 1, 0);
REQUIRE(1 == map.count("hello"sv));
check(__LINE__, map, 3, 2, 0);
REQUIRE(0 == map.count("huh"s));
check(__LINE__, map, 3, 2, 1);
REQUIRE(1 == map.count("hello"s));
check(__LINE__, map, 3, 2, 2);
}
TEST_CASE_MAP("transparent_contains", std::string, size_t, string_hash, std::equal_to<>) {
auto map = map_t();
map.try_emplace("hello", 1);
check(__LINE__, map, 1, 0, 0);
REQUIRE(!map.contains("huh"));
check(__LINE__, map, 2, 0, 0);
REQUIRE(map.contains("hello"));
check(__LINE__, map, 3, 0, 0);
REQUIRE(!map.contains("huh"sv));
check(__LINE__, map, 3, 1, 0);
REQUIRE(map.contains("hello"sv));
check(__LINE__, map, 3, 2, 0);
REQUIRE(!map.contains("huh"s));
check(__LINE__, map, 3, 2, 1);
REQUIRE(map.contains("hello"s));
check(__LINE__, map, 3, 2, 2);
}
TEST_CASE_MAP("transparent_erase", std::string, size_t, string_hash, std::equal_to<>) {
auto map = map_t();
map.try_emplace("hello", 1);
check(__LINE__, map, 1, 0, 0);
REQUIRE(0 == map.erase("huh"));
check(__LINE__, map, 2, 0, 0);
REQUIRE(1 == map.erase("hello"));
check(__LINE__, map, 3, 0, 0);
map.try_emplace("hello", 1);
check(__LINE__, map, 4, 0, 0);
REQUIRE(0 == map.erase("huh"sv));
check(__LINE__, map, 4, 1, 0);
REQUIRE(1 == map.erase("hello"sv));
check(__LINE__, map, 4, 2, 0);
map.try_emplace("hello", 1);
check(__LINE__, map, 5, 2, 0);
REQUIRE(0 == map.erase("huh"s));
check(__LINE__, map, 5, 2, 1);
REQUIRE(1 == map.erase("hello"s));
check(__LINE__, map, 5, 2, 2);
}
TEST_CASE_MAP("transparent_equal_range", std::string, size_t, string_hash, std::equal_to<>) {
auto map = map_t();
map.try_emplace("hello", 1);
check(__LINE__, map, 1, 0, 0);
auto range = map.equal_range("hello");
check(__LINE__, map, 2, 0, 0);
REQUIRE(range.first != range.second);
REQUIRE(range.first->first == "hello");
REQUIRE(range.second == map.end());
auto crange = std::as_const(map).equal_range("hello"sv);
check(__LINE__, map, 2, 1, 0);
REQUIRE(crange.first != range.second);
REQUIRE(crange.first->first == "hello");
REQUIRE(crange.second == map.end());
}
TYPE_TO_STRING_MAP(std::string, size_t, string_hash, string_eq);
TEST_CASE_MAP("transparent_string_eq", std::string, size_t, string_hash, string_eq) {
auto map = map_t();
map.try_emplace("hello", 1);
REQUIRE(map.count("hello"));
REQUIRE(map.count("hello"sv));
REQUIRE(map.count("hello"s));
auto ke = map.key_eq();
REQUIRE(ke.counts().size() == 3);
for (auto const& kv : ke.counts()) {
REQUIRE(kv.second == 1U);
}
}
TEST_CASE_MAP("transparent_at", std::string, size_t, string_hash, string_eq) {
auto map = map_t();
map.try_emplace("asdf", 123);
check(__LINE__, map, 1, 0, 0);
auto& vt = map.at("asdf");
check(__LINE__, map, 2, 0, 0);
REQUIRE(vt == 123);
// NOLINTNEXTLINE(llvm-else-after-return,readability-else-after-return)
REQUIRE_THROWS_AS(map.at("nope"sv), std::out_of_range);
check(__LINE__, map, 2, 1, 0);
}
TYPE_TO_STRING_MAP(std::string, size_t, string_hash);
TEST_CASE_MAP("transparent_at_not", std::string, size_t, string_hash) {
// no string_eq, so not is_transparent
auto map = map_t();
map.try_emplace("asdf", 123);
check(__LINE__, map, 0, 0, 1);
auto& vt = map.at("asdf");
check(__LINE__, map, 0, 0, 2);
REQUIRE(vt == 123);
// NOLINTNEXTLINE(llvm-else-after-return,readability-else-after-return)
REQUIRE_THROWS_AS(map.at("nope"), std::out_of_range);
check(__LINE__, map, 0, 0, 3);
}
TEST_CASE_MAP("transparent_insert_or_assign", std::string, size_t, string_hash, string_eq) {
auto map = map_t();
auto r = map.insert_or_assign("asdf", 123U);
check(__LINE__, map, 1, 0, 0);
REQUIRE(r.first->first == "asdf");
REQUIRE(r.first->second == 123U);
REQUIRE(r.second);
r = map.insert_or_assign("asdf", 42U);
check(__LINE__, map, 2, 0, 0);
REQUIRE(r.first->first == "asdf");
REQUIRE(r.first->second == 42U);
REQUIRE(!r.second);
REQUIRE(map.size() == 1U);
}
TEST_CASE_MAP("transparent_insert_or_assign_not", std::string, size_t, string_hash) {
auto map = map_t();
auto r = map.insert_or_assign("asdf", 123U);
check(__LINE__, map, 0, 0, 1);
REQUIRE(r.first->first == "asdf");
REQUIRE(r.first->second == 123U);
REQUIRE(r.second);
r = map.insert_or_assign("asdf", 42U);
check(__LINE__, map, 0, 0, 2);
REQUIRE(r.first->first == "asdf");
REQUIRE(r.first->second == 42U);
REQUIRE(!r.second);
REQUIRE(map.size() == 1U);
}
TEST_CASE_MAP("transparent_insert_or_assign_iterator", std::string, size_t, string_hash, string_eq) {
auto map = map_t();
auto r = map.insert_or_assign(typename map_t::const_iterator{}, "asdf", 123U);
check(__LINE__, map, 1, 0, 0);
REQUIRE(r->first == "asdf");
REQUIRE(r->second == 123U);
r = map.insert_or_assign(typename map_t::const_iterator{}, "asdf", 42U);
check(__LINE__, map, 2, 0, 0);
REQUIRE(r->first == "asdf");
REQUIRE(r->second == 42U);
REQUIRE(map.size() == 1U);
}
TEST_CASE_MAP("transparent_insert_or_assign_iterator_not", std::string, size_t, string_hash) {
auto map = map_t();
auto r = map.insert_or_assign(typename map_t::const_iterator{}, "asdf", 123U);
check(__LINE__, map, 0, 0, 1);
REQUIRE(r->first == "asdf");
REQUIRE(r->second == 123U);
r = map.insert_or_assign(typename map_t::const_iterator{}, "asdf", 42U);
check(__LINE__, map, 0, 0, 2);
REQUIRE(r->first == "asdf");
REQUIRE(r->second == 42U);
REQUIRE(map.size() == 1U);
}
TYPE_TO_STRING_SET(std::string, string_hash, string_eq);
// insert() transparent is only possible with unordered_set, not with unordered_map
TEST_CASE_SET("transparent_set_insert", std::string, string_hash, string_eq) {
auto set = set_t();
set.insert("abcdefg");
check(__LINE__, set, 1, 0, 0);
set.insert("abcdefg");
check(__LINE__, set, 2, 0, 0);
}
TYPE_TO_STRING_SET(std::string, string_hash);
// insert() transparent is only possible with unordered_set, not with unordered_map
TEST_CASE_SET("transparent_set_insert_not", std::string, string_hash) {
auto set = set_t();
set.insert("abcdefg");
check(__LINE__, set, 0, 0, 1);
set.insert("abcdefg");
check(__LINE__, set, 0, 0, 2);
}
// emplace() transparent is only possible with unordered_set, not with unordered_map
TEST_CASE_SET("transparent_set_emplace", std::string, string_hash, string_eq) {
auto set = set_t();
set.emplace("abcdefg");
check(__LINE__, set, 1, 0, 0);
set.emplace("abcdefg");
check(__LINE__, set, 2, 0, 0);
}
// emplace() transparent is only possible with unordered_set, not with unordered_map
TEST_CASE_SET("transparent_set_emplace_not", std::string, string_hash) {
auto set = set_t();
set.emplace("abcdefg");
check(__LINE__, set, 0, 0, 1);
set.emplace("abcdefg");
check(__LINE__, set, 0, 0, 2);
}
struct string_hash_simple {
using is_transparent = void; // enable heterogeneous lookup
using is_avalanching = void; // mark class as high quality avalanching hash
[[nodiscard]] auto operator()(std::string_view str) const noexcept -> uint64_t {
return ankerl::unordered_dense::hash<std::string_view>{}(str);
}
};
TYPE_TO_STRING_MAP(std::string, size_t, string_hash_simple, std::equal_to<>);
TEST_CASE_MAP("transparent_find_simple", std::string, size_t, string_hash_simple, std::equal_to<>) {
auto map = map_t();
map.try_emplace("hello", 1);
auto it = map.find("huh");
REQUIRE(it == map.end());
it = map.find("hello");
REQUIRE(it != map.end());
auto cit = std::as_const(map).find("huh");
REQUIRE(cit == map.end());
REQUIRE(cit == map.cend());
cit = std::as_const(map).find("hello");
REQUIRE(cit != map.end());
// string_view
it = map.find("huh"sv);
REQUIRE(it == map.end());
it = map.find("hello"sv);
REQUIRE(it != map.end());
// string
it = map.find("huh"s);
REQUIRE(it == map.end());
it = map.find("hello"s);
REQUIRE(it != map.end());
}
+67
View File
@@ -0,0 +1,67 @@
#include <ankerl/unordered_dense.h>
#include <app/doctest.h>
#include <cstddef> // for size_t
#include <string> // for allocator, string, operator==
#include <utility> // for pair, move
#include <vector> // for vector
struct regular_type {
// cppcheck-suppress passedByValue
regular_type(std::size_t i, std::string s) noexcept
: m_s(std::move(s))
, m_i(i) {}
friend auto operator==(const regular_type& r1, const regular_type& r2) -> bool {
return r1.m_i == r2.m_i && r1.m_s == r2.m_s;
}
private:
std::string m_s;
std::size_t m_i;
};
TYPE_TO_STRING_MAP(std::string, regular_type);
TEST_CASE_MAP("try_emplace", std::string, regular_type) {
map_t map;
auto ret = map.try_emplace("a", 1U, "b");
REQUIRE(ret.second);
REQUIRE(ret.first == map.find("a"));
REQUIRE(ret.first->second == regular_type(1U, "b"));
REQUIRE(map.size() == 1);
ret = map.try_emplace("c", 2U, "d");
REQUIRE(ret.second);
REQUIRE(ret.first == map.find("c"));
REQUIRE(ret.first->second == regular_type(2U, "d"));
REQUIRE(map.size() == 2U);
std::string key = "c";
ret = map.try_emplace(key, 3U, "dd");
REQUIRE_FALSE(ret.second);
REQUIRE(ret.first == map.find("c"));
REQUIRE(ret.first->second == regular_type(2U, "d"));
REQUIRE(map.size() == 2U);
key = "a";
regular_type value(3U, "dd");
ret = map.try_emplace(key, value);
REQUIRE_FALSE(ret.second);
REQUIRE(ret.first == map.find("a"));
REQUIRE(ret.first->second == regular_type(1U, "b"));
REQUIRE(map.size() == 2U);
auto pos = map.try_emplace(map.end(), "e", 67U, "f");
REQUIRE(pos == map.find("e"));
REQUIRE(pos->second == regular_type(67U, "f"));
REQUIRE(map.size() == 3U);
key = "e";
regular_type value2(66U, "ff");
pos = map.try_emplace(map.begin(), key, value2);
REQUIRE(pos == map.find("e"));
REQUIRE(pos->second == regular_type(67U, "f"));
REQUIRE(map.size() == 3U);
}
+70
View File
@@ -0,0 +1,70 @@
#include <ankerl/unordered_dense.h>
#include <app/doctest.h>
#include <third-party/nanobench.h> // for Rng, doNotOptimizeAway, Bench
#include <string>
#include <string_view>
TEST_CASE("tuple_hash") {
auto m = ankerl::unordered_dense::map<std::pair<int, std::string>, int>();
auto pair_hash = ankerl::unordered_dense::hash<std::pair<int, std::string>>{};
REQUIRE(pair_hash(std::pair<int, std::string>{1, "a"}) != pair_hash(std::pair<int, std::string>{1, "b"}));
m.try_emplace({1, "a"}, 23);
m.try_emplace({1, "b"}, 42);
REQUIRE(m.size() == 2U);
}
TEST_CASE("good_tuple_hash") {
auto hashes = ankerl::unordered_dense::set<uint64_t>();
auto t = std::tuple<uint8_t, uint8_t, uint8_t>();
for (size_t i = 0; i < 256 * 256; ++i) {
std::get<0>(t) = static_cast<uint8_t>(i);
std::get<2>(t) = static_cast<uint8_t>(i / 256);
hashes.emplace(ankerl::unordered_dense::hash<decltype(t)>{}(t));
}
REQUIRE(hashes.size() == 256 * 256);
}
TEST_CASE("tuple_hash_with_stringview") {
using T = std::tuple<int, std::string_view>;
auto t = T();
std::get<0>(t) = 1;
auto str = std::string("hello");
std::get<1>(t) = str;
auto h1 = ankerl::unordered_dense::hash<T>{}(t);
str = "world";
REQUIRE(std::get<1>(t) == std::string{"world"});
auto h2 = ankerl::unordered_dense::hash<T>{}(t);
REQUIRE(h1 != h2);
}
// #include <absl/hash/hash.h>
TEST_CASE("bench_tuple_hash" * doctest::test_suite("bench") * doctest::skip()) {
using T = std::tuple<uint8_t, int, uint16_t, uint64_t>;
auto vecs = std::vector<T>(100);
auto rng = ankerl::nanobench::Rng(123);
for (auto& v : vecs) {
std::get<0>(v) = static_cast<uint8_t>(rng());
std::get<1>(v) = static_cast<int>(rng());
std::get<2>(v) = static_cast<uint16_t>(rng());
std::get<3>(v) = static_cast<uint64_t>(rng());
}
uint64_t h = 0;
ankerl::nanobench::Bench().batch(vecs.size()).run("ankerl hash", [&] {
for (auto const& v : vecs) {
h += ankerl::unordered_dense::hash<T>{}(v);
// h += absl::Hash<T>{}(v);
}
});
ankerl::nanobench::doNotOptimizeAway(h);
}
+59
View File
@@ -0,0 +1,59 @@
#include <ankerl/unordered_dense.h>
#include <app/doctest.h>
#include <cstddef> // for size_t
#include <memory> // for operator!=, unique_ptr, make_unique
#include <utility> // for move, pair
#include <vector> // for vector
TYPE_TO_STRING_MAP(size_t, std::unique_ptr<int>);
TEST_CASE_MAP("unique_ptr", size_t, std::unique_ptr<int>) {
map_t m;
REQUIRE(m.end() == m.find(123));
REQUIRE(m.end() == m.begin());
m[static_cast<size_t>(32)] = std::make_unique<int>(123);
REQUIRE(m.end() != m.begin());
REQUIRE(m.end() == m.find(123));
REQUIRE(m.end() != m.find(32));
for (auto const& kv : m) {
REQUIRE(kv.first == 32);
REQUIRE(kv.second != nullptr);
REQUIRE(*kv.second == 123);
}
m = map_t();
REQUIRE(m.end() == m.begin());
REQUIRE(m.end() == m.find(123));
REQUIRE(m.end() == m.find(32));
map_t empty;
map_t m3(std::move(empty));
REQUIRE(m3.end() == m3.begin());
REQUIRE(m3.end() == m3.find(123));
REQUIRE(m3.end() == m3.find(32));
m3[static_cast<size_t>(32)];
REQUIRE(m3.end() != m3.begin());
REQUIRE(m3.end() == m3.find(123));
REQUIRE(m3.end() != m3.find(32));
empty = map_t{};
map_t m4(std::move(empty));
REQUIRE(m4.count(123) == 0);
REQUIRE(m4.end() == m4.begin());
REQUIRE(m4.end() == m4.find(123));
REQUIRE(m4.end() == m4.find(32));
}
TEST_CASE_MAP("unique_ptr_fill", size_t, std::unique_ptr<int>) {
map_t m;
for (int i = 0; i < 1000; ++i) {
// m.emplace(i % 500, std::make_unique<int>(i));
m.emplace(static_cast<size_t>(i), new int(i)); // NOLINT(cppcoreguidelines-owning-memory)
// element is still constructed, so there's no memory leak here.
// Boost 1.80 behaves differently
m.emplace(static_cast<size_t>(i), new int(i)); // NOLINT(cppcoreguidelines-owning-memory)
}
}
+105
View File
@@ -0,0 +1,105 @@
#include <ankerl/unordered_dense.h>
#include <app/doctest.h>
#include <cstddef> // for size_t
#include <cstdint> // for UINT64_C, uint64_t
#include <string> // for basic_string, allocator, operator==
#include <type_traits> // for integral_constant<>::value, is_same
#include <vector> // for vector
TEST_CASE("unordered_set_asserts") {
using set1_t = ankerl::unordered_dense::set<uint64_t>;
static_assert(std::is_same<typename set1_t::key_type, uint64_t>::value, "key_type same");
static_assert(std::is_same<typename set1_t::value_type, uint64_t>::value, "value_type same");
using set2_t = ankerl::unordered_dense::segmented_set<uint64_t>;
static_assert(std::is_same<typename set2_t::key_type, uint64_t>::value, "key_type same");
static_assert(std::is_same<typename set2_t::value_type, uint64_t>::value, "value_type same");
}
TEST_CASE_SET("unordered_set", uint64_t) {
set_t set;
set.emplace(UINT64_C(123));
REQUIRE(set.size() == 1U);
set.insert(UINT64_C(333));
REQUIRE(set.size() == 2U);
set.erase(UINT64_C(222));
REQUIRE(set.size() == 2U);
set.erase(UINT64_C(123));
REQUIRE(set.size() == 1U);
}
TEST_CASE_SET("unordered_set_string", std::string) {
set_t set;
REQUIRE(set.begin() == set.end());
set.emplace(static_cast<size_t>(2000), 'a');
REQUIRE(set.size() == 1);
REQUIRE(set.begin() != set.end());
std::string const& str = *set.begin();
REQUIRE(str == std::string(static_cast<size_t>(2000), 'a'));
auto it = set.begin();
REQUIRE(++it == set.end());
}
TEST_CASE_SET("unordered_set_eq", std::string) {
set_t set1;
set_t set2;
REQUIRE(set1.size() == set2.size());
REQUIRE(set1 == set2);
REQUIRE(set2 == set1);
set1.emplace("asdf");
// (asdf) == ()
REQUIRE(set1.size() != set2.size());
REQUIRE(set1 != set2);
REQUIRE(set2 != set1);
set2.emplace("huh");
// (asdf) == (huh)
REQUIRE(set1.size() == set2.size());
REQUIRE(set1 != set2);
REQUIRE(set2 != set1);
set1.emplace("huh");
// (asdf, huh) == (huh)
REQUIRE(set1.size() != set2.size());
REQUIRE(set1 != set2);
REQUIRE(set2 != set1);
set2.emplace("asdf");
// (asdf, huh) == (asdf, huh)
REQUIRE(set1.size() == set2.size());
REQUIRE(set1 == set2);
REQUIRE(set2 == set1);
set1.erase("asdf");
// (huh) == (asdf, huh)
REQUIRE(set1.size() != set2.size());
REQUIRE(set1 != set2);
REQUIRE(set2 != set1);
set2.erase("asdf");
// (huh) == (huh)
REQUIRE(set1.size() == set2.size());
REQUIRE(set1 == set2);
REQUIRE(set2 == set1);
set1.clear();
// () == (huh)
REQUIRE(set1.size() != set2.size());
REQUIRE(set1 != set2);
REQUIRE(set2 != set1);
set2.erase("huh");
// () == ()
REQUIRE(set1.size() == set2.size());
REQUIRE(set1 == set2);
REQUIRE(set2 == set1);
}
+58
View File
@@ -0,0 +1,58 @@
#include <ankerl/unordered_dense.h>
#include <app/counter.h>
#include <app/doctest.h>
#include <third-party/nanobench.h>
#include <algorithm> // for max
#include <cstddef> // for size_t
#include <utility> // for move
#include <vector> // for vector
template <typename Map>
void fill(counter& counts, Map& map, ankerl::nanobench::Rng& rng) {
auto n = rng.bounded(20);
for (size_t i = 0; i < n; ++i) {
auto a = rng.bounded(20);
auto b = rng.bounded(20);
map.try_emplace({a, counts}, b, counts);
}
}
TEST_CASE_MAP("vectormap", counter::obj, counter::obj) {
auto counts = counter();
INFO(counts);
auto rng = ankerl::nanobench::Rng(32154);
{
counts("begin");
std::vector<map_t> maps;
// copies
for (size_t i = 0; i < 10; ++i) {
map_t m;
fill(counts, m, rng);
maps.push_back(m);
}
counts("copies");
// move
for (size_t i = 0; i < 10; ++i) {
map_t m;
fill(counts, m, rng);
maps.push_back(std::move(m));
}
counts("move");
// emplace
for (size_t i = 0; i < 10; ++i) {
maps.emplace_back();
fill(counts, maps.back(), rng);
}
counts("emplace");
}
counts("dtor");
REQUIRE(counts.dtor() ==
counts.ctor() + counts.static_default_ctor + counts.copy_ctor() + counts.default_ctor() + counts.move_ctor());
}
@@ -0,0 +1,118 @@
#if defined(__has_include)
# if __has_include(<Windows.h>)
# include <Windows.h>
# pragma message("Windows.h included")
# endif
#endif
#include <ankerl/unordered_dense.h>
#include <app/counter.h>
#include <doctest.h>
#include <initializer_list>
TEST_CASE("unordered_dense_with_windows_h") {
auto counts = counter();
using map_t = ankerl::unordered_dense::map<counter::obj, counter::obj>;
auto map = map_t();
{
auto key = size_t{1};
auto it = map.try_emplace(counter::obj(key, counts), counter::obj(key, counts)).first;
REQUIRE(it != map.end());
REQUIRE(it->first.get() == key);
}
{
auto key = size_t{2};
map.emplace(std::piecewise_construct, std::forward_as_tuple(key, counts), std::forward_as_tuple(key + 77, counts));
}
{
auto key = size_t{3};
map[counter::obj(key, counts)] = counter::obj(key + 123, counts);
}
{
auto key = size_t{4};
map.insert(std::pair<counter::obj, counter::obj>(counter::obj(key, counts), counter::obj(key, counts)));
}
{
auto key = size_t{5};
map.insert_or_assign(counter::obj(key, counts), counter::obj(key + 1, counts));
}
{
auto key = size_t{6};
map.erase(counter::obj(key, counts));
}
{ map = map_t{}; }
{
auto m = map_t{};
m.swap(map);
}
{ map.clear(); }
{
auto s = size_t{7};
map.rehash(s);
}
{
auto s = size_t{8};
map.reserve(s);
}
{
auto key = size_t{9};
auto it = map.find(counter::obj(key, counts));
auto d = std::distance(map.begin(), it);
REQUIRE(0 <= d);
REQUIRE(d <= static_cast<std::ptrdiff_t>(map.size()));
}
{
if (!map.empty()) {
auto idx = static_cast<int>(map.size() / 2);
auto it = map.cbegin() + idx;
auto const& key = it->first;
auto found_it = map.find(key);
REQUIRE(it == found_it);
}
}
{
if (!map.empty()) {
auto it = map.begin() + static_cast<int>(map.size() / 2);
map.erase(it);
}
}
{
auto tmp = map_t();
std::swap(tmp, map);
}
{
map = std::initializer_list<std::pair<counter::obj, counter::obj>>{
{{1, counts}, {2, counts}},
{{3, counts}, {4, counts}},
{{5, counts}, {6, counts}},
};
REQUIRE(map.size() == 3);
}
{
auto first_idx = 0;
auto last_idx = 0;
if (!map.empty()) {
first_idx = static_cast<int>(map.size() / 2);
last_idx = static_cast<int>(map.size() / 2);
if (first_idx > last_idx) {
std::swap(first_idx, last_idx);
}
}
map.erase(map.cbegin() + first_idx, map.cbegin() + last_idx);
}
{
map.~map_t();
counts.check_all_done();
new (&map) map_t();
}
{
bool b = true;
std::erase_if(map, [&](map_t::value_type const& /*v*/) {
b = !b;
return b;
});
}
}