userver: userver/utils/projected_set.hpp Source File
Loading...
Searching...
No Matches
projected_set.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/utils/projected_set.hpp
4/// @brief @copybrief utils::ProjectedUnorderedSet
5
6#include <functional>
7#include <set>
8#include <type_traits>
9#include <unordered_set>
10#include <utility>
11
12#include <userver/utils/impl/projecting_view.hpp>
13#include <userver/utils/impl/transparent_hash.hpp>
14#include <userver/utils/meta_light.hpp>
15
16USERVER_NAMESPACE_BEGIN
17
18namespace utils {
19
20namespace impl::projected_set {
21
22template <typename Raw, auto Projection>
23using ProjectionResult = std::decay_t<std::invoke_result_t<decltype(Projection), const Raw&>>;
24
25template <typename Raw, auto Projection, typename ResultHash>
26using DefaultedResultHash =
27 std::conditional_t<std::is_void_v<ResultHash>, std::hash<ProjectionResult<Raw, Projection>>, ResultHash>;
28
29template <typename Raw, auto Projection, typename ResultHash>
30struct Hash : public DefaultedResultHash<Raw, Projection, ResultHash> {
31 using is_transparent [[maybe_unused]] = void;
32 using Base = DefaultedResultHash<Raw, Projection, ResultHash>;
33
34 auto operator()(const Raw& value) const noexcept { return Base::operator()(std::invoke(Projection, value)); }
35
36 using Base::operator();
37};
38
39template <typename Raw, auto Projection, typename ResultCompare>
40struct Compare : public ResultCompare {
41 using is_transparent [[maybe_unused]] = void;
42
43 auto operator()(const Raw& lhs, const Raw& rhs) const noexcept {
44 return ResultCompare::operator()(std::invoke(Projection, lhs), std::invoke(Projection, rhs));
45 }
46
47 template <typename T>
48 auto operator()(const Raw& lhs, const T& rhs) const noexcept {
49 return ResultCompare::operator()(std::invoke(Projection, lhs), rhs);
50 }
51
52 template <typename T>
53 auto operator()(const T& lhs, const Raw& rhs) const noexcept {
54 return ResultCompare::operator()(lhs, std::invoke(Projection, rhs));
55 }
56
57 template <typename T, typename U>
58 auto operator()(const T& lhs, const U& rhs) const noexcept {
59 return ResultCompare::operator()(lhs, rhs);
60 }
61};
62
63template <typename Set, typename Value>
64void DoInsert(Set& set, Value&& value) {
65 const auto iter = set.find(value);
66 if (iter == set.end()) {
67 set.insert(set.end(), std::forward<Value>(value));
68 return;
69 }
70
71 using SetValue = std::decay_t<decltype(*iter)>;
72 // 'const_cast' is safe here, because the new key compares equal to the
73 // old one and should have the same ordering (or hash) as the old one.
74 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
75 const_cast<SetValue&>(*iter) = std::forward<Value>(value);
76}
77
78template <typename T>
79using HasHasher = typename T::hasher;
80
81} // namespace impl::projected_set
82
83/// @ingroup userver_universal
84/// @brief A `std::unordered_set` that compares its elements (of type @a Value)
85/// based on their @a Projection. It allows to create, essentially, an
86/// equivalent of `std::unordered_map` where keys are stored inside values.
87///
88/// Usage example:
89/// @snippet utils/projected_set_test.cpp user
90/// @snippet utils/projected_set_test.cpp usage
91///
92/// @see @ref utils::ProjectedInsertOrAssign
93/// @see @ref utils::ProjectedFind
94template <
95 typename Value,
96 auto Projection,
97 typename Hash = void,
98 typename Equal = std::equal_to<>,
99 typename Allocator = std::allocator<Value>>
100using ProjectedUnorderedSet = utils::impl::TransparentSet<
101 Value,
102 impl::projected_set::Hash<Value, Projection, Hash>,
103 impl::projected_set::Compare<Value, Projection, Equal>,
104 Allocator>;
105
106/// @ingroup userver_universal
107/// @brief Same as @ref utils::ProjectedUnorderedSet, but for `std::set`.
108template <typename Value, auto Projection, typename Compare = std::less<>, typename Allocator = std::allocator<Value>>
109using ProjectedSet = std::set<Value, impl::projected_set::Compare<Value, Projection, Compare>, Allocator>;
110
111/// @brief An equivalent of `std::unordered_map::insert_or_assign` for
112/// utils::ProjectedUnorderedSet and utils::ProjectedSet.
113template <typename Container, typename Value>
114void ProjectedInsertOrAssign(Container& set, Value&& value) {
115 impl::projected_set::DoInsert(set, std::forward<Value>(value));
116}
117
118/// @brief An equivalent of `std::unordered_map::find` for @ref utils::ProjectedUnorderedSet
119/// and @ref utils::ProjectedSet.
120///
121/// Only required for pre-C++20 compilers, can just use `set.find(key)` otherwise.
122///
123/// @note Always returns const iterator, even for a non-const `set` parameter.
124template <typename Container, typename Key>
125auto ProjectedFind(Container& set, const Key& key) {
126 if constexpr (meta::IsDetected<impl::projected_set::HasHasher, std::decay_t<Container>>) {
127 return utils::impl::FindTransparent(set, key);
128 } else {
129 return set.find(key);
130 }
131}
132
133namespace impl {
134
135/// @name Mutating elements inside utils::ProjectedUnorderedSet.
136/// @{
137
138/// @brief Used to work around the fact that mutation is prohibited inside utils::ProjectedUnorderedSet.
139///
140/// @warning Use with utmost caution! Mutating the part of the values that serves as key invokes UB.
141template <typename Value>
142class MutableWrapper {
143public:
144 template <typename... Args>
145 /*implicit*/ MutableWrapper(Args&&... args)
146 : value_(std::forward<Args>(args)...)
147 {}
148
149 Value& operator*() const { return value_; }
150 Value* operator->() const { return std::addressof(value_); }
151
152private:
153 mutable Value value_;
154};
155
156template <typename Container, typename Key>
157auto ProjectedFindOrNullptrUnsafe(Container& set, const Key& key) {
158 auto iter = utils::ProjectedFind(set, key);
159 if constexpr (std::is_const_v<Container>) {
160 return iter == set.end() ? nullptr : std::addressof(std::as_const(**iter));
161 } else {
162 return iter == set.end() ? nullptr : std::addressof(**iter);
163 }
164}
165/// @}
166
167} // namespace impl
168
169namespace impl::projected_set {
170
171// Comparing Projected*Set results in only Projections being compared, which
172// breaks value semantics. Unfortunately, if we define them as aliases of
173// std::*set, we can't have operator== compare full values. The least bad
174// decision in this case is to prohibit the comparison.
175template <
176 typename V1,
177 const auto& P1,
178 typename H1,
179 typename E1,
180 typename A1,
181 typename V2,
182 const auto& P2,
183 typename H2,
184 typename E2,
185 typename A2>
186void operator==(
187 const ProjectedUnorderedSet<V1, P1, H1, E1, A1>& lhs,
188 const ProjectedUnorderedSet<V2, P2, H2, E2, A2>& rhs
189) = delete;
190
191template <
192 typename V1,
193 const auto& P1,
194 typename H1,
195 typename E1,
196 typename A1,
197 typename V2,
198 const auto& P2,
199 typename H2,
200 typename E2,
201 typename A2>
202void operator!=(
203 const ProjectedUnorderedSet<V1, P1, H1, E1, A1>& lhs,
204 const ProjectedUnorderedSet<V2, P2, H2, E2, A2>& rhs
205) = delete;
206
207template <typename V1, const auto& P1, typename C1, typename A1, typename V2, const auto& P2, typename C2, typename A2>
208void operator==(const ProjectedSet<V1, P1, C1, A1>& lhs, const ProjectedSet<V2, P2, C2, A2>& rhs) = delete;
209
210template <typename V1, const auto& P1, typename C1, typename A1, typename V2, const auto& P2, typename C2, typename A2>
211void operator!=(const ProjectedSet<V1, P1, C1, A1>& lhs, const ProjectedSet<V2, P2, C2, A2>& rhs) = delete;
212
213} // namespace impl::projected_set
214
215} // namespace utils
216
217USERVER_NAMESPACE_END