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 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr SwitchByFirst& Type() noexcept {
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 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr SwitchByFirst& Type() noexcept {
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 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr SwitchByFirstICase& Type() noexcept {
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 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr SwitchByFirstICase& Type() noexcept {
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 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr SwitchBySecondICase& Type() noexcept {
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 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr SwitchBySecond& Type() noexcept {
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 USERVER_IMPL_NODEBUG_INLINE_FUNC 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 = void>
452 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr CaseCounter& Type() noexcept {
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 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr CaseDescriber& Type() noexcept {
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 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr CaseFirstDescriber& Type() noexcept {
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 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr CaseSecondDescriber& Type() noexcept {
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 constexpr explicit 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 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr CaseGetValuesByIndex& Type() noexcept {
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 CaseGetValuesByIndex<First, void> final {
588public:
589 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr explicit CaseGetValuesByIndex(std::size_t search_index)
590 : index_(search_index + 1)
591 {}
592
593 constexpr CaseGetValuesByIndex& Case(First first) noexcept {
594 if (index_ == 0) {
595 return *this;
596 }
597 if (index_ == 1) {
598 lazy_ = Lazy{first};
599 }
600 --index_;
601
602 return *this;
603 }
604
605 template <typename T>
606 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr CaseGetValuesByIndex& Type() noexcept {
607 return *this;
608 }
609
610 [[nodiscard]] constexpr First GetFirst() noexcept { return std::move(lazy_.first); }
611
612private:
613 std::size_t index_;
614
615 // Work with non default constructible types
616 union Lazy {
617 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr Lazy() noexcept : empty{} {}
618 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr Lazy(First f) noexcept : first{f} {}
619
620 char empty;
621 First first;
622 };
623 Lazy lazy_;
624};
625
626template <typename First>
627class CaseFirstIndexer final {
628public:
629 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr explicit CaseFirstIndexer(First search_value) noexcept : state_(search_value) {}
630
631 constexpr CaseFirstIndexer& Case(First first) noexcept {
632 if (!state_.IsFound() && state_.GetKey() == first) {
633 state_.SetValue(index_);
634 }
635 ++index_;
636 return *this;
637 }
638
639 template <typename T, typename U = void>
640 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr CaseFirstIndexer& Type() noexcept {
641 return *this;
642 }
643
644 [[nodiscard]] constexpr std::optional<std::size_t> Extract() && noexcept { return state_.Extract(); }
645
646private:
647 SearchState<First, std::size_t> state_;
648 std::size_t index_ = 0;
649};
650
651template <typename First>
652class CaseFirstIndexerICase final {
653public:
654 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr explicit CaseFirstIndexerICase(First search_value) noexcept : state_(search_value) {}
655
656 constexpr CaseFirstIndexerICase& Case(First first) noexcept {
657 if (!state_.IsFound() && state_.GetKey().size() == first.size() &&
658 impl::ICaseEqualLowercase(first, state_.GetKey()))
659 {
660 state_.SetValue(index_);
661 }
662 ++index_;
663 return *this;
664 }
665
666 template <typename T, typename U = void>
667 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr CaseFirstIndexerICase& Type() noexcept {
668 return *this;
669 }
670
671 [[nodiscard]] constexpr std::optional<std::size_t> Extract() && noexcept { return state_.Extract(); }
672
673private:
674 SearchState<First, std::size_t> state_;
675 std::size_t index_ = 0;
676};
677
678} // namespace impl
679
680/// Decays utils::StringLiteral and utils::zstring_view to a more generic std::string_view type.
681template <class T>
683 std::conditional_t<std::is_same_v<T, StringLiteral> || std::is_same_v<T, zstring_view>, std::string_view, T>;
684
685/// @ingroup userver_universal userver_containers
686///
687/// @brief Bidirectional unordered map for trivial types, including string
688/// literals; could be efficiently used as a unordered non-bidirectional map.
689///
690/// @snippet universal/src/utils/trivial_map_test.cpp sample string bimap
691///
692/// utils::TrivialBiMap and utils::TrivialSet are known to outperform
693/// std::unordered_map if:
694/// * there's 32 or less elements in map/set
695/// * or keys are string literals and all of them differ in length.
696///
697/// Implementation of string search is \b very efficient due to
698/// modern compilers optimize it to a switch by input string
699/// length and an integral comparison (rather than a std::memcmp call). In other
700/// words, it usually takes O(1) to find the match in the map.
701///
702/// The same story with integral or enum mappings - compiler optimizes them
703/// into a switch and it usually takes O(1) to find the match.
704///
705/// @snippet universal/src/utils/trivial_map_test.cpp sample bidir bimap
706///
707/// Empty map:
708///
709/// @snippet universal/src/utils/trivial_map_test.cpp sample empty bimap
710///
711/// For a single value Case statements see @ref utils::TrivialSet.
712template <typename BuilderFunc>
713class TrivialBiMap final {
714 using TypesPair = std::invoke_result_t<const BuilderFunc&, impl::SwitchTypesDetector>;
715
716public:
717 using First = typename TypesPair::first_type;
718 using Second = typename TypesPair::second_type;
719
720 struct ValueType {
721 First first;
722 Second second;
723 };
724
725 /// Returns Second if T is convertible to First, otherwise returns Second type.
726 template <class T>
727 using MappedTypeFor = std::conditional_t<std::is_convertible_v<T, DecayToStringView<First>>, Second, First>;
728
729 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr TrivialBiMap(BuilderFunc&& func) noexcept : func_(std::move(func)) {
730 static_assert(std::is_empty_v<BuilderFunc>, "Mapping function should not capture variables");
731 static_assert(std::is_trivially_copyable_v<First>, "First type in Case must be trivially copyable");
732 static_assert(
733 !std::is_void_v<Second>,
734 "If second type in Case is missing, use utils::TrivialSet instead of utils::TrivialBiMap"
735 );
736 static_assert(std::is_trivially_copyable_v<Second>, "Second type in Case must be trivially copyable");
737 }
738
739 constexpr std::optional<Second> TryFindByFirst(DecayToStringView<First> value) const noexcept {
740 return func_([value]() { return impl::SwitchByFirst<DecayToStringView<First>, Second>{value}; }).Extract();
741 }
742
743 constexpr std::optional<First> TryFindBySecond(DecayToStringView<Second> value) const noexcept {
744 return func_([value]() { return impl::SwitchBySecond<First, DecayToStringView<Second>>{value}; }).Extract();
745 }
746
747 template <class T>
748 constexpr std::optional<MappedTypeFor<T>> TryFind(T value) const noexcept {
749 if constexpr (std::is_convertible_v<T, DecayToStringView<First>>) {
750 static_assert(
751 !std::is_convertible_v<T, DecayToStringView<Second>>,
752 "Ambiguous conversion, use TryFindByFirst/TryFindBySecond instead"
753 );
754 return TryFindByFirst(value);
755 } else {
756 return TryFindBySecond(value);
757 }
758 }
759
760 /// @brief Case insensitive search for value.
761 ///
762 /// For efficiency reasons, first parameter in Case() should be lower case
763 /// string literal.
764 constexpr std::optional<Second> TryFindICaseByFirst(std::string_view value) const noexcept {
765 return func_([value]() { return impl::SwitchByFirstICase<Second>{value}; }).Extract();
766 }
767
768 /// @brief Case insensitive search for value.
769 ///
770 /// For efficiency reasons, second parameter in Case() should be lower case
771 /// string literal.
772 constexpr std::optional<First> TryFindICaseBySecond(std::string_view value) const noexcept {
773 return func_([value]() { return impl::SwitchBySecondICase<First>{value}; }).Extract();
774 }
775
776 /// @brief Case insensitive search for value that calls either
777 /// TryFindICaseBySecond or TryFindICaseByFirst.
778 constexpr std::optional<MappedTypeFor<std::string_view>> TryFindICase(std::string_view value) const noexcept {
779 if constexpr (std::is_convertible_v<std::string_view, DecayToStringView<First>>) {
780 static_assert(
781 !std::is_convertible_v<std::string_view, DecayToStringView<Second>>,
782 "Ambiguous conversion, use TryFindICaseByFirst/TryFindICaseBySecond"
783 );
784 return TryFindICaseByFirst(value);
785 } else {
786 return TryFindICaseBySecond(value);
787 }
788 }
789
790 /// Returns count of Case's in mapping
791 constexpr std::size_t size() const noexcept {
792 return func_([]() { return impl::CaseCounter{}; }).Extract();
793 }
794
795 /// Returns a string of comma separated quoted values of Case parameters.
796 ///
797 /// \b Example: "('a', '1'), ('b', '2'), ('c', '3')"
798 ///
799 /// Parameters of Case should be formattable.
800 std::string Describe() const {
801 return func_([]() { return impl::CaseDescriber{}; }).Extract();
802 }
803
804 /// Returns a string of comma separated quoted values of first Case
805 /// parameters.
806 ///
807 /// \b Example: "'a', 'b', 'c'"
808 ///
809 /// First parameters of Case should be formattable.
810 std::string DescribeFirst() const {
811 return func_([]() { return impl::CaseFirstDescriber{}; }).Extract();
812 }
813
814 /// Returns a string of comma separated quoted values of second Case
815 /// parameters.
816 ///
817 /// \b Example: "'1', '2', '3'"
818 ///
819 /// Second parameters of Case should be formattable.
820 std::string DescribeSecond() const {
821 return func_([]() { return impl::CaseSecondDescriber{}; }).Extract();
822 }
823
824 /// Returns a string of comma separated quoted values of Case
825 /// parameters that matches by type.
826 ///
827 /// \b Example: "'1', '2', '3'"
828 ///
829 /// Corresponding Case must be formattable
830 template <typename T>
831 std::string DescribeByType() const {
832 if constexpr (std::is_convertible_v<T, DecayToStringView<First>>) {
833 return DescribeFirst();
834 } else {
835 return DescribeSecond();
836 }
837 }
838
839 /// Returns the parameters of a Case with index `index`
840 constexpr ValueType GetValuesByIndex(std::size_t index) const {
841 UASSERT_MSG(index < size(), "Index is out of bounds");
842 auto result = func_([index]() { return impl::CaseGetValuesByIndex<First, Second>{index}; });
843 return ValueType{.first = result.GetFirst(), .second = result.GetSecond()};
844 }
845
846 class Iterator {
847 public:
848 using iterator_category = std::input_iterator_tag;
849 using difference_type = std::ptrdiff_t;
850 using value_type = ValueType;
851 using reference = ValueType;
852 using pointer = void;
853
854 constexpr explicit Iterator(const TrivialBiMap& map, std::size_t position)
855 : map_{&map},
856 position_{position}
857 {}
858
859 constexpr bool operator==(Iterator other) const { return position_ == other.position_; }
860
861 constexpr bool operator!=(Iterator other) const { return position_ != other.position_; }
862
863 constexpr Iterator& operator++() {
864 ++position_;
865 return *this;
866 }
867
868 constexpr Iterator operator++(int) {
869 Iterator copy{*this};
870 ++*this;
871 return copy;
872 }
873
874 constexpr ValueType operator*() const { return map_->GetValuesByIndex(position_); }
875
876 private:
877 const TrivialBiMap* map_;
878 std::size_t position_;
879 };
880
881 constexpr Iterator begin() const { return Iterator(*this, 0); }
882 constexpr Iterator end() const { return Iterator(*this, size()); }
883 constexpr Iterator cbegin() const { return begin(); }
884 constexpr Iterator cend() const { return end(); }
885
886private:
887 [[no_unique_address]] const BuilderFunc func_;
888};
889
890template <typename BuilderFunc>
891TrivialBiMap(BuilderFunc) -> TrivialBiMap<BuilderFunc>;
892
893/// @ingroup userver_universal userver_containers
894///
895/// @brief Unordered set for trivial types, including string literals.
896///
897/// For a two-value Case statements or efficiency notes
898/// see @ref utils::TrivialBiMap.
899template <typename BuilderFunc>
900class TrivialSet final {
901 using TypesPair = std::invoke_result_t<const BuilderFunc&, impl::SwitchTypesDetector>;
902
903public:
904 using First = typename TypesPair::first_type;
905 using Second = typename TypesPair::second_type;
906
907 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr TrivialSet(BuilderFunc&& func) noexcept : func_(std::move(func)) {
908 static_assert(std::is_empty_v<BuilderFunc>, "Mapping function should not capture variables");
909 static_assert(std::is_trivially_copyable_v<First>, "First type in Case must be trivially copyable");
910 static_assert(std::is_void_v<Second>, "Second type in Case should be skipped in utils::TrivialSet");
911 }
912
913 constexpr bool Contains(DecayToStringView<First> value) const noexcept {
914 return func_([value]() { return impl::SwitchByFirst<DecayToStringView<First>, Second>{value}; }).Extract();
915 }
916
917 constexpr bool ContainsICase(std::string_view value) const noexcept {
918 static_assert(
919 std::is_convertible_v<DecayToStringView<First>, std::string_view>,
920 "ContainsICase works only with std::string_view"
921 );
922
923 return func_([value]() { return impl::SwitchByFirstICase<void>{value}; }).Extract();
924 }
925
926 /// Returns count of Case's in mapping
927 constexpr std::size_t size() const noexcept {
928 return func_([]() { return impl::CaseCounter{}; }).Extract();
929 }
930
931 /// Returns a string of comma separated quoted values of Case parameters.
932 ///
933 /// \b Example: "'a', 'b', 'c'"
934 ///
935 /// Parameters of Case should be formattable.
936 std::string Describe() const {
937 return func_([]() { return impl::CaseFirstDescriber{}; }).Extract();
938 }
939
940 /// Returns index of the value in Case parameters or std::nullopt if no such value.
941 constexpr std::optional<std::size_t> GetIndex(DecayToStringView<First> value) const {
942 return func_([value]() { return impl::CaseFirstIndexer{value}; }).Extract();
943 }
944
945 /// Returns index of the case insensitive value in Case parameters or
946 /// std::nullopt if no such value.
947 constexpr std::optional<std::size_t> GetIndexICase(std::string_view value) const {
948 return func_([value]() { return impl::CaseFirstIndexerICase{value}; }).Extract();
949 }
950
951 /// Returns the parameter of a Case with index `index`
952 constexpr First GetKeyByIndex(std::size_t index) const {
953 UASSERT_MSG(index < size(), "Index is out of bounds");
954 auto result = func_([index]() { return impl::CaseGetValuesByIndex<First, Second>{index}; });
955 return result.GetFirst();
956 }
957
958private:
959 [[no_unique_address]] const BuilderFunc func_;
960};
961
962template <typename BuilderFunc>
963TrivialSet(BuilderFunc) -> TrivialSet<BuilderFunc>;
964
965/// @brief Parses and returns whatever is specified by `map` from a
966/// `formats::json::Value` or another format's `Value`.
967/// @throws ExceptionType or `Value::Exception` by default, if `value` is not a
968/// string, or if `value` is not contained in `map`.
969/// @see @ref scripts/docs/en/userver/formats.md
970template <typename ExceptionType = void, typename Value, typename BuilderFunc>
971auto ParseFromValueString(const Value& value, TrivialBiMap<BuilderFunc> map) {
972 if constexpr (!std::is_void_v<ExceptionType>) {
973 if (!value.IsString()) {
974 throw ExceptionType(fmt::format("Invalid value at '{}': expected a string", value.GetPath()));
975 }
976 }
977
978 const auto string = value.template As<std::string>();
979 const auto parsed = map.TryFind(string);
980 if (parsed) {
981 return *parsed;
982 }
983
984 using Exception = std::conditional_t<std::is_void_v<ExceptionType>, typename Value::Exception, ExceptionType>;
985
986 throw Exception(fmt::format(
987 "Invalid value of {} at '{}': '{}' is not one of {}",
988 compiler::GetTypeName<std::decay_t<decltype(*parsed)>>(),
989 value.GetPath(),
990 string,
991 map.template DescribeByType<std::string_view>()
992 ));
993}
994
995namespace impl {
996
997// @brief Converts `value` to `std::string_view` using `map`. If `value` is not
998// contained in `map`, then crashes the service in Debug builds, or throws
999// utils::InvariantError in Release builds.
1000template <typename Enum, typename BuilderFunc>
1001std::string_view EnumToStringView(Enum value, TrivialBiMap<BuilderFunc> map) {
1002 static_assert(std::is_enum_v<Enum>);
1003 if (const auto string = map.TryFind(value)) {
1004 return *string;
1005 }
1006
1007 UINVARIANT(
1008 false,
1009 fmt::format(
1010 "Invalid value of enum {}: {}",
1011 compiler::GetTypeName<Enum>(),
1012 static_cast<std::underlying_type_t<Enum>>(value)
1013 )
1014 );
1015}
1016
1017template <typename Selector, class Keys, typename Values, std::size_t... Indices>
1018constexpr auto
1019TrivialBiMapMultiCase(Selector selector, const Keys& keys, const Values& values, std::index_sequence<0, Indices...>) {
1020 auto selector2 = selector.Case(std::data(keys)[0], std::data(values)[0]);
1021 ((selector2 = selector2.Case(std::data(keys)[Indices], std::data(values)[Indices])), ...);
1022 return selector2;
1023}
1024
1025template <const auto& Keys, const auto& Values>
1026struct TrivialBiMapMultiCaseDispatch {
1027 template <class Selector>
1028 constexpr auto operator()(Selector selector) const {
1029 constexpr auto kKeysSize = std::size(Keys);
1030 return impl::TrivialBiMapMultiCase(selector(), Keys, Values, std::make_index_sequence<kKeysSize>{});
1031 }
1032};
1033
1034template <typename Selector, class Values, std::size_t... Indices>
1035constexpr auto TrivialSetMultiCase(Selector selector, const Values& values, std::index_sequence<0, Indices...>) {
1036 auto selector2 = selector.Case(std::data(values)[0]);
1037 ((selector2 = selector2.Case(std::data(values)[Indices])), ...);
1038 return selector2;
1039}
1040
1041template <const auto& Values>
1042struct TrivialSetMultiCaseDispatch {
1043 template <class Selector>
1044 constexpr auto operator()(Selector selector) const {
1045 constexpr auto kValuesSize = std::size(Values);
1046 return impl::TrivialSetMultiCase(selector(), Values, std::make_index_sequence<kValuesSize>{});
1047 }
1048};
1049
1050} // namespace impl
1051
1052/// @brief Zips two global `constexpr` arrays into an utils::TrivialBiMap.
1053template <const auto& Keys, const auto& Values>
1054consteval auto MakeTrivialBiMap() {
1055 static_assert(std::size(Keys) == std::size(Values));
1056 static_assert(std::size(Keys) >= 1);
1057 return TrivialBiMap(impl::TrivialBiMapMultiCaseDispatch<Keys, Values>{});
1058}
1059
1060template <const auto& Values>
1061consteval auto MakeTrivialSet() {
1062 return TrivialSet(impl::TrivialSetMultiCaseDispatch<Values>{});
1063}
1064
1065} // namespace utils
1066
1067USERVER_NAMESPACE_END