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>
682using DecayToStringView =
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
851 constexpr explicit Iterator(const TrivialBiMap& map, std::size_t position)
852 : map_{map},
853 position_{position}
854 {}
855
856 constexpr bool operator==(Iterator other) const { return position_ == other.position_; }
857
858 constexpr bool operator!=(Iterator other) const { return position_ != other.position_; }
859
860 constexpr Iterator operator++() {
861 ++position_;
862 return *this;
863 }
864
865 constexpr Iterator operator++(int) {
866 Iterator copy{*this};
867 ++position_;
868 return copy;
869 }
870
871 constexpr ValueType operator*() const { return map_.GetValuesByIndex(position_); }
872
873 private:
874 const TrivialBiMap& map_;
875 std::size_t position_;
876 };
877
878 constexpr Iterator begin() const { return Iterator(*this, 0); }
879 constexpr Iterator end() const { return Iterator(*this, size()); }
880 constexpr Iterator cbegin() const { return begin(); }
881 constexpr Iterator cend() const { return end(); }
882
883private:
884 [[no_unique_address]] const BuilderFunc func_;
885};
886
887template <typename BuilderFunc>
888TrivialBiMap(BuilderFunc) -> TrivialBiMap<BuilderFunc>;
889
890/// @ingroup userver_universal userver_containers
891///
892/// @brief Unordered set for trivial types, including string literals.
893///
894/// For a two-value Case statements or efficiency notes
895/// see @ref utils::TrivialBiMap.
896template <typename BuilderFunc>
897class TrivialSet final {
898 using TypesPair = std::invoke_result_t<const BuilderFunc&, impl::SwitchTypesDetector>;
899
900public:
901 using First = typename TypesPair::first_type;
902 using Second = typename TypesPair::second_type;
903
904 USERVER_IMPL_NODEBUG_INLINE_FUNC constexpr TrivialSet(BuilderFunc&& func) noexcept : func_(std::move(func)) {
905 static_assert(std::is_empty_v<BuilderFunc>, "Mapping function should not capture variables");
906 static_assert(std::is_trivially_copyable_v<First>, "First type in Case must be trivially copyable");
907 static_assert(std::is_void_v<Second>, "Second type in Case should be skipped in utils::TrivialSet");
908 }
909
910 constexpr bool Contains(DecayToStringView<First> value) const noexcept {
911 return func_([value]() { return impl::SwitchByFirst<DecayToStringView<First>, Second>{value}; }).Extract();
912 }
913
914 constexpr bool ContainsICase(std::string_view value) const noexcept {
915 static_assert(
916 std::is_convertible_v<DecayToStringView<First>, std::string_view>,
917 "ContainsICase works only with std::string_view"
918 );
919
920 return func_([value]() { return impl::SwitchByFirstICase<void>{value}; }).Extract();
921 }
922
923 /// Returns count of Case's in mapping
924 constexpr std::size_t size() const noexcept {
925 return func_([]() { return impl::CaseCounter{}; }).Extract();
926 }
927
928 /// Returns a string of comma separated quoted values of Case parameters.
929 ///
930 /// \b Example: "'a', 'b', 'c'"
931 ///
932 /// Parameters of Case should be formattable.
933 std::string Describe() const {
934 return func_([]() { return impl::CaseFirstDescriber{}; }).Extract();
935 }
936
937 /// Returns index of the value in Case parameters or std::nullopt if no such value.
938 constexpr std::optional<std::size_t> GetIndex(DecayToStringView<First> value) const {
939 return func_([value]() { return impl::CaseFirstIndexer{value}; }).Extract();
940 }
941
942 /// Returns index of the case insensitive value in Case parameters or
943 /// std::nullopt if no such value.
944 constexpr std::optional<std::size_t> GetIndexICase(std::string_view value) const {
945 return func_([value]() { return impl::CaseFirstIndexerICase{value}; }).Extract();
946 }
947
948 /// Returns the parameter of a Case with index `index`
949 constexpr First GetKeyByIndex(std::size_t index) const {
950 UASSERT_MSG(index < size(), "Index is out of bounds");
951 auto result = func_([index]() { return impl::CaseGetValuesByIndex<First, Second>{index}; });
952 return result.GetFirst();
953 }
954
955private:
956 [[no_unique_address]] const BuilderFunc func_;
957};
958
959template <typename BuilderFunc>
960TrivialSet(BuilderFunc) -> TrivialSet<BuilderFunc>;
961
962/// @brief Parses and returns whatever is specified by `map` from a
963/// `formats::json::Value` or another format's `Value`.
964/// @throws ExceptionType or `Value::Exception` by default, if `value` is not a
965/// string, or if `value` is not contained in `map`.
966/// @see @ref scripts/docs/en/userver/formats.md
967template <typename ExceptionType = void, typename Value, typename BuilderFunc>
968auto ParseFromValueString(const Value& value, TrivialBiMap<BuilderFunc> map) {
969 if constexpr (!std::is_void_v<ExceptionType>) {
970 if (!value.IsString()) {
971 throw ExceptionType(fmt::format("Invalid value at '{}': expected a string", value.GetPath()));
972 }
973 }
974
975 const auto string = value.template As<std::string>();
976 const auto parsed = map.TryFind(string);
977 if (parsed) {
978 return *parsed;
979 }
980
981 using Exception = std::conditional_t<std::is_void_v<ExceptionType>, typename Value::Exception, ExceptionType>;
982
983 throw Exception(fmt::format(
984 "Invalid value of {} at '{}': '{}' is not one of {}",
985 compiler::GetTypeName<std::decay_t<decltype(*parsed)>>(),
986 value.GetPath(),
987 string,
988 map.template DescribeByType<std::string_view>()
989 ));
990}
991
992namespace impl {
993
994// @brief Converts `value` to `std::string_view` using `map`. If `value` is not
995// contained in `map`, then crashes the service in Debug builds, or throws
996// utils::InvariantError in Release builds.
997template <typename Enum, typename BuilderFunc>
998std::string_view EnumToStringView(Enum value, TrivialBiMap<BuilderFunc> map) {
999 static_assert(std::is_enum_v<Enum>);
1000 if (const auto string = map.TryFind(value)) {
1001 return *string;
1002 }
1003
1004 UINVARIANT(
1005 false,
1006 fmt::format(
1007 "Invalid value of enum {}: {}",
1008 compiler::GetTypeName<Enum>(),
1009 static_cast<std::underlying_type_t<Enum>>(value)
1010 )
1011 );
1012}
1013
1014template <typename Selector, class Keys, typename Values, std::size_t... Indices>
1015constexpr auto
1016TrivialBiMapMultiCase(Selector selector, const Keys& keys, const Values& values, std::index_sequence<0, Indices...>) {
1017 auto selector2 = selector.Case(std::data(keys)[0], std::data(values)[0]);
1018 ((selector2 = selector2.Case(std::data(keys)[Indices], std::data(values)[Indices])), ...);
1019 return selector2;
1020}
1021
1022template <const auto& Keys, const auto& Values>
1023struct TrivialBiMapMultiCaseDispatch {
1024 template <class Selector>
1025 constexpr auto operator()(Selector selector) const {
1026 constexpr auto kKeysSize = std::size(Keys);
1027 return impl::TrivialBiMapMultiCase(selector(), Keys, Values, std::make_index_sequence<kKeysSize>{});
1028 }
1029};
1030
1031template <typename Selector, class Values, std::size_t... Indices>
1032constexpr auto TrivialSetMultiCase(Selector selector, const Values& values, std::index_sequence<0, Indices...>) {
1033 auto selector2 = selector.Case(std::data(values)[0]);
1034 ((selector2 = selector2.Case(std::data(values)[Indices])), ...);
1035 return selector2;
1036}
1037
1038template <const auto& Values>
1039struct TrivialSetMultiCaseDispatch {
1040 template <class Selector>
1041 constexpr auto operator()(Selector selector) const {
1042 constexpr auto kValuesSize = std::size(Values);
1043 return impl::TrivialSetMultiCase(selector(), Values, std::make_index_sequence<kValuesSize>{});
1044 }
1045};
1046
1047} // namespace impl
1048
1049/// @brief Zips two global `constexpr` arrays into an utils::TrivialBiMap.
1050template <const auto& Keys, const auto& Values>
1051consteval auto MakeTrivialBiMap() {
1052 static_assert(std::size(Keys) == std::size(Values));
1053 static_assert(std::size(Keys) >= 1);
1054 return TrivialBiMap(impl::TrivialBiMapMultiCaseDispatch<Keys, Values>{});
1055}
1056
1057template <const auto& Values>
1058consteval auto MakeTrivialSet() {
1059 return TrivialSet(impl::TrivialSetMultiCaseDispatch<Values>{});
1060}
1061
1062} // namespace utils
1063
1064USERVER_NAMESPACE_END