userver: userver/utils/algo.hpp Source File
Loading...
Searching...
No Matches
algo.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/utils/algo.hpp
4/// @brief Small useful algorithms.
5/// @ingroup userver_universal
6
7#include <algorithm>
8#include <cstddef>
9#include <iterator>
10#include <memory>
11#include <optional>
12#include <ranges>
13#include <string>
14#include <string_view>
15#include <type_traits>
16#include <utility>
17
18#include <userver/utils/checked_pointer.hpp>
19
20USERVER_NAMESPACE_BEGIN
21
22/// @brief General-purpose utilities used across userver libraries.
23namespace utils {
24
25/// @brief Concatenates multiple `std::string_view`-convertible items
26template <typename ResultString = std::string, typename... Strings>
27ResultString StrCat(const Strings&... strings) {
28 return [](auto... string_views) {
29 std::size_t result_size = 0;
30 ((result_size += string_views.size()), ...);
31
32 ResultString result;
33 result.reserve(result_size);
34 (result.append(string_views), ...);
35 return result;
36 }(std::string_view{strings}...);
37}
38
39namespace impl {
40
41template <typename Container>
42concept HasMappedType = requires { typename Container::mapped_type; };
43
44} // namespace impl
45
46/// @brief Returns nullptr if no key in associative container, otherwise
47/// returns pointer to value.
48template <typename Container, typename Key>
49auto* FindOrNullptr(Container& container, const Key& key) {
50 const auto it = container.find(key);
51 if constexpr (impl::HasMappedType<Container>) {
52 return (it != std::ranges::end(container) ? std::addressof(it->second) : nullptr);
53 } else {
54 return (it != std::ranges::end(container) ? std::addressof(*it) : nullptr);
55 }
56}
57
58/// @brief Returns default value if no key in associative container, otherwise
59/// returns a copy of the stored value.
60template <typename Container, typename Key, typename Default>
61auto FindOrDefault(Container& container, const Key& key, Default&& def) {
62 const auto* ptr = USERVER_NAMESPACE::utils::FindOrNullptr(container, key);
63 using R = std::remove_cvref_t<decltype(*ptr)>;
64 return (ptr ? *ptr : R(std::forward<Default>(def)));
65}
66
67/// @brief Returns default value if no key in associative container, otherwise
68/// returns a copy of the stored value.
69template <typename Container, typename Key>
70auto FindOrDefault(Container& container, const Key& key) {
71 const auto* ptr = USERVER_NAMESPACE::utils::FindOrNullptr(container, key);
72 using R = std::remove_cvref_t<decltype(*ptr)>;
73 return (ptr ? *ptr : R());
74}
75
76/// @brief Returns std::nullopt if no key in associative container, otherwise
77/// returns std::optional with a copy of value
78template <typename Container, typename Key>
79auto FindOptional(Container& container, const Key& key) {
80 const auto* ptr = USERVER_NAMESPACE::utils::FindOrNullptr(container, key);
81 return (ptr ? std::make_optional(*ptr) : std::nullopt);
82}
83
84/// @brief Searches a map for an element and return a checked pointer to
85/// the found element
86template <typename Container, typename Key>
87auto CheckedFind(Container& container, const Key& key) {
88 return utils::MakeCheckedPtr(USERVER_NAMESPACE::utils::FindOrNullptr(container, key));
89}
90
91/// @brief Converts one container type to another
92///
93/// @warning This function moves from elements if the range is an rvalue. This is not correct for rvalue ranges that
94/// do not own their elements, such as typical views. For example:
95/// @code
96/// std::vector<std::string> v = {"hello", "world"};
97/// auto result = utils::AsContainer<std::vector<std::string>>(
98/// v | std::views::filter([](const auto& item) { return true; })
99/// );
100/// @endcode
101/// This will move the items from `v` to `result`, which is not what you want if you want to keep the original vector.
102///
103/// Use C++23 `std::ranges::to` instead if possible, optionally paired with `std::ranges::as_rvalue`.
104template <typename ToContainer, typename FromContainer>
105// NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward)
106ToContainer AsContainer(FromContainer&& container) {
107 if constexpr (std::is_rvalue_reference_v<decltype(container)>) {
108 return ToContainer(
109 std::make_move_iterator(std::ranges::begin(container)),
110 std::make_move_iterator(std::ranges::end(container))
111 );
112 } else {
113 return ToContainer(std::ranges::begin(container), std::ranges::end(container));
114 }
115}
116
117namespace impl {
118
119template <typename ToContainer, typename Range>
120// NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward)
121ToContainer AsContainerViaInsert(Range&& range) {
122 ToContainer result;
123 if constexpr (requires { result.reserve(std::ranges::size(range)); }) {
124 result.reserve(std::ranges::size(range));
125 }
126 for (auto&& ref : range) {
127 result.insert(std::ranges::end(result), std::forward<decltype(ref)>(ref));
128 }
129 return result;
130}
131
132template <typename Container>
133concept HasKeyType = requires { typename Container::key_type; };
134
135} // namespace impl
136
137/// @brief Erased elements and returns number of deleted elements
138template <typename Container, typename Pred>
139std::integral auto EraseIf(Container& container, Pred pred) {
140 if constexpr (impl::HasKeyType<Container>) {
141 auto old_size = std::ranges::size(container);
142 for (auto it = std::ranges::begin(container), last = std::ranges::end(container); it != last;) {
143 if (pred(*it)) {
144 it = container.erase(it);
145 } else {
146 ++it;
147 }
148 }
149 return old_size - std::ranges::size(container);
150 } else {
151 auto garbage = std::ranges::remove_if(container, pred);
152 container.erase(garbage.begin(), garbage.end());
153 return std::ranges::size(garbage);
154 }
155}
156
157/// @brief Erased elements and returns number of deleted elements
158template <typename Container, typename T>
159std::integral auto Erase(Container& container, const T& elem) {
160 if constexpr (impl::HasKeyType<Container>) {
161 return container.erase(elem);
162 } else {
163 // NOLINTNEXTLINE(readability-qualified-auto)
164 auto garbage = std::ranges::remove(container, elem);
165 container.erase(garbage.begin(), garbage.end());
166 return std::ranges::size(garbage);
167 }
168}
169
170/// @brief returns true if there is an element in the container which satisfies the predicate.
171///
172/// @deprecated Use `std::ranges::any_of` instead.
173template <typename Container, typename Pred>
174bool ContainsIf(const Container& container, Pred pred) {
175 return std::ranges::any_of(container, pred);
176}
177
178/// @brief returns true if there is a specified element in the container
179///
180/// In C++23, use `std::ranges::contains` instead.
181template <typename Container, typename Item>
182bool Contains(const Container& container, const Item& item) {
183 return std::ranges::find(container, item) != std::ranges::end(container);
184}
185
186} // namespace utils
187
188USERVER_NAMESPACE_END