osrm-backend/third_party/unordered_dense/test/unit/transparent.cpp

421 lines
13 KiB
C++

#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());
}