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