Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

slot_map: Do not require Container to provide reverse_iterator typedefs. #122

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 77 additions & 14 deletions SG14/slot_map.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

#pragma once

#include <iterator>
#include <type_traits>
#include <utility>
#include <vector>
Expand All @@ -37,12 +38,63 @@

namespace stdext {

namespace slot_map_detail {

template<size_t I> struct priority_tag : public priority_tag<I-1> {};
template<> struct priority_tag<0> {};

template<class Ctr, class SizeType>
inline auto reserve_if_possible(Ctr&, SizeType, priority_tag<0>) -> void {}

template<class Ctr, class SizeType>
inline auto reserve_if_possible(Ctr& ctr, SizeType n, priority_tag<1>) -> decltype(void(ctr.reserve(n)))
{
ctr.reserve(n);
}

template<class Ctr, class SizeType>
inline void reserve_if_possible(Ctr& ctr, const SizeType& n)
{
slot_map_detail::reserve_if_possible(ctr, n, priority_tag<1>{});
}

template<class T> using iterator_category_t = typename std::iterator_traits<T>::iterator_category;
template<class T> struct is_bidirectional_iterator : std::is_convertible<iterator_category_t<T>, std::bidirectional_iterator_tag> {};

template<class...> using void_t = void;
template<class T, class=void> struct has_reverse_iterators : std::false_type {};
template<class T> struct has_reverse_iterators<T, void_t<typename T::reverse_iterator, typename T::const_reverse_iterator>> : std::true_type {};

template<class CRTP, class ContainerT, bool Reversible = slot_map_detail::has_reverse_iterators<ContainerT>::value>
struct reverse_iterator_methods {};

template<class CRTP, class ContainerT>
struct reverse_iterator_methods<CRTP, ContainerT, true> {
using container_type = ContainerT;
using reverse_iterator = typename container_type::reverse_iterator;
using const_reverse_iterator = typename container_type::const_reverse_iterator;

// All begin() and end() variations have O(1) time and space complexity.
//
constexpr reverse_iterator rbegin() { return values().rbegin(); }
constexpr reverse_iterator rend() { return values().rend(); }
constexpr const_reverse_iterator rbegin() const { return values().rbegin(); }
constexpr const_reverse_iterator rend() const { return values().rend(); }
constexpr const_reverse_iterator crbegin() const { return values().rbegin(); }
constexpr const_reverse_iterator crend() const { return values().rend(); }
private:
ContainerT& values() { return static_cast<CRTP*>(this)->values_; }
const ContainerT& values() const { return static_cast<const CRTP*>(this)->values_; }
};

} // namespace slot_map_detail

template<
class T,
class Key = std::pair<unsigned, unsigned>,
template<class...> class Container = std::vector
>
class slot_map
class slot_map : public slot_map_detail::reverse_iterator_methods<slot_map<T, Key, Container>, Container<T>>
{
#if __cplusplus >= 201703L
static constexpr auto get_index(const Key& k) { const auto& [idx, gen] = k; return idx; }
Expand All @@ -58,6 +110,8 @@ class slot_map

using slot_iterator = typename Container<Key>::iterator;

template<class, class, bool> friend struct slot_map_detail::reverse_iterator_methods;

public:
using key_type = Key;
using mapped_type = T;
Expand All @@ -72,13 +126,12 @@ class slot_map
using const_pointer = typename container_type::const_pointer;
using iterator = typename container_type::iterator;
using const_iterator = typename container_type::const_iterator;
using reverse_iterator = typename container_type::reverse_iterator;
using const_reverse_iterator = typename container_type::const_reverse_iterator;

using size_type = typename container_type::size_type;
using value_type = typename container_type::value_type;

static_assert(std::is_same<value_type, mapped_type>::value, "Container<T>::value_type must be identical to T");
static_assert(slot_map_detail::is_bidirectional_iterator<iterator>::value, "Container<T> must have bidirectional iterators");

constexpr slot_map() = default;
constexpr slot_map(const slot_map&) = default;
Expand Down Expand Up @@ -160,12 +213,6 @@ class slot_map
constexpr const_iterator end() const { return values_.end(); }
constexpr const_iterator cbegin() const { return values_.begin(); }
constexpr const_iterator cend() const { return values_.end(); }
constexpr reverse_iterator rbegin() { return values_.rbegin(); }
constexpr reverse_iterator rend() { return values_.rend(); }
constexpr const_reverse_iterator rbegin() const { return values_.rbegin(); }
constexpr const_reverse_iterator rend() const { return values_.rend(); }
constexpr const_reverse_iterator crbegin() const { return values_.rbegin(); }
constexpr const_reverse_iterator crend() const { return values_.rend(); }

// Functions for checking the size and capacity of the adapted container
// have the same complexity as the adapted container.
Expand All @@ -176,16 +223,32 @@ class slot_map
constexpr bool empty() const { return values_.size() == 0; }
constexpr size_type size() const { return values_.size(); }
// constexpr size_type max_size() const; TODO, NO SEMANTICS
constexpr size_type capacity() const { return values_.capacity(); }
constexpr void reserve(size_type n) { values_.reserve(n); reserve_slots(n); }

// Functions for accessing and modifying the capacity of the slots container.
constexpr void reserve(size_type n) {
slot_map_detail::reserve_if_possible(values_, n);
slot_map_detail::reserve_if_possible(reverse_map_, n);
reserve_slots(n);
}

template<class C = Container<T>, class = decltype(std::declval<const C&>().capacity())>
constexpr size_type capacity() const {
return values_.capacity();
}

// Functions for accessing and modifying the size of the slots container.
// These are beneficial as allocating more slots than values will cause the
// generation counter increases to be more evenly distributed across the slots.
// TODO [ajo]: The above comment is false, at least for this implementation.
//
constexpr void reserve_slots(size_type n) { slots_.reserve(n); reverse_map_.reserve(n); }
constexpr size_type capacity_slots() const { return slots_.capacity(); }
constexpr void reserve_slots(size_type n) {
slot_map_detail::reserve_if_possible(slots_, n);
while (slots_.size() < n) {
auto idx = next_available_slot_index_;
next_available_slot_index_ = slots_.size();
slots_.emplace_back(key_type{idx, key_generation_type{}});
}
}
constexpr size_type slot_count() const { return slots_.size(); }

// These operations have O(1) time and space complexity.
// When size() == capacity() an allocation is required
Expand Down
98 changes: 92 additions & 6 deletions SG14_test/slot_map_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ struct Vector {
using size_type = unsigned;
using iterator = T*;
using const_iterator = const T*;
using reverse_iterator = std::reverse_iterator<T*>;
using const_reverse_iterator = std::reverse_iterator<const T*>;

Vector() = default;
template<class T_ = T, class = std::enable_if_t<std::is_copy_constructible<T_>::value>>
Expand Down Expand Up @@ -278,6 +276,51 @@ static void EraseRangeTest()
}
}

template<class SM>
static void ReserveTest()
{
using T = typename SM::mapped_type;
SM sm;
auto k = sm.emplace(Monad<T>::from_value(1));
(void)k;
assert(sm.size() == 1);

auto original_cap = sm.slot_count();
static_assert(std::is_same<decltype(original_cap), typename SM::size_type>::value, "");
assert(original_cap >= 1);

sm.reserve_slots(original_cap + 3);
assert(sm.slot_count() >= original_cap + 3);
assert(sm.size() == 1);

sm.emplace(Monad<T>::from_value(2));
sm.emplace(Monad<T>::from_value(3));
sm.emplace(Monad<T>::from_value(4));
assert(sm.size() == 4);
}

template<class SM, class = decltype(std::declval<const SM&>().capacity())>
static void VerifyCapacityExists(bool expected)
{
assert(expected);
SM sm;
auto n = sm.capacity();
static_assert(std::is_same<decltype(n), typename SM::size_type>::value, "");
assert(n == 0);
sm.reserve(100);
assert(sm.capacity() >= 100);
assert(sm.slot_count() >= 100);
}

template<class SM, class Bool>
void VerifyCapacityExists(Bool expected)
{
assert(not expected);
SM sm;
sm.reserve(100);
assert(sm.slot_count() >= 100);
}

static void TypedefTests()
{
if (true) {
Expand Down Expand Up @@ -347,8 +390,6 @@ static void TypedefTests()
static_assert(std::is_same<typename SM::const_pointer, const int*>::value, "");
static_assert(std::is_same<typename SM::iterator, TestContainer::Vector<int>::iterator>::value, "");
static_assert(std::is_same<typename SM::const_iterator, TestContainer::Vector<int>::const_iterator>::value, "");
static_assert(std::is_same<typename SM::reverse_iterator, TestContainer::Vector<int>::reverse_iterator>::value, "");
static_assert(std::is_same<typename SM::const_reverse_iterator, TestContainer::Vector<int>::const_reverse_iterator>::value, "");
static_assert(std::is_same<typename SM::size_type, unsigned>::value, "");
static_assert(std::is_same<typename SM::value_type, int>::value, "");
}
Expand All @@ -374,63 +415,108 @@ static void TypedefTests()
#endif // __cplusplus >= 201703L
}

template<class SM, class = decltype(std::declval<SM&>().rbegin())>
void VerifyRbeginExists(bool expected)
{
assert(expected);
SM sm;
auto r1 = sm.rbegin();
auto r2 = sm.rend();
auto cr1 = sm.crbegin();
auto cr2 = sm.crend();
assert(r1 == r2);
assert(cr1 == cr2);
static_assert(std::is_same<decltype(r1), typename SM::reverse_iterator>::value, "");
static_assert(std::is_same<decltype(r2), typename SM::reverse_iterator>::value, "");
static_assert(std::is_same<decltype(cr1), typename SM::const_reverse_iterator>::value, "");
static_assert(std::is_same<decltype(cr2), typename SM::const_reverse_iterator>::value, "");
static_assert(std::is_same<typename SM::reverse_iterator, typename SM::container_type::reverse_iterator>::value, "");
static_assert(std::is_same<typename SM::const_reverse_iterator, typename SM::container_type::const_reverse_iterator>::value, "");
}

template<class SM, class Bool>
void VerifyRbeginExists(Bool expected)
{
assert(not expected);

}

void sg14_test::slot_map_test()
{
TypedefTests();

// Test the most basic slot_map.
using slot_map_1 = stdext::slot_map<int>;
VerifyRbeginExists<slot_map_1>(true);
BasicTests<slot_map_1>(42, 37);
FullContainerStressTest<slot_map_1>([]() { return 1; });
InsertEraseStressTest<slot_map_1>([i=3]() mutable { return ++i; });
EraseInLoopTest<slot_map_1>();
EraseRangeTest<slot_map_1>();
ReserveTest<slot_map_1>();
VerifyCapacityExists<slot_map_1>(true);

// Test slot_map with a custom key type (C++14 destructuring).
using slot_map_2 = stdext::slot_map<unsigned long, TestKey::key_16_8_t>;
VerifyRbeginExists<slot_map_2>(true);
BasicTests<slot_map_2>(425, 375);
FullContainerStressTest<slot_map_2>([]() { return 42; });
InsertEraseStressTest<slot_map_2>([i=5]() mutable { return ++i; });
EraseInLoopTest<slot_map_2>();
EraseRangeTest<slot_map_2>();
ReserveTest<slot_map_2>();
VerifyCapacityExists<slot_map_2>(true);

#if __cplusplus >= 201703L
// Test slot_map with a custom key type (C++17 destructuring).
using slot_map_3 = stdext::slot_map<int, TestKey::key_11_5_t>;
VerifyRbeginExists<slot_map_3>(true);
BasicTests<slot_map_3>(42, 37);
FullContainerStressTest<slot_map_3>([]() { return 42; });
InsertEraseStressTest<slot_map_3>([i=3]() mutable { return ++i; });
EraseInLoopTest<slot_map_3>();
EraseRangeTest<slot_map_3>();
ReserveTest<slot_map_3>();
VerifyCapacityExists<slot_map_3>(true);
#endif // __cplusplus >= 201703L

// Test slot_map with a custom (but standard and random-access) container type.
using slot_map_4 = stdext::slot_map<int, std::pair<unsigned, unsigned>, std::deque>;
VerifyRbeginExists<slot_map_4>(true);
BasicTests<slot_map_4>(415, 315);
FullContainerStressTest<slot_map_4>([]() { return 37; });
InsertEraseStressTest<slot_map_4>([i=7]() mutable { return ++i; });
EraseInLoopTest<slot_map_4>();
EraseRangeTest<slot_map_4>();
ReserveTest<slot_map_4>();
VerifyCapacityExists<slot_map_4>(false);

// Test slot_map with a custom (non-standard, random-access) container type.
using slot_map_5 = stdext::slot_map<int, std::pair<unsigned, unsigned>, TestContainer::Vector>;
VerifyRbeginExists<slot_map_5>(false);
BasicTests<slot_map_5>(415, 315);
FullContainerStressTest<slot_map_5>([]() { return 37; });
InsertEraseStressTest<slot_map_5>([i=7]() mutable { return ++i; });
EraseInLoopTest<slot_map_5>();
EraseRangeTest<slot_map_5>();
ReserveTest<slot_map_5>();
VerifyCapacityExists<slot_map_5>(false);

// Test slot_map with a custom (standard, bidirectional-access) container type.
using slot_map_6 = stdext::slot_map<int, std::pair<unsigned, unsigned>, std::list>;
VerifyRbeginExists<slot_map_6>(true);
BasicTests<slot_map_6>(415, 315);
FullContainerStressTest<slot_map_6>([]() { return 37; });
InsertEraseStressTest<slot_map_6>([i=7]() mutable { return ++i; });
EraseInLoopTest<slot_map_6>();
EraseRangeTest<slot_map_6>();
ReserveTest<slot_map_6>();
VerifyCapacityExists<slot_map_6>(false);

// Test slot_map with a move-only value_type.
// Sadly, standard containers do not propagate move-only-ness, so we must use our custom Vector instead.
using slot_map_7 = stdext::slot_map<std::unique_ptr<int>, std::pair<unsigned, int>, TestContainer::Vector>;
VerifyRbeginExists<slot_map_7>(false);
static_assert(std::is_move_constructible<slot_map_7>::value, "");
static_assert(std::is_move_assignable<slot_map_7>::value, "");
static_assert(! std::is_copy_constructible<slot_map_7>::value, "");
Expand All @@ -440,6 +526,8 @@ void sg14_test::slot_map_test()
InsertEraseStressTest<slot_map_7>([i=7]() mutable { return std::make_unique<int>(++i); });
EraseInLoopTest<slot_map_7>();
EraseRangeTest<slot_map_7>();
ReserveTest<slot_map_7>();
VerifyCapacityExists<slot_map_7>(false);
}

#if defined(__cpp_concepts)
Expand All @@ -459,8 +547,6 @@ concept bool SlotMapContainer =
typename Ctr<T>::const_pointer;
typename Ctr<T>::iterator;
typename Ctr<T>::const_iterator;
typename Ctr<T>::reverse_iterator;
typename Ctr<T>::const_reverse_iterator;
{ c.emplace_back(t) };
{ c.pop_back() };
{ c.begin() } -> typename Ctr<T>::iterator;
Expand Down