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