userver: userver/utils/projected_set.hpp Source File
⚠️ This is the documentation for an old userver version. Click here to switch to the latest version.
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
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