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 return (ptr ? *ptr : decltype(*ptr){std::forward<Default>(def)});
64}
65
66/// @brief Returns default value if no key in associative container, otherwise
67/// returns a copy of the stored value.
68template <typename Container, typename Key>
69auto FindOrDefault(Container& container, const Key& key) {
70 const auto* ptr = USERVER_NAMESPACE::utils::FindOrNullptr(container, key);
71 return (ptr ? *ptr : decltype(*ptr){});
72}
73
74/// @brief Returns std::nullopt if no key in associative container, otherwise
75/// returns std::optional with a copy of value
76template <typename Container, typename Key>
77auto FindOptional(Container& container, const Key& key) {
78 const auto* ptr = USERVER_NAMESPACE::utils::FindOrNullptr(container, key);
79 return (ptr ? std::make_optional(*ptr) : std::nullopt);
80}
81
82/// @brief Searches a map for an element and return a checked pointer to
83/// the found element
84template <typename Container, typename Key>
85auto CheckedFind(Container& container, const Key& key) {
86 return utils::MakeCheckedPtr(USERVER_NAMESPACE::utils::FindOrNullptr(container, key));
87}
88
89/// @brief Converts one container type to another
90///
91/// @warning This function moves from elements if the range is an rvalue. This is not correct for rvalue ranges that
92/// do not own their elements, such as typical views. For example:
93/// @code
94/// std::vector<std::string> v = {"hello", "world"};
95/// auto result = utils::AsContainer<std::vector<std::string>>(
96/// v | std::views::filter([](const auto& item) { return true; })
97/// );
98/// @endcode
99/// This will move the items from `v` to `result`, which is not what you want if you want to keep the original vector.
100///
101/// Use C++23 `std::ranges::to` instead if possible, optionally paired with `std::ranges::as_rvalue`.
102template <typename ToContainer, typename FromContainer>
103// NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward)
104ToContainer AsContainer(FromContainer&& container) {
105 if constexpr (std::is_rvalue_reference_v<decltype(container)>) {
106 return ToContainer(
107 std::make_move_iterator(std::ranges::begin(container)),
108 std::make_move_iterator(std::ranges::end(container))
109 );
110 } else {
111 return ToContainer(std::ranges::begin(container), std::ranges::end(container));
112 }
113}
114
115namespace impl {
116
117template <typename ToContainer, typename Range>
118// NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward)
119ToContainer AsContainerViaInsert(Range&& range) {
120 ToContainer result;
121 if constexpr (requires { result.reserve(std::ranges::size(range)); }) {
122 result.reserve(std::ranges::size(range));
123 }
124 for (auto&& ref : range) {
125 result.insert(std::ranges::end(result), std::forward<decltype(ref)>(ref));
126 }
127 return result;
128}
129
130template <typename Container>
131concept HasKeyType = requires { typename Container::key_type; };
132
133} // namespace impl
134
135/// @brief Erased elements and returns number of deleted elements
136template <typename Container, typename Pred>
137std::integral auto EraseIf(Container& container, Pred pred) {
138 if constexpr (impl::HasKeyType<Container>) {
139 auto old_size = std::ranges::size(container);
140 for (auto it = std::ranges::begin(container), last = std::ranges::end(container); it != last;) {
141 if (pred(*it)) {
142 it = container.erase(it);
143 } else {
144 ++it;
145 }
146 }
147 return old_size - std::ranges::size(container);
148 } else {
149 auto garbage = std::ranges::remove_if(container, pred);
150 container.erase(garbage.begin(), garbage.end());
151 return std::ranges::size(garbage);
152 }
153}
154
155/// @brief Erased elements and returns number of deleted elements
156template <typename Container, typename T>
157std::integral auto Erase(Container& container, const T& elem) {
158 if constexpr (impl::HasKeyType<Container>) {
159 return container.erase(elem);
160 } else {
161 // NOLINTNEXTLINE(readability-qualified-auto)
162 auto garbage = std::ranges::remove(container, elem);
163 container.erase(garbage.begin(), garbage.end());
164 return std::ranges::size(garbage);
165 }
166}
167
168/// @brief returns true if there is an element in the container which satisfies the predicate.
169///
170/// @deprecated Use `std::ranges::any_of` instead.
171template <typename Container, typename Pred>
172bool ContainsIf(const Container& container, Pred pred) {
173 return std::ranges::any_of(container, pred);
174}
175
176/// @brief returns true if there is a specified element in the container
177///
178/// In C++23, use `std::ranges::contains` instead.
179template <typename Container, typename Item>
180bool Contains(const Container& container, const Item& item) {
181 return std::ranges::find(container, item) != std::ranges::end(container);
182}
183
184} // namespace utils
185
186USERVER_NAMESPACE_END