From d470fe73468f6475a12bb885adf2e33aeacd0782 Mon Sep 17 00:00:00 2001 From: lisyarus Date: Thu, 26 Nov 2020 22:01:57 +0300 Subject: [PATCH] Add a heterogeneous container (with type list specified at compile-time) & tests --- .../psemek/util/heterogeneous_container.hpp | 378 ++++++++++++++++++ libs/util/tests/heterogeneous_container.cpp | 156 ++++++++ 2 files changed, 534 insertions(+) create mode 100644 libs/util/include/psemek/util/heterogeneous_container.hpp create mode 100644 libs/util/tests/heterogeneous_container.cpp diff --git a/libs/util/include/psemek/util/heterogeneous_container.hpp b/libs/util/include/psemek/util/heterogeneous_container.hpp new file mode 100644 index 00000000..08ceebcf --- /dev/null +++ b/libs/util/include/psemek/util/heterogeneous_container.hpp @@ -0,0 +1,378 @@ +#pragma once + +#include + +#include +#include +#include + +namespace psemek::util +{ + + namespace detail + { + + constexpr std::size_t count_binary_digits(std::size_t n) + { + std::size_t result = 0; + for (; n > 0; n >>= 1) + ++result; + return result; + } + + template + struct one_of; + + template + struct one_of + : std::false_type + {}; + + template + struct one_of + : std::bool_constant< + std::is_same_v || + one_of::value + > + {}; + + template + static constexpr bool one_of_v = one_of::value; + + template + struct index_of; + + template + struct index_of + { + // there is no good value here, but we have to break recursion + static constexpr std::size_t value = -1; + }; + + template + struct index_of + { + static constexpr std::size_t value = + std::is_same_v + ? 0 + : 1 + index_of::value; + }; + + template + static constexpr std::size_t index_of_v = index_of::value; + + template + void tuple_for_each_impl(F && f, T & t, std::index_sequence) + { + using expander = int[]; + (void) expander { 0, (f(std::get(t), Is), 0) ... }; + } + + template + void tuple_for_each(F && f, T & t) + { + tuple_for_each_impl(std::forward(f), t, std::make_index_sequence>{}); + } + + template + struct tuple_apply_at_impl; + + template + struct tuple_apply_at_impl> + { + static decltype(auto) apply(F && f, T & t, std::size_t) + { + throw std::out_of_range("Bad tuple index"); + return std::forward(f)(std::get<0>(t)); + } + }; + + + template + struct tuple_apply_at_impl> + { + static decltype(auto) apply(F && f, T & t, std::size_t i) + { + if (i == I) + { + return std::forward(f)(std::get(t)); + } + return tuple_apply_at_impl>::apply(std::forward(f), t, i); + } + }; + + template + decltype(auto) tuple_apply_at(F && f, T & t, std::size_t i) + { + if (i >= std::tuple_size_v) + throw std::out_of_range("Bad tuple index"); + + return tuple_apply_at_impl>>::apply(std::forward(f), t, i); + } + + } + + struct bad_handle + : std::out_of_range + { + bad_handle() + : std::out_of_range("Bad handle") + {} + }; + + template + struct heterogeneous_container + { + using types = std::tuple; + + using any = std::variant; + + static constexpr std::size_t types_count = sizeof...(Types); + static_assert(types_count > 0); + + using handle = Handle; + static_assert(std::is_integral_v && std::is_unsigned_v); + + static std::size_t max_size() + { + return std::numeric_limits::max(); + } + + static std::size_t max_single_size() + { + return max_size() >> types_mask_bits; + } + + template + std::enable_if_t, handle> + insert(T value); + + handle insert(any value); + + void erase(handle h); + + void clear(); + + any get(handle h) const; + + template + std::enable_if_t, T const &> + get(handle h) const; + + template + std::enable_if_t, T &> + get(handle h); + + // TODO: arbitrary-arity visitors + + template + decltype(auto) visit(F && f, handle h1) const; + + template + decltype(auto) visit(F && f, handle h1, handle h2) const; + + template + decltype(auto) visit(F && f, handle h1); + + template + decltype(auto) visit(F && f, handle h1, handle h2); + + std::size_t size() const; + + bool empty() const; + + private: + + static constexpr std::size_t handle_bits = detail::count_binary_digits(static_cast(-1)); + + static constexpr std::size_t types_mask_bits = detail::count_binary_digits(std::max(1, types_count - 1)); + + static constexpr handle type_mask = static_cast(-1) ^ (static_cast(-1) >> types_mask_bits); + + std::tuple...> storage_; + + static std::pair unpack_handle(handle h) + { + std::size_t const i = (h & type_mask) >> (handle_bits - types_mask_bits); + handle const hh = h & (~type_mask); + return {i, hh}; + } + + static handle pack_handle(std::size_t i, handle h) + { + return h | static_cast(i << (handle_bits - types_mask_bits)); + } + }; + + template + template + std::enable_if_t, Handle> + heterogeneous_container::insert(T value) + { + static constexpr std::size_t i = detail::index_of_v; + auto h = std::get(storage_).insert(std::move(value)); + return pack_handle(i, h); + } + + template + Handle heterogeneous_container::insert(any value) + { + std::visit([this](auto && value){ this->insert(value); }, value); + } + + template + void heterogeneous_container::erase(handle h) + { + auto [i, hh] = unpack_handle(h); + if (i >= types_count) + throw bad_handle{}; + detail::tuple_apply_at([hh](auto & t) -> void { + t.erase(hh); + }, storage_, i); + } + + template + void heterogeneous_container::clear() + { + detail::tuple_for_each([](auto & t, auto){ + t.clear(); + }, storage_); + } + + template + heterogeneous_container::any heterogeneous_container::get(handle h) const + { + auto [i, hh] = unpack_handle(h); + return detail::tuple_apply_at([hh](auto & t) -> any { + return t[hh]; + }, storage_, i); + } + + template + template + std::enable_if_t, T const &> + heterogeneous_container::get(handle h) const + { + T const * res = nullptr; + std::pair const p = unpack_handle(h); + std::size_t const i = p.first; + handle const hh = p.second; + detail::tuple_for_each([i, hh, &res](auto & t, std::size_t j){ + if constexpr (std::is_same_v, T>) + { + if (i != j) + throw std::bad_any_cast{}; + res = &t[hh]; + } + }, storage_); + if (!res) + throw std::bad_any_cast{}; + + return *res; + } + + template + template + std::enable_if_t, T &> + heterogeneous_container::get(handle h) + { + T * res = nullptr; + std::pair const p = unpack_handle(h); + std::size_t const i = p.first; + handle const hh = p.second; + detail::tuple_for_each([i, hh, &res](auto & t, std::size_t j){ + if constexpr (std::is_same_v, T>) + { + if (i != j) + throw std::bad_any_cast{}; + res = &t[hh]; + } + }, storage_); + if (!res) + throw std::bad_any_cast{}; + + return *res; + } + + template + template + decltype(auto) heterogeneous_container::visit(F && f, handle h1) const + { + std::pair const p1 = unpack_handle(h1); + std::size_t const i1 = p1.first; + handle const hh1 = p1.second; + + return detail::tuple_apply_at([hh1, &f](auto & t1){ + return std::forward(f)(t1[hh1]); + }, storage_, i1); + } + + template + template + decltype(auto) heterogeneous_container::visit(F && f, handle h1, handle h2) const + { + std::pair const p1 = unpack_handle(h1); + std::size_t const i1 = p1.first; + handle const hh1 = p1.second; + + std::pair const p2 = unpack_handle(h2); + std::size_t const i2 = p2.first; + handle const hh2 = p2.second; + + return detail::tuple_apply_at([i2, hh1, hh2, &f, this](auto & t1){ + return detail::tuple_apply_at([hh1, hh2, &f, &t1](auto & t2){ + return std::forward(f)(t1[hh1], t2[hh2]); + }, storage_, i2); + }, storage_, i1); + } + + template + template + decltype(auto) heterogeneous_container::visit(F && f, handle h1) + { + std::pair const p1 = unpack_handle(h1); + std::size_t const i1 = p1.first; + handle const hh1 = p1.second; + + return detail::tuple_apply_at([hh1, &f](auto & t1){ + return std::forward(f)(t1[hh1]); + }, storage_, i1); + } + + template + template + decltype(auto) heterogeneous_container::visit(F && f, handle h1, handle h2) + { + std::pair const p1 = unpack_handle(h1); + std::size_t const i1 = p1.first; + handle const hh1 = p1.second; + + std::pair const p2 = unpack_handle(h2); + std::size_t const i2 = p2.first; + handle const hh2 = p2.second; + + return detail::tuple_apply_at([i2, hh1, hh2, &f, this](auto & t1){ + return detail::tuple_apply_at([hh1, hh2, &f, &t1](auto & t2){ + return std::forward(f)(t1[hh1], t2[hh2]); + }, storage_, i2); + }, storage_, i1); + } + + template + std::size_t heterogeneous_container::size() const + { + std::size_t res = 0; + detail::tuple_for_each([&res](auto & t, auto){ res += t.size(); }, storage_); + return res; + } + + template + bool heterogeneous_container::empty() const + { + bool res = true; + detail::tuple_for_each([&res](auto & t, auto){ res &= t.empty(); }, storage_); + return res; + } + + +} diff --git a/libs/util/tests/heterogeneous_container.cpp b/libs/util/tests/heterogeneous_container.cpp new file mode 100644 index 00000000..6830b04c --- /dev/null +++ b/libs/util/tests/heterogeneous_container.cpp @@ -0,0 +1,156 @@ +#include + +#include + +using namespace psemek::util; + +using container = heterogeneous_container; + +test_case(util_heterogeneous__container_empty) +{ + container c; + + expect(c.empty()); + expect_equal(c.size(), 0); +} + +test_case(util_heterogeneous__container_max__size) +{ + container c; + + expect_gequal(c.max_single_size(), 1 << 20); +} + +test_case(util_heterogeneous__container_size) +{ + container c; + + auto c0 = c.insert('0'); + expect_equal(c.size(), 1); + expect(!c.empty()); + + auto c1 = c.insert('1'); + expect_equal(c.size(), 2); + expect(!c.empty()); + + auto c2 = c.insert('2'); + expect_equal(c.size(), 3); + expect(!c.empty()); + + auto d0 = c.insert(0.0); + expect_equal(c.size(), 4); + expect(!c.empty()); + + auto d1 = c.insert(1.0); + expect_equal(c.size(), 5); + expect(!c.empty()); + + auto d2 = c.insert(2.0); + expect_equal(c.size(), 6); + expect(!c.empty()); + + c.erase(c2); + expect_equal(c.size(), 5); + expect(!c.empty()); + + c.erase(d1); + expect_equal(c.size(), 4); + expect(!c.empty()); + + c.erase(d0); + expect_equal(c.size(), 3); + expect(!c.empty()); + + c.erase(c0); + expect_equal(c.size(), 2); + expect(!c.empty()); + + c.erase(d2); + expect_equal(c.size(), 1); + expect(!c.empty()); + + c.erase(c1); + expect_equal(c.size(), 0); + expect(c.empty()); +} + +test_case(util_heterogeneous__container_get) +{ + container c; + + auto c0 = c.insert('0'); + expect_equal(c.get(c0), '0'); + expect_equal(c.get(c0).index(), 0); + expect_equal(std::get<0>(c.get(c0)), '0'); + expect_equal(std::get(c.get(c0)), '0'); + + auto c1 = c.insert('1'); + expect_equal(c.get(c1), '1'); + expect_equal(c.get(c1).index(), 0); + expect_equal(std::get<0>(c.get(c1)), '1'); + expect_equal(std::get(c.get(c1)), '1'); + + auto f0 = c.insert(3.14f); + expect_equal(c.get(f0), 3.14f); + expect_equal(c.get(f0).index(), 2); + expect_equal(std::get<2>(c.get(f0)), 3.14f); + expect_equal(std::get(c.get(f0)), 3.14f); + + expect_throw(c.get(c0), std::bad_any_cast); + expect_throw(c.get(c1), std::bad_any_cast); + expect_throw(c.get(f0), std::bad_any_cast); +} + +test_case(util_heterogeneous__container_visit) +{ + container c; + + auto c0 = c.insert('0'); + auto f0 = c.insert(3.14f); + + c.visit([](auto const & v){ + if constexpr (std::is_same_v, char>) + { + expect_equal(v, '0'); + } + else + { + expect(false); + } + }, c0); + + c.visit([](auto const & v){ + if constexpr (std::is_same_v, float>) + { + expect_equal(v, 3.14f); + } + else + { + expect(false); + } + }, f0); + + c.visit([](auto const & vc, auto const & vf){ + if constexpr (std::is_same_v, char> && std::is_same_v, float>) + { + expect_equal(vc, '0'); + expect_equal(vf, 3.14f); + } + else + { + expect(false); + } + }, c0, f0); + + c.visit([](auto const & vf, auto const & vc){ + if constexpr (std::is_same_v, char> && std::is_same_v, float>) + { + expect_equal(vc, '0'); + expect_equal(vf, 3.14f); + } + else + { + expect(false); + } + }, f0, c0); +}