Merge commit 'f1087e81ecdca5a59ba5ffca684c955c5b38f7c2' as 'third_party/unordered_dense'
This commit is contained in:
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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));
|
||||
}
|
||||
@@ -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
@@ -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));
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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{};
|
||||
}
|
||||
@@ -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
@@ -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());
|
||||
});
|
||||
}
|
||||
@@ -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
@@ -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));
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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));
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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}));
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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>);
|
||||
@@ -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
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user