userver: userver/utils/projected_set.hpp Source File
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
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::kIsDetected<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) : value_(std::forward<Args>(args)...) {}
143
144 Value& operator*() const { return value_; }
145 Value* operator->() const { return std::addressof(value_); }
146
147private:
148 mutable Value value_;
149};
150
151template <typename Container, typename Key>
152auto ProjectedFindOrNullptrUnsafe(Container& set, const Key& key) {
153 auto iter = utils::ProjectedFind(set, key);
154 if constexpr (std::is_const_v<Container>) {
155 return iter == set.end() ? nullptr : std::addressof(std::as_const(**iter));
156 } else {
157 return iter == set.end() ? nullptr : std::addressof(**iter);
158 }
159}
160/// @}
161
162} // namespace impl
163
164namespace impl::projected_set {
165
166// Comparing Projected*Set results in only Projections being compared, which
167// breaks value semantics. Unfortunately, if we define them as aliases of
168// std::*set, we can't have operator== compare full values. The least bad
169// decision in this case is to prohibit the comparison.
170template <
171 typename V1,
172 const auto& P1,
173 typename H1,
174 typename E1,
175 typename A1,
176 typename V2,
177 const auto& P2,
178 typename H2,
179 typename E2,
180 typename A2>
181void operator==(
182 const ProjectedUnorderedSet<V1, P1, H1, E1, A1>& lhs,
183 const ProjectedUnorderedSet<V2, P2, H2, E2, A2>& rhs
184) = delete;
185
186template <
187 typename V1,
188 const auto& P1,
189 typename H1,
190 typename E1,
191 typename A1,
192 typename V2,
193 const auto& P2,
194 typename H2,
195 typename E2,
196 typename A2>
197void operator!=(
198 const ProjectedUnorderedSet<V1, P1, H1, E1, A1>& lhs,
199 const ProjectedUnorderedSet<V2, P2, H2, E2, A2>& rhs
200) = delete;
201
202template <typename V1, const auto& P1, typename C1, typename A1, typename V2, const auto& P2, typename C2, typename A2>
203void operator==(const ProjectedSet<V1, P1, C1, A1>& lhs, const ProjectedSet<V2, P2, C2, A2>& rhs) = delete;
204
205template <typename V1, const auto& P1, typename C1, typename A1, typename V2, const auto& P2, typename C2, typename A2>
206void operator!=(const ProjectedSet<V1, P1, C1, A1>& lhs, const ProjectedSet<V2, P2, C2, A2>& rhs) = delete;
207
208} // namespace impl::projected_set
209
210} // namespace utils
211
212USERVER_NAMESPACE_END