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
11USERVER_NAMESPACE_BEGIN
12
13namespace utils {
14
15namespace impl::projected_set {
16
17template <typename Raw, auto Projection>
18using ProjectionResult = std::decay_t<std::invoke_result_t<decltype(Projection), const Raw&>>;
19
20template <typename Raw, auto Projection, typename ResultHash>
21using DefaultedResultHash =
22 std::conditional_t<std::is_void_v<ResultHash>, std::hash<ProjectionResult<Raw, Projection>>, ResultHash>;
23
24template <typename Raw, auto Projection, typename ResultHash>
25struct Hash : public DefaultedResultHash<Raw, Projection, ResultHash> {
26 using is_transparent [[maybe_unused]] = void;
27 using Base = DefaultedResultHash<Raw, Projection, ResultHash>;
28
29 auto operator()(const Raw& value) const noexcept { return Base::operator()(std::invoke(Projection, value)); }
30
31 using Base::operator();
32};
33
34template <typename Raw, auto Projection, typename ResultCompare>
35struct Compare : public ResultCompare {
36 using is_transparent [[maybe_unused]] = void;
37
38 auto operator()(const Raw& lhs, const Raw& rhs) const noexcept {
39 return ResultCompare::operator()(std::invoke(Projection, lhs), std::invoke(Projection, rhs));
40 }
41
42 template <typename T>
43 auto operator()(const Raw& lhs, const T& rhs) const noexcept {
44 return ResultCompare::operator()(std::invoke(Projection, lhs), rhs);
45 }
46
47 template <typename T>
48 auto operator()(const T& lhs, const Raw& rhs) const noexcept {
49 return ResultCompare::operator()(lhs, std::invoke(Projection, rhs));
50 }
51
52 template <typename T, typename U>
53 auto operator()(const T& lhs, const U& rhs) const noexcept {
54 return ResultCompare::operator()(lhs, rhs);
55 }
56};
57
58template <typename Set, typename Value>
59void DoInsert(Set& set, Value&& value) {
60 const auto [iter, success] = set.insert(std::forward<Value>(value));
61 if (!success) {
62 using SetValue = std::decay_t<decltype(*iter)>;
63 // 'const_cast' is safe here, because the new key compares equal to the
64 // old one and should have the same ordering (or hash) as the old one.
65 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
66 const_cast<SetValue&>(*iter) = std::forward<Value>(value);
67 }
68}
69
70} // namespace impl::projected_set
71
72/// @ingroup userver_universal userver_containers
73/// @brief A `std::unordered_set` that compares its elements (of type @a Value)
74/// based on their @a Projection. It allows to create, essentially, an
75/// equivalent of `std::unordered_map` where keys are stored inside values.
76///
77/// Usage example:
78/// @snippet utils/projected_set_test.cpp user
79/// @snippet utils/projected_set_test.cpp usage
80template <
81 typename Value,
82 auto Projection,
83 typename Hash = void,
84 typename Equal = std::equal_to<>,
85 typename Allocator = std::allocator<Value>>
87 Value,
90 Allocator>;
91
92/// @ingroup userver_universal userver_containers
93/// @brief Same as ProjectedUnorderedSet, but for `std::set`.
94template <typename Value, auto Projection, typename Compare = std::less<>, typename Allocator = std::allocator<Value>>
96
97/// @brief An equivalent of `std::unordered_map::insert_or_assign` for
98/// utils::ProjectedUnorderedSet and utils::ProjectedSet.
99template <typename Container, typename Value>
100void ProjectedInsertOrAssign(Container& set, Value&& value) {
101 impl::projected_set::DoInsert(set, std::forward<Value>(value));
102}
103
104namespace impl::projected_set {
105
106// Comparing Projected*Set results in only Projections being compared, which
107// breaks value semantics. Unfortunately, if we define them as aliases of
108// std::*set, we can't have operator== compare full values. The least bad
109// decision in this case is to prohibit the comparison.
110template <
111 typename V1,
112 const auto& P1,
113 typename H1,
114 typename E1,
115 typename A1,
116 typename V2,
117 const auto& P2,
118 typename H2,
119 typename E2,
120 typename A2>
121void operator==(
122 const ProjectedUnorderedSet<V1, P1, H1, E1, A1>& lhs,
123 const ProjectedUnorderedSet<V2, P2, H2, E2, A2>& rhs
124) = delete;
125
126template <
127 typename V1,
128 const auto& P1,
129 typename H1,
130 typename E1,
131 typename A1,
132 typename V2,
133 const auto& P2,
134 typename H2,
135 typename E2,
136 typename A2>
137void operator!=(
138 const ProjectedUnorderedSet<V1, P1, H1, E1, A1>& lhs,
139 const ProjectedUnorderedSet<V2, P2, H2, E2, A2>& rhs
140) = delete;
141
142template <typename V1, const auto& P1, typename C1, typename A1, typename V2, const auto& P2, typename C2, typename A2>
143void operator==(const ProjectedSet<V1, P1, C1, A1>& lhs, const ProjectedSet<V2, P2, C2, A2>& rhs) = delete;
144
145template <typename V1, const auto& P1, typename C1, typename A1, typename V2, const auto& P2, typename C2, typename A2>
146void operator!=(const ProjectedSet<V1, P1, C1, A1>& lhs, const ProjectedSet<V2, P2, C2, A2>& rhs) = delete;
147
148} // namespace impl::projected_set
149
150} // namespace utils
151
152USERVER_NAMESPACE_END