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, success] = set.insert(std::forward<Value>(value));
66 if (!success) {
67 using SetValue = std::decay_t<decltype(*iter)>;
68 // 'const_cast' is safe here, because the new key compares equal to the
69 // old one and should have the same ordering (or hash) as the old one.
70 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
71 const_cast<SetValue&>(*iter) = std::forward<Value>(value);
72 }
73}
74
75template <typename T>
76using HasHasher = typename T::hasher;
77
78} // namespace impl::projected_set
79
80/// @ingroup userver_universal
81/// @brief A `std::unordered_set` that compares its elements (of type @a Value)
82/// based on their @a Projection. It allows to create, essentially, an
83/// equivalent of `std::unordered_map` where keys are stored inside values.
84///
85/// Usage example:
86/// @snippet utils/projected_set_test.cpp user
87/// @snippet utils/projected_set_test.cpp usage
88///
89/// @see @ref utils::ProjectedInsertOrAssign
90/// @see @ref utils::ProjectedFind
91template <
92 typename Value,
93 auto Projection,
94 typename Hash = void,
95 typename Equal = std::equal_to<>,
96 typename Allocator = std::allocator<Value>>
97using ProjectedUnorderedSet = utils::impl::TransparentSet<
98 Value,
99 impl::projected_set::Hash<Value, Projection, Hash>,
100 impl::projected_set::Compare<Value, Projection, Equal>,
101 Allocator>;
102
103/// @ingroup userver_universal
104/// @brief Same as @ref utils::ProjectedUnorderedSet, but for `std::set`.
105template <typename Value, auto Projection, typename Compare = std::less<>, typename Allocator = std::allocator<Value>>
106using ProjectedSet = std::set<Value, impl::projected_set::Compare<Value, Projection, Compare>, Allocator>;
107
108/// @brief An equivalent of `std::unordered_map::insert_or_assign` for
109/// utils::ProjectedUnorderedSet and utils::ProjectedSet.
110template <typename Container, typename Value>
111void ProjectedInsertOrAssign(Container& set, Value&& value) {
112 impl::projected_set::DoInsert(set, std::forward<Value>(value));
113}
114
115/// @brief An equivalent of `std::unordered_map::find` for @ref utils::ProjectedUnorderedSet
116/// and @ref utils::ProjectedSet.
117///
118/// Only required for pre-C++20 compilers, can just use `set.find(key)` otherwise.
119///
120/// @note Always returns const iterator, even for a non-const `set` parameter.
121template <typename Container, typename Key>
122auto ProjectedFind(Container& set, const Key& key) {
123 if constexpr (meta::IsDetected<impl::projected_set::HasHasher, std::decay_t<Container>>) {
124 return utils::impl::FindTransparent(set, key);
125 } else {
126 return set.find(key);
127 }
128}
129
130namespace impl {
131
132/// @name Mutating elements inside utils::ProjectedUnorderedSet.
133/// @{
134
135/// @brief Used to work around the fact that mutation is prohibited inside utils::ProjectedUnorderedSet.
136///
137/// @warning Use with utmost caution! Mutating the part of the values that serves as key invokes UB.
138template <typename Value>
139class MutableWrapper {
140public:
141 template <typename... Args>
142 /*implicit*/ MutableWrapper(Args&&... args)
143 : value_(std::forward<Args>(args)...)
144 {}
145
146 Value& operator*() const { return value_; }
147 Value* operator->() const { return std::addressof(value_); }
148
149private:
150 mutable Value value_;
151};
152
153template <typename Container, typename Key>
154auto ProjectedFindOrNullptrUnsafe(Container& set, const Key& key) {
155 auto iter = utils::ProjectedFind(set, key);
156 if constexpr (std::is_const_v<Container>) {
157 return iter == set.end() ? nullptr : std::addressof(std::as_const(**iter));
158 } else {
159 return iter == set.end() ? nullptr : std::addressof(**iter);
160 }
161}
162/// @}
163
164} // namespace impl
165
166namespace impl::projected_set {
167
168// Comparing Projected*Set results in only Projections being compared, which
169// breaks value semantics. Unfortunately, if we define them as aliases of
170// std::*set, we can't have operator== compare full values. The least bad
171// decision in this case is to prohibit the comparison.
172template <
173 typename V1,
174 const auto& P1,
175 typename H1,
176 typename E1,
177 typename A1,
178 typename V2,
179 const auto& P2,
180 typename H2,
181 typename E2,
182 typename A2>
183void operator==(
184 const ProjectedUnorderedSet<V1, P1, H1, E1, A1>& lhs,
185 const ProjectedUnorderedSet<V2, P2, H2, E2, A2>& rhs
186) = delete;
187
188template <
189 typename V1,
190 const auto& P1,
191 typename H1,
192 typename E1,
193 typename A1,
194 typename V2,
195 const auto& P2,
196 typename H2,
197 typename E2,
198 typename A2>
199void operator!=(
200 const ProjectedUnorderedSet<V1, P1, H1, E1, A1>& lhs,
201 const ProjectedUnorderedSet<V2, P2, H2, E2, A2>& rhs
202) = delete;
203
204template <typename V1, const auto& P1, typename C1, typename A1, typename V2, const auto& P2, typename C2, typename A2>
205void operator==(const ProjectedSet<V1, P1, C1, A1>& lhs, const ProjectedSet<V2, P2, C2, A2>& rhs) = 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
210} // namespace impl::projected_set
211
212} // namespace utils
213
214USERVER_NAMESPACE_END