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