-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtype_set.hpp
230 lines (205 loc) · 7.63 KB
/
type_set.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
#ifndef TYPE_MAP_HPP_INCLUDED
#define TYPE_MAP_HPP_INCLUDED
#include <cstddef>
#include <concepts>
#include <functional>
#include <type_traits>
#include <tuple>
namespace detail {
struct prechecked_tag {};
}
// A type-level set, for building up a set of unique types.
// Uses 'IsEqual' metafunction for type equality testing.
template <template <typename, typename> typename IsEqual, typename... Args>
class type_set {
public:
constexpr type_set() = default;
constexpr type_set(std::tuple<Args...>&& data) : m_data(std::move(data)) {
static_assert(is_valid(), "provided values do not fulfil set invariant");
}
constexpr type_set(const std::tuple<Args...>& data) : m_data(data) {
static_assert(is_valid(), "provided values do not fulfil set invariant");
}
private:
constexpr type_set(detail::prechecked_tag, std::tuple<Args...>&& data)
: m_data(std::move(data))
{}
template <template <typename, typename> typename, typename...>
friend class type_set;
std::tuple<Args...> m_data;
template <std::size_t I>
using ic = std::integral_constant<std::size_t, I>;
static constexpr std::size_t npos = -1;
template <typename Key>
static constexpr std::size_t indexof() {
constexpr auto loop = []<std::size_t I>(auto&& self, ic<I>) {
if constexpr (I < sizeof...(Args)) {
using Elem = std::tuple_element_t<I, std::tuple<Args...>>;
if constexpr (IsEqual<Elem, Key>::value)
return I;
else
return self(self, ic<I+1>{});
} else {
return npos;
}
};
return loop(loop, ic<0>{});
}
public:
// full check to ensure that the type is valid
static constexpr bool is_valid() {
constexpr auto loop = []<std::size_t I>(auto&& self, ic<I>) {
if constexpr (I + 1 < sizeof...(Args)) {
using Lhs = std::tuple_element_t<I, std::tuple<Args...>>;
constexpr auto inner = []<std::size_t J>(auto&& self, ic<J>) {
if constexpr (J < sizeof...(Args)) {
using Rhs = std::tuple_element_t<J, std::tuple<Args...>>;
if constexpr (IsEqual<Lhs, Rhs>::value)
return false;
else
return self(self, ic<J + 1>{});
} else {
return true;
}
};
if constexpr (inner(inner, ic<I + 1>{}))
return self(self, ic<I + 1>{});
else
return false;
} else {
return true;
}
};
return loop(loop, ic<0>{});
}
template <typename Key>
constexpr decltype(auto) get() {
constexpr auto I = indexof<Key>();
static_assert(I != npos, "key not found");
if constexpr (I != npos)
return std::get<indexof<Key>()>(m_data);
}
template <typename Key>
constexpr decltype(auto) get() const {
constexpr auto I = indexof<Key>();
static_assert(I != npos, "key not found");
if constexpr (I != npos)
return std::get<indexof<Key>()>(m_data);
}
template <typename T>
constexpr auto insert(T&& t) && noexcept {
using NewKey = std::remove_cvref_t<T>;
constexpr auto I = indexof<NewKey>();
static_assert(I == npos, "key already exists");
if constexpr (I == npos)
return type_set<IsEqual, Args..., NewKey>(detail::prechecked_tag{},
std::tuple_cat(std::move(m_data), std::tuple<NewKey>(std::forward<T>(t))));
else
return *this;
}
template <typename T>
constexpr auto insert(T&& t) const& {
using NewKey = std::remove_cvref_t<T>;
constexpr auto I = indexof<NewKey>();
static_assert(I == npos, "key already exists");
if constexpr (I == npos)
return type_set<IsEqual, Args..., NewKey>(detail::prechecked_tag{},
std::tuple_cat(m_data, std::tuple<NewKey>(std::forward<T>(t))));
else
return *this;
}
// Access a the first element as specified by the given `Compare`,
// calling `Function` with the result (which should be a generic functor).
// Returns `true` on success, or `false` if no such key exists.
template <typename Compare, typename Function>
constexpr bool inspect(Compare&& key, Function&& func) {
auto loop = [&]<std::size_t I>(auto&& self, ic<I>) {
if constexpr (I == sizeof...(Args)) {
return false;
} else {
using elem_t = std::tuple_element_t<I, std::tuple<Args...>>;
if constexpr (std::regular_invocable<Compare, const elem_t&>
and std::invocable<Function, elem_t&>)
{
if (std::invoke(key, std::get<I>(m_data))) {
std::invoke(std::forward<Function>(func), std::get<I>(m_data));
return true;
}
}
return self(self, ic<I + 1>{});
}
};
return loop(loop, ic<0>{});
}
template <typename Compare, typename Function>
constexpr bool inspect(Compare&& key, Function&& func) const {
return cinspect(std::forward<Compare>(key), std::forward<Function>(func));
}
// Identical to `inspect` but without possibility of mutability
// (sad code duplication life)
template <typename Compare, typename Function>
constexpr bool cinspect(Compare&& key, Function&& func) const {
auto loop = [&]<std::size_t I>(auto&& self, ic<I>) {
if constexpr (I == sizeof...(Args)) {
return false;
} else {
using elem_t = std::tuple_element_t<I, std::tuple<Args...>>;
if constexpr (std::regular_invocable<Compare, const elem_t&>
and std::invocable<Function, const elem_t&>)
{
if (std::invoke(key, std::get<I>(m_data))) {
std::invoke(std::forward<Function>(func), std::get<I>(m_data));
return true;
}
}
return self(self, ic<I + 1>{});
}
};
return loop(loop, ic<0>{});
}
// Runtime check if the given key exists
template <typename Compare>
constexpr bool contains(Compare&& key) const {
return inspect(std::forward<Compare>(key), [](auto&&){});
}
// Convert this type set to use a different comparison function,
// verifying that such a translation is valid
template <template <typename, typename> typename NewIsEqual>
constexpr type_set<NewIsEqual, Args...> map() const {
static_assert(type_set<NewIsEqual, Args...>::is_valid(),
"new key does not maintain set invariants");
return { detail::prechecked_tag{}, m_data };
}
// Convert this type set to use a different comparison function,
// mutating the elements as we go as appropriate,
// verifying that such a translation is valid
template <template <typename, typename> typename NewIsEqual = IsEqual,
typename Function>
constexpr auto map(Function&& mapper) const {
auto transformed = [this, &mapper]<std::size_t... Is>(std::index_sequence<Is...>) {
return std::tuple{ mapper(std::get<Is>(m_data))... };
};
return make_type_set<NewIsEqual>(
transformed(std::make_index_sequence<sizeof...(Args)>{}));
}
// Merge two type sets together,
// erroring on mismatch
template <typename... Others>
constexpr auto merge(const type_set<IsEqual, Others...>& other) const
-> type_set<IsEqual, Args..., Others...>
{
static_assert(type_set<IsEqual, Args..., Others...>::is_valid(),
"the sets share a common key");
return { detail::prechecked_tag{}, std::tuple_cat(m_data, other.m_data) };
}
// TODO: other ref-qualifiers for map and merge
};
template <template <typename, typename> typename IsEqual, typename... Args>
constexpr auto make_type_set(std::tuple<Args...>&& args) {
return type_set<IsEqual, Args...>(std::move(args));
}
template <template <typename, typename> typename IsEqual, typename... Args>
constexpr auto make_type_set(const std::tuple<Args...>& args) {
return type_set<IsEqual, Args...>(args);
}
#endif // TYPE_MAP_HPP_INCLUDED