userver: userver/utils/trivial_map.hpp Source File
Loading...
Searching...
No Matches
trivial_map.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/utils/trivial_map.hpp
4/// @brief Bidirectional map|sets over string literals or other trivial types.
5
6#include <cstddef>
7#include <optional>
8#include <string>
9#include <string_view>
10#include <type_traits>
11#include <utility>
12#include <variant>
13
14#include <fmt/format.h>
15
16#include <userver/compiler/demangle.hpp>
17#include <userver/compiler/impl/nodebug.hpp>
18#include <userver/utils/assert.hpp>
19#include <userver/utils/string_literal.hpp>
20
21USERVER_NAMESPACE_BEGIN
22
23namespace utils {
24
25namespace impl {
26
27constexpr bool HasUppercaseAscii(std::string_view value) noexcept {
28 for (auto c : value) {
29 if ('A' <= c && c <= 'Z') {
30 return true;
31 }
32 }
33
34 return false;
35}
36
37constexpr bool ICaseEqualLowercase(std::string_view lowercase, std::string_view y) noexcept {
38 const auto size = lowercase.size();
39 UASSERT(size == y.size());
40 constexpr char kLowerToUpperMask = static_cast<char>(~unsigned{32});
41 for (std::size_t i = 0; i < size; ++i) {
42 const auto lowercase_c = lowercase[i];
43 UASSERT(!('A' <= lowercase_c && lowercase_c <= 'Z'));
44 if (lowercase_c != y[i]) {
45 if (!('a' <= lowercase_c && lowercase_c <= 'z') || (lowercase_c & kLowerToUpperMask) != y[i]) {
46 return false;
47 }
48 }
49 }
50
51 return true;
52}
53
54struct Found final {
55 constexpr explicit Found(std::size_t value) noexcept { UASSERT(value == 0); }
56
57 constexpr explicit operator std::size_t() const noexcept { return 0; }
58};
59
60template <typename Key, typename Value>
61class SearchState final {
62public:
63 constexpr explicit SearchState(Key key) noexcept : key_or_result_(std::in_place_index<0>, key) {}
64
65 constexpr bool IsFound() const noexcept { return key_or_result_.index() != 0; }
66
67 constexpr Key GetKey() const noexcept {
68 UASSERT(!IsFound());
69 return std::get<0>(key_or_result_);
70 }
71
72 constexpr void SetValue(Value value) noexcept {
73 key_or_result_ = std::variant<Key, Value>(std::in_place_index<1>, value);
74 }
75
76 [[nodiscard]] constexpr std::optional<Value> Extract() noexcept {
77 if (key_or_result_.index() == 1) {
78 return std::get<1>(key_or_result_);
79 } else {
80 return std::nullopt;
81 }
82 }
83
84private:
85 std::variant<Key, Value> key_or_result_;
86};
87
88inline constexpr std::size_t kInvalidSize = std::numeric_limits<std::size_t>::max();
89
90template <typename Payload>
91concept FitsInStringOrPayload =
92 sizeof(Payload) <= sizeof(const char*) &&
93 (std::is_integral_v<Payload> || std::is_enum_v<Payload> || std::is_same_v<Payload, Found>);
94
95// A compacted std::variant<std::string_view, Payload>
96template <typename Payload>
97class StringOrPayload final {
98public:
99 constexpr explicit StringOrPayload(std::string_view string) noexcept
100 : data_or_payload_(string.data()), size_(string.size()) {
101#if defined(__clang__)
102 __builtin_assume(size_ != kInvalidSize);
103#elif defined(__GNUC__)
104 if (size_ == kInvalidSize) {
105 __builtin_unreachable();
106 }
107#endif
108 }
109
110 constexpr explicit StringOrPayload(Payload payload) noexcept : data_or_payload_(payload), size_(kInvalidSize) {}
111
112 constexpr bool HasPayload() const noexcept { return size_ == kInvalidSize; }
113
114 constexpr const char* GetStringPointer() const noexcept {
115 UASSERT(!HasPayload());
116 UASSERT(data_or_payload_.data);
117 return data_or_payload_.data;
118 }
119
120 constexpr std::size_t GetStringSize() const noexcept {
121 UASSERT(!HasPayload());
122 UASSERT(data_or_payload_.data);
123 return size_;
124 }
125
126 constexpr Payload GetPayload() const noexcept {
127 UASSERT(HasPayload());
128 return data_or_payload_.payload;
129 }
130
131private:
132 static_assert(FitsInStringOrPayload<Payload>);
133
134 union DataOrPayload {
135 constexpr explicit DataOrPayload(const char* data) noexcept : data(data) {}
136
137 constexpr explicit DataOrPayload(Payload payload) noexcept : payload(payload) {}
138
139 const char* data{};
140 Payload payload;
141 };
142
143 DataOrPayload data_or_payload_;
144 std::size_t size_;
145};
146
147template <FitsInStringOrPayload Value>
148class SearchState<std::string_view, Value> {
149public:
150 constexpr explicit SearchState(std::string_view key) noexcept : state_(key) {}
151
152 constexpr bool IsFound() const noexcept { return state_.HasPayload(); }
153
154 constexpr std::string_view GetKey() const noexcept {
155 return std::string_view{state_.GetStringPointer(), state_.GetStringSize()};
156 }
157
158 constexpr void SetValue(Value value) noexcept { state_ = StringOrPayload<Value>{value}; }
159
160 [[nodiscard]] constexpr std::optional<Value> Extract() noexcept {
161 return IsFound() ? std::optional{state_.GetPayload()} : std::nullopt;
162 }
163
164private:
165 StringOrPayload<Value> state_;
166};
167
168template <FitsInStringOrPayload Value>
169class SearchState<zstring_view, Value> final : public SearchState<std::string_view, Value> {};
170
171template <FitsInStringOrPayload Value>
172class SearchState<StringLiteral, Value> final : public SearchState<std::string_view, Value> {};
173
174template <FitsInStringOrPayload Key>
175class SearchState<Key, std::string_view> final {
176public:
177 constexpr explicit SearchState(Key key) noexcept : state_(key) {}
178
179 constexpr bool IsFound() const noexcept { return !state_.HasPayload(); }
180
181 constexpr Key GetKey() const noexcept { return state_.GetPayload(); }
182
183 constexpr void SetValue(std::string_view value) noexcept { state_ = StringOrPayload<Key>{value}; }
184
185 [[nodiscard]] constexpr std::optional<std::string_view> Extract() noexcept {
186 return IsFound() ? std::optional{std::string_view{state_.GetStringPointer(), state_.GetStringSize()}}
187 : std::nullopt;
188 }
189
190private:
191 StringOrPayload<Key> state_;
192};
193
194template <FitsInStringOrPayload Key>
195class SearchState<Key, zstring_view> final {
196public:
197 constexpr explicit SearchState(Key key) noexcept : state_(key) {}
198
199 constexpr bool IsFound() const noexcept { return !state_.HasPayload(); }
200
201 constexpr Key GetKey() const noexcept { return state_.GetPayload(); }
202
203 constexpr void SetValue(zstring_view value) noexcept { state_ = StringOrPayload<Key>{value}; }
204
205 [[nodiscard]] constexpr std::optional<zstring_view> Extract() noexcept {
206 return IsFound() ? std::optional{zstring_view::UnsafeMake(state_.GetStringPointer(), state_.GetStringSize())}
207 : std::nullopt;
208 }
209
210private:
211 StringOrPayload<Key> state_;
212};
213
214template <FitsInStringOrPayload Key>
215class SearchState<Key, StringLiteral> final {
216public:
217 constexpr explicit SearchState(Key key) noexcept : state_(key) {}
218
219 constexpr bool IsFound() const noexcept { return !state_.HasPayload(); }
220
221 constexpr Key GetKey() const noexcept { return state_.GetPayload(); }
222
223 constexpr void SetValue(StringLiteral value) noexcept { state_ = StringOrPayload<Key>{value}; }
224
225 [[nodiscard]] constexpr std::optional<StringLiteral> Extract() noexcept {
226 return IsFound() ? std::optional{StringLiteral::UnsafeMake(state_.GetStringPointer(), state_.GetStringSize())}
227 : std::nullopt;
228 }
229
230private:
231 StringOrPayload<Key> state_;
232};
233
234template <typename First, typename Second>
235class SwitchByFirst final {
236public:
237 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr explicit SwitchByFirst(First search) noexcept : state_(search) {}
238
239 constexpr SwitchByFirst& Case(First first, Second second) noexcept {
240 if (!state_.IsFound() && state_.GetKey() == first) {
241 state_.SetValue(second);
242 }
243 return *this;
244 }
245
246 template <typename T, typename U = void>
247 constexpr SwitchByFirst& Type() {
248 return *this;
249 }
250
251 [[nodiscard]] constexpr std::optional<Second> Extract() noexcept { return state_.Extract(); }
252
253private:
254 SearchState<First, Second> state_;
255};
256
257template <typename First>
258class SwitchByFirst<First, void> final {
259public:
260 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr explicit SwitchByFirst(First search) noexcept : state_(search) {}
261
262 constexpr SwitchByFirst& Case(First first) noexcept {
263 if (!state_.IsFound() && state_.GetKey() == first) {
264 state_.SetValue(Found{0});
265 }
266 return *this;
267 }
268
269 template <typename T, typename U = void>
270 constexpr SwitchByFirst& Type() {
271 return *this;
272 }
273
274 [[nodiscard]] constexpr bool Extract() noexcept { return state_.IsFound(); }
275
276private:
277 SearchState<First, Found> state_;
278};
279
280template <typename Second>
281class SwitchByFirstICase final {
282public:
283 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr explicit SwitchByFirstICase(std::string_view search) noexcept : state_(search) {}
284
285 constexpr SwitchByFirstICase& Case(std::string_view first, Second second) noexcept {
287 !impl::HasUppercaseAscii(first),
288 fmt::format("String literal '{}' in utils::Switch*::Case() should be in lower case", first)
289 );
290 if (!state_.IsFound() && state_.GetKey().size() == first.size() &&
291 impl::ICaseEqualLowercase(first, state_.GetKey()))
292 {
293 state_.SetValue(second);
294 }
295 return *this;
296 }
297
298 template <typename T, typename U>
299 constexpr SwitchByFirstICase& Type() {
300 return *this;
301 }
302
303 [[nodiscard]] constexpr std::optional<Second> Extract() noexcept { return state_.Extract(); }
304
305private:
306 SearchState<std::string_view, Second> state_;
307};
308
309template <>
310class SwitchByFirstICase<void> final {
311public:
312 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr explicit SwitchByFirstICase(std::string_view search) noexcept : state_(search) {}
313
314 constexpr SwitchByFirstICase& Case(std::string_view first) noexcept {
316 !impl::HasUppercaseAscii(first),
317 fmt::format("String literal '{}' in utils::Switch*::Case() should be in lower case", first)
318 );
319 if (!state_.IsFound() && state_.GetKey().size() == first.size() &&
320 impl::ICaseEqualLowercase(first, state_.GetKey()))
321 {
322 state_.SetValue(Found{0});
323 }
324 return *this;
325 }
326
327 template <typename T, typename U>
328 constexpr SwitchByFirstICase& Type() {
329 return *this;
330 }
331
332 [[nodiscard]] constexpr bool Extract() const noexcept { return state_.IsFound(); }
333
334private:
335 SearchState<std::string_view, Found> state_;
336};
337
338template <typename First>
339class SwitchBySecondICase final {
340public:
341 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr explicit SwitchBySecondICase(std::string_view search) noexcept : state_(search) {}
342
343 constexpr SwitchBySecondICase& Case(First first, std::string_view second) noexcept {
345 !impl::HasUppercaseAscii(second),
346 fmt::format("String literal '{}' in utils::Switch*::Case() should be in lower case", second)
347 );
348 if (!state_.IsFound() && state_.GetKey().size() == second.size() &&
349 impl::ICaseEqualLowercase(second, state_.GetKey()))
350 {
351 state_.SetValue(first);
352 }
353 return *this;
354 }
355
356 template <typename T, typename U>
357 constexpr SwitchBySecondICase& Type() {
358 return *this;
359 }
360
361 [[nodiscard]] constexpr std::optional<First> Extract() noexcept { return state_.Extract(); }
362
363private:
364 SearchState<std::string_view, First> state_;
365};
366
367template <typename First, typename Second>
368class SwitchBySecond final {
369public:
370 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr explicit SwitchBySecond(Second search) noexcept : state_(search) {}
371
372 constexpr SwitchBySecond& Case(First first, Second second) noexcept {
373 if (!state_.IsFound() && state_.GetKey() == second) {
374 state_.SetValue(first);
375 }
376 return *this;
377 }
378
379 template <typename T, typename U>
380 constexpr SwitchBySecond& Type() {
381 return *this;
382 }
383
384 [[nodiscard]] constexpr std::optional<First> Extract() noexcept { return state_.Extract(); }
385
386private:
387 SearchState<Second, First> state_;
388};
389
390template <typename First, typename Second>
391class SwitchTypesDetected final {
392public:
393 using first_type = First;
394 using second_type = Second;
395
396 constexpr SwitchTypesDetected& Case(First, Second) noexcept { return *this; }
397};
398
399template <typename First>
400class SwitchTypesDetected<First, void> final {
401public:
402 using first_type = First;
403 using second_type = void;
404
405 constexpr SwitchTypesDetected& Case(First) noexcept { return *this; }
406};
407
408class SwitchTypesDetector final {
409public:
410 template <class T>
411 using DetectType = std::conditional_t<
412 std::is_convertible_v<T, StringLiteral>,
414 std::conditional_t< // TODO: force StringLiteral
415 std::is_same_v<T, zstring_view>,
417 std::conditional_t<std::is_convertible_v<T, std::string_view>, std::string_view, T>>>;
418
419 constexpr SwitchTypesDetector& operator()() noexcept { return *this; }
420
421 template <typename First, typename Second>
422 constexpr auto Case(First, Second) noexcept {
423 return Type<First, Second>();
424 }
425
426 template <typename First>
427 constexpr auto Case(First) noexcept {
428 return Type<First, void>();
429 }
430
431 template <typename First, typename Second = void>
432 constexpr auto Type() noexcept {
433 return SwitchTypesDetected<DetectType<First>, DetectType<Second>>{};
434 }
435};
436
437class CaseCounter final {
438public:
439 template <typename First, typename Second>
440 constexpr CaseCounter& Case(First, Second) noexcept {
441 ++count_;
442 return *this;
443 }
444
445 template <typename First>
446 constexpr CaseCounter& Case(First) noexcept {
447 ++count_;
448 return *this;
449 }
450
451 template <typename T, typename U>
452 constexpr CaseCounter& Type() {
453 return *this;
454 }
455
456 [[nodiscard]] constexpr std::size_t Extract() const noexcept { return count_; }
457
458private:
459 std::size_t count_{0};
460};
461
462class CaseDescriber final {
463public:
464 template <typename First, typename Second>
465 CaseDescriber& Case(First first, Second second) noexcept {
466 if (!description_.empty()) {
467 description_ += ", ";
468 }
469
470 description_ += fmt::format("('{}', '{}')", first, second);
471
472 return *this;
473 }
474
475 template <typename T, typename U>
476 constexpr CaseDescriber& Type() {
477 return *this;
478 }
479
480 [[nodiscard]] std::string Extract() && noexcept { return std::move(description_); }
481
482private:
483 std::string description_{};
484};
485
486class CaseFirstDescriber final {
487public:
488 template <typename First>
489 CaseFirstDescriber& Case(First first) noexcept {
490 if (!description_.empty()) {
491 description_ += ", ";
492 }
493
494 description_ += fmt::format("'{}'", first);
495
496 return *this;
497 }
498
499 template <typename First, typename Second>
500 CaseFirstDescriber& Case(First first, Second /*second*/) noexcept {
501 return Case(first);
502 }
503
504 template <typename T, typename U = void>
505 constexpr CaseFirstDescriber& Type() {
506 return *this;
507 }
508
509 [[nodiscard]] std::string Extract() && noexcept { return std::move(description_); }
510
511private:
512 std::string description_{};
513};
514
515class CaseSecondDescriber final {
516public:
517 template <typename First, typename Second>
518 CaseSecondDescriber& Case(First /*first*/, Second second) noexcept {
519 if (!description_.empty()) {
520 description_ += ", ";
521 }
522
523 description_ += fmt::format("'{}'", second);
524
525 return *this;
526 }
527
528 template <typename T, typename U>
529 constexpr CaseSecondDescriber& Type() {
530 return *this;
531 }
532
533 [[nodiscard]] std::string Extract() && noexcept { return std::move(description_); }
534
535private:
536 std::string description_{};
537};
538
539template <typename First, typename Second>
540class CaseGetValuesByIndex final {
541public:
542 USERVER_IMPL_NODEBUG_INLINE_FUNC explicit constexpr CaseGetValuesByIndex(std::size_t search_index)
543 : index_(search_index + 1)
544 {}
545
546 constexpr CaseGetValuesByIndex& Case(First first, Second second) noexcept {
547 if (index_ == 0) {
548 return *this;
549 }
550 if (index_ == 1) {
551 lazy_ = Lazy{Storage{first, second}};
552 }
553 --index_;
554
555 return *this;
556 }
557
558 template <typename T, typename U>
559 constexpr CaseGetValuesByIndex& Type() {
560 return *this;
561 }
562
563 [[nodiscard]] constexpr First GetFirst() noexcept { return std::move(lazy_.storage.first); }
564
565 [[nodiscard]] constexpr Second GetSecond() noexcept { return std::move(lazy_.storage.second); }
566
567private:
568 std::size_t index_;
569
570 // Work with non default constructible types
571 struct Storage {
572 First first;
573 Second second;
574 };
575
576 union Lazy {
577 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr Lazy() noexcept : empty{} {}
578 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr Lazy(Storage s) noexcept : storage{s} {}
579
580 char empty;
581 Storage storage;
582 };
583 Lazy lazy_;
584};
585
586template <typename First>
587class CaseFirstIndexer final {
588public:
589 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr explicit CaseFirstIndexer(First search_value) noexcept : state_(search_value) {}
590
591 constexpr CaseFirstIndexer& Case(First first) noexcept {
592 if (!state_.IsFound() && state_.GetKey() == first) {
593 state_.SetValue(index_);
594 }
595 ++index_;
596 return *this;
597 }
598
599 template <typename T, typename U = void>
600 constexpr CaseFirstIndexer& Type() {
601 return *this;
602 }
603
604 [[nodiscard]] constexpr std::optional<std::size_t> Extract() && noexcept { return state_.Extract(); }
605
606private:
607 SearchState<First, std::size_t> state_;
608 std::size_t index_ = 0;
609};
610
611template <typename First>
612class CaseFirstIndexerICase final {
613public:
614 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr explicit CaseFirstIndexerICase(First search_value) noexcept : state_(search_value) {}
615
616 constexpr CaseFirstIndexerICase& Case(First first) noexcept {
617 if (!state_.IsFound() && state_.GetKey().size() == first.size() &&
618 impl::ICaseEqualLowercase(first, state_.GetKey()))
619 {
620 state_.SetValue(index_);
621 }
622 ++index_;
623 return *this;
624 }
625
626 template <typename T, typename U = void>
627 constexpr CaseFirstIndexerICase& Type() {
628 return *this;
629 }
630
631 [[nodiscard]] constexpr std::optional<std::size_t> Extract() && noexcept { return state_.Extract(); }
632
633private:
634 SearchState<First, std::size_t> state_;
635 std::size_t index_ = 0;
636};
637
638} // namespace impl
639
640/// Decays utils::StringLiteral and utils::zstring_view to a more generic std::string_view type.
641template <class T>
642using DecayToStringView =
643 std::conditional_t<std::is_same_v<T, StringLiteral> || std::is_same_v<T, zstring_view>, std::string_view, T>;
644
645/// @ingroup userver_universal userver_containers
646///
647/// @brief Bidirectional unordered map for trivial types, including string
648/// literals; could be efficiently used as a unordered non-bidirectional map.
649///
650/// @snippet universal/src/utils/trivial_map_test.cpp sample string bimap
651///
652/// utils::TrivialBiMap and utils::TrivialSet are known to outperform
653/// std::unordered_map if:
654/// * there's 32 or less elements in map/set
655/// * or keys are string literals and all of them differ in length.
656///
657/// Implementation of string search is \b very efficient due to
658/// modern compilers optimize it to a switch by input string
659/// length and an integral comparison (rather than a std::memcmp call). In other
660/// words, it usually takes O(1) to find the match in the map.
661///
662/// The same story with integral or enum mappings - compiler optimizes them
663/// into a switch and it usually takes O(1) to find the match.
664///
665/// @snippet universal/src/utils/trivial_map_test.cpp sample bidir bimap
666///
667/// Empty map:
668///
669/// @snippet universal/src/utils/trivial_map_test.cpp sample empty bimap
670///
671/// For a single value Case statements see @ref utils::TrivialSet.
672template <typename BuilderFunc>
673class TrivialBiMap final {
674 using TypesPair = std::invoke_result_t<const BuilderFunc&, impl::SwitchTypesDetector>;
675
676public:
677 using First = typename TypesPair::first_type;
678 using Second = typename TypesPair::second_type;
679
680 struct ValueType {
681 First first;
682 Second second;
683 };
684
685 /// Returns Second if T is convertible to First, otherwise returns Second type.
686 template <class T>
687 using MappedTypeFor = std::conditional_t<std::is_convertible_v<T, DecayToStringView<First>>, Second, First>;
688
689 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr TrivialBiMap(BuilderFunc&& func) noexcept : func_(std::move(func)) {
690 static_assert(std::is_empty_v<BuilderFunc>, "Mapping function should not capture variables");
691 static_assert(std::is_trivially_copyable_v<First>, "First type in Case must be trivially copyable");
692 static_assert(
693 !std::is_void_v<Second>,
694 "If second type in Case is missing, use utils::TrivialSet instead of utils::TrivialBiMap"
695 );
696 static_assert(std::is_trivially_copyable_v<Second>, "Second type in Case must be trivially copyable");
697 }
698
699 constexpr std::optional<Second> TryFindByFirst(DecayToStringView<First> value) const noexcept {
700 return func_([value]() { return impl::SwitchByFirst<DecayToStringView<First>, Second>{value}; }).Extract();
701 }
702
703 constexpr std::optional<First> TryFindBySecond(DecayToStringView<Second> value) const noexcept {
704 return func_([value]() { return impl::SwitchBySecond<First, DecayToStringView<Second>>{value}; }).Extract();
705 }
706
707 template <class T>
708 constexpr std::optional<MappedTypeFor<T>> TryFind(T value) const noexcept {
709 if constexpr (std::is_convertible_v<T, DecayToStringView<First>>) {
710 static_assert(
711 !std::is_convertible_v<T, DecayToStringView<Second>>,
712 "Ambiguous conversion, use TryFindByFirst/TryFindBySecond instead"
713 );
714 return TryFindByFirst(value);
715 } else {
716 return TryFindBySecond(value);
717 }
718 }
719
720 /// @brief Case insensitive search for value.
721 ///
722 /// For efficiency reasons, first parameter in Case() should be lower case
723 /// string literal.
724 constexpr std::optional<Second> TryFindICaseByFirst(std::string_view value) const noexcept {
725 return func_([value]() { return impl::SwitchByFirstICase<Second>{value}; }).Extract();
726 }
727
728 /// @brief Case insensitive search for value.
729 ///
730 /// For efficiency reasons, second parameter in Case() should be lower case
731 /// string literal.
732 constexpr std::optional<First> TryFindICaseBySecond(std::string_view value) const noexcept {
733 return func_([value]() { return impl::SwitchBySecondICase<First>{value}; }).Extract();
734 }
735
736 /// @brief Case insensitive search for value that calls either
737 /// TryFindICaseBySecond or TryFindICaseByFirst.
738 constexpr std::optional<MappedTypeFor<std::string_view>> TryFindICase(std::string_view value) const noexcept {
739 if constexpr (std::is_convertible_v<std::string_view, DecayToStringView<First>>) {
740 static_assert(
741 !std::is_convertible_v<std::string_view, DecayToStringView<Second>>,
742 "Ambiguous conversion, use TryFindICaseByFirst/TryFindICaseBySecond"
743 );
744 return TryFindICaseByFirst(value);
745 } else {
746 return TryFindICaseBySecond(value);
747 }
748 }
749
750 /// Returns count of Case's in mapping
751 constexpr std::size_t size() const noexcept {
752 return func_([]() { return impl::CaseCounter{}; }).Extract();
753 }
754
755 /// Returns a string of comma separated quoted values of Case parameters.
756 ///
757 /// \b Example: "('a', '1'), ('b', '2'), ('c', '3')"
758 ///
759 /// Parameters of Case should be formattable.
760 std::string Describe() const {
761 return func_([]() { return impl::CaseDescriber{}; }).Extract();
762 }
763
764 /// Returns a string of comma separated quoted values of first Case
765 /// parameters.
766 ///
767 /// \b Example: "'a', 'b', 'c'"
768 ///
769 /// First parameters of Case should be formattable.
770 std::string DescribeFirst() const {
771 return func_([]() { return impl::CaseFirstDescriber{}; }).Extract();
772 }
773
774 /// Returns a string of comma separated quoted values of second Case
775 /// parameters.
776 ///
777 /// \b Example: "'1', '2', '3'"
778 ///
779 /// Second parameters of Case should be formattable.
780 std::string DescribeSecond() const {
781 return func_([]() { return impl::CaseSecondDescriber{}; }).Extract();
782 }
783
784 /// Returns a string of comma separated quoted values of Case
785 /// parameters that matches by type.
786 ///
787 /// \b Example: "'1', '2', '3'"
788 ///
789 /// Corresponding Case must be formattable
790 template <typename T>
791 std::string DescribeByType() const {
792 if constexpr (std::is_convertible_v<T, DecayToStringView<First>>) {
793 return DescribeFirst();
794 } else {
795 return DescribeSecond();
796 }
797 }
798
799 constexpr ValueType GetValuesByIndex(std::size_t index) const {
800 UASSERT_MSG(index < size(), "Index is out of bounds");
801 auto result = func_([index]() { return impl::CaseGetValuesByIndex<First, Second>{index}; });
802 return ValueType{.first = result.GetFirst(), .second = result.GetSecond()};
803 }
804
805 class Iterator {
806 public:
807 using iterator_category = std::input_iterator_tag;
808 using difference_type = std::ptrdiff_t;
809
810 explicit constexpr Iterator(const TrivialBiMap& map, std::size_t position)
811 : map_{map},
812 position_{position}
813 {}
814
815 constexpr bool operator==(Iterator other) const { return position_ == other.position_; }
816
817 constexpr bool operator!=(Iterator other) const { return position_ != other.position_; }
818
819 constexpr Iterator operator++() {
820 ++position_;
821 return *this;
822 }
823
824 constexpr Iterator operator++(int) {
825 Iterator copy{*this};
826 ++position_;
827 return copy;
828 }
829
830 constexpr ValueType operator*() const { return map_.GetValuesByIndex(position_); }
831
832 private:
833 const TrivialBiMap& map_;
834 std::size_t position_;
835 };
836
837 constexpr Iterator begin() const { return Iterator(*this, 0); }
838 constexpr Iterator end() const { return Iterator(*this, size()); }
839 constexpr Iterator cbegin() const { return begin(); }
840 constexpr Iterator cend() const { return end(); }
841
842private:
843 const BuilderFunc func_;
844};
845
846template <typename BuilderFunc>
847TrivialBiMap(BuilderFunc) -> TrivialBiMap<BuilderFunc>;
848
849/// @ingroup userver_universal userver_containers
850///
851/// @brief Unordered set for trivial types, including string literals.
852///
853/// For a two-value Case statements or efficiency notes
854/// see @ref utils::TrivialBiMap.
855template <typename BuilderFunc>
856class TrivialSet final {
857 using TypesPair = std::invoke_result_t<const BuilderFunc&, impl::SwitchTypesDetector>;
858
859public:
860 using First = typename TypesPair::first_type;
861 using Second = typename TypesPair::second_type;
862
863 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr TrivialSet(BuilderFunc&& func) noexcept : func_(std::move(func)) {
864 static_assert(std::is_empty_v<BuilderFunc>, "Mapping function should not capture variables");
865 static_assert(std::is_trivially_copyable_v<First>, "First type in Case must be trivially copyable");
866 static_assert(std::is_void_v<Second>, "Second type in Case should be skipped in utils::TrivialSet");
867 }
868
869 constexpr bool Contains(DecayToStringView<First> value) const noexcept {
870 return func_([value]() { return impl::SwitchByFirst<DecayToStringView<First>, Second>{value}; }).Extract();
871 }
872
873 constexpr bool ContainsICase(std::string_view value) const noexcept {
874 static_assert(
875 std::is_convertible_v<DecayToStringView<First>, std::string_view>,
876 "ContainsICase works only with std::string_view"
877 );
878
879 return func_([value]() { return impl::SwitchByFirstICase<void>{value}; }).Extract();
880 }
881
882 constexpr std::size_t size() const noexcept {
883 return func_([]() { return impl::CaseCounter{}; }).Extract();
884 }
885
886 /// Returns a string of comma separated quoted values of Case parameters.
887 ///
888 /// \b Example: "'a', 'b', 'c'"
889 ///
890 /// Parameters of Case should be formattable.
891 std::string Describe() const {
892 return func_([]() { return impl::CaseFirstDescriber{}; }).Extract();
893 }
894
895 /// Returns index of the value in Case parameters or std::nullopt if no such
896 /// value.
897 constexpr std::optional<std::size_t> GetIndex(DecayToStringView<First> value) const {
898 return func_([value]() { return impl::CaseFirstIndexer{value}; }).Extract();
899 }
900
901 /// Returns index of the case insensitive value in Case parameters or
902 /// std::nullopt if no such value.
903 constexpr std::optional<std::size_t> GetIndexICase(std::string_view value) const {
904 return func_([value]() { return impl::CaseFirstIndexerICase{value}; }).Extract();
905 }
906
907private:
908 const BuilderFunc func_;
909};
910
911template <typename BuilderFunc>
912TrivialSet(BuilderFunc) -> TrivialSet<BuilderFunc>;
913
914/// @brief Parses and returns whatever is specified by `map` from a
915/// `formats::json::Value` or another format's `Value`.
916/// @throws ExceptionType or `Value::Exception` by default, if `value` is not a
917/// string, or if `value` is not contained in `map`.
918/// @see @ref scripts/docs/en/userver/formats.md
919template <typename ExceptionType = void, typename Value, typename BuilderFunc>
920auto ParseFromValueString(const Value& value, TrivialBiMap<BuilderFunc> map) {
921 if constexpr (!std::is_void_v<ExceptionType>) {
922 if (!value.IsString()) {
923 throw ExceptionType(fmt::format("Invalid value at '{}': expected a string", value.GetPath()));
924 }
925 }
926
927 const auto string = value.template As<std::string>();
928 const auto parsed = map.TryFind(string);
929 if (parsed) {
930 return *parsed;
931 }
932
933 using Exception = std::conditional_t<std::is_void_v<ExceptionType>, typename Value::Exception, ExceptionType>;
934
935 throw Exception(fmt::format(
936 "Invalid value of {} at '{}': '{}' is not one of {}",
937 compiler::GetTypeName<std::decay_t<decltype(*parsed)>>(),
938 value.GetPath(),
939 string,
940 map.template DescribeByType<std::string_view>()
941 ));
942}
943
944namespace impl {
945
946// @brief Converts `value` to `std::string_view` using `map`. If `value` is not
947// contained in `map`, then crashes the service in Debug builds, or throws
948// utils::InvariantError in Release builds.
949template <typename Enum, typename BuilderFunc>
950std::string_view EnumToStringView(Enum value, TrivialBiMap<BuilderFunc> map) {
951 static_assert(std::is_enum_v<Enum>);
952 if (const auto string = map.TryFind(value)) {
953 return *string;
954 }
955
957 false,
958 fmt::format(
959 "Invalid value of enum {}: {}",
960 compiler::GetTypeName<Enum>(),
961 static_cast<std::underlying_type_t<Enum>>(value)
962 )
963 );
964}
965
966template <typename Selector, class Keys, typename Values, std::size_t... Indices>
967constexpr auto
968TrivialBiMapMultiCase(Selector selector, const Keys& keys, const Values& values, std::index_sequence<0, Indices...>) {
969 auto selector2 = selector.Case(std::data(keys)[0], std::data(values)[0]);
970 ((selector2 = selector2.Case(std::data(keys)[Indices], std::data(values)[Indices])), ...);
971 return selector2;
972}
973
974template <const auto& Keys, const auto& Values>
975struct TrivialBiMapMultiCaseDispatch {
976 template <class Selector>
977 constexpr auto operator()(Selector selector) const {
978 constexpr auto kKeysSize = std::size(Keys);
979 return impl::TrivialBiMapMultiCase(selector(), Keys, Values, std::make_index_sequence<kKeysSize>{});
980 }
981};
982
983template <typename Selector, class Values, std::size_t... Indices>
984constexpr auto TrivialSetMultiCase(Selector selector, const Values& values, std::index_sequence<0, Indices...>) {
985 auto selector2 = selector.Case(std::data(values)[0]);
986 ((selector2 = selector2.Case(std::data(values)[Indices])), ...);
987 return selector2;
988}
989
990template <const auto& Values>
991struct TrivialSetMultiCaseDispatch {
992 template <class Selector>
993 constexpr auto operator()(Selector selector) const {
994 constexpr auto kValuesSize = std::size(Values);
995 return impl::TrivialSetMultiCase(selector(), Values, std::make_index_sequence<kValuesSize>{});
996 }
997};
998
999} // namespace impl
1000
1001/// @brief Zips two global `constexpr` arrays into an utils::TrivialBiMap.
1002template <const auto& Keys, const auto& Values>
1003consteval auto MakeTrivialBiMap() {
1004 static_assert(std::size(Keys) == std::size(Values));
1005 static_assert(std::size(Keys) >= 1);
1006 return TrivialBiMap(impl::TrivialBiMapMultiCaseDispatch<Keys, Values>{});
1007}
1008
1009template <const auto& Values>
1010consteval auto MakeTrivialSet() {
1011 return TrivialSet(impl::TrivialSetMultiCaseDispatch<Values>{});
1012}
1013
1014} // namespace utils
1015
1016USERVER_NAMESPACE_END