14#include <fmt/format.h>
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>
21USERVER_NAMESPACE_BEGIN
27constexpr bool HasUppercaseAscii(std::string_view value)
noexcept {
28 for (
auto c : value) {
29 if (
'A' <= c && c <=
'Z') {
37constexpr bool ICaseEqualLowercase(std::string_view lowercase, std::string_view y)
noexcept {
38 const auto size = lowercase.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]) {
55 constexpr explicit Found(std::size_t value)
noexcept {
UASSERT(value == 0); }
57 constexpr explicit operator std::size_t()
const noexcept {
return 0; }
60template <
typename Key,
typename Value>
61class SearchState
final {
63 constexpr explicit SearchState(Key key)
noexcept : key_or_result_(std::in_place_index<0>, key) {}
65 constexpr bool IsFound()
const noexcept {
return key_or_result_.index() != 0; }
67 constexpr Key GetKey()
const noexcept {
69 return std::get<0>(key_or_result_);
72 constexpr void SetValue(Value value)
noexcept {
73 key_or_result_ = std::variant<Key, Value>(std::in_place_index<1>, value);
76 [[nodiscard]]
constexpr std::optional<Value> Extract()
noexcept {
77 if (key_or_result_.index() == 1) {
78 return std::get<1>(key_or_result_);
85 std::variant<Key, Value> key_or_result_;
88inline constexpr std::size_t kInvalidSize = std::numeric_limits<std::size_t>::max();
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>);
96template <
typename Payload>
97class StringOrPayload
final {
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();
110 constexpr explicit StringOrPayload(Payload payload)
noexcept : data_or_payload_(payload), size_(kInvalidSize) {}
112 constexpr bool HasPayload()
const noexcept {
return size_ == kInvalidSize; }
114 constexpr const char* GetStringPointer()
const noexcept {
116 UASSERT(data_or_payload_.data);
117 return data_or_payload_.data;
120 constexpr std::size_t GetStringSize()
const noexcept {
122 UASSERT(data_or_payload_.data);
126 constexpr Payload GetPayload()
const noexcept {
128 return data_or_payload_.payload;
132 static_assert(FitsInStringOrPayload<Payload>);
134 union DataOrPayload {
135 constexpr explicit DataOrPayload(
const char* data)
noexcept : data(data) {}
137 constexpr explicit DataOrPayload(Payload payload)
noexcept : payload(payload) {}
143 DataOrPayload data_or_payload_;
147template <FitsInStringOrPayload Value>
148class SearchState<std::string_view, Value> {
150 constexpr explicit SearchState(std::string_view key)
noexcept : state_(key) {}
152 constexpr bool IsFound()
const noexcept {
return state_.HasPayload(); }
154 constexpr std::string_view GetKey()
const noexcept {
155 return std::string_view{state_.GetStringPointer(), state_.GetStringSize()};
158 constexpr void SetValue(Value value)
noexcept { state_ = StringOrPayload<Value>{value}; }
160 [[nodiscard]]
constexpr std::optional<Value> Extract()
noexcept {
161 return IsFound() ? std::optional{state_.GetPayload()} : std::nullopt;
165 StringOrPayload<Value> state_;
168template <FitsInStringOrPayload Value>
169class SearchState<
zstring_view, Value>
final :
public SearchState<std::string_view, Value> {};
171template <FitsInStringOrPayload Value>
172class SearchState<
StringLiteral, Value>
final :
public SearchState<std::string_view, Value> {};
174template <FitsInStringOrPayload Key>
175class SearchState<Key, std::string_view>
final {
177 constexpr explicit SearchState(Key key)
noexcept : state_(key) {}
179 constexpr bool IsFound()
const noexcept {
return !state_.HasPayload(); }
181 constexpr Key GetKey()
const noexcept {
return state_.GetPayload(); }
183 constexpr void SetValue(std::string_view value)
noexcept { state_ = StringOrPayload<Key>{value}; }
185 [[nodiscard]]
constexpr std::optional<std::string_view> Extract()
noexcept {
186 return IsFound() ? std::optional{std::string_view{state_.GetStringPointer(), state_.GetStringSize()}}
191 StringOrPayload<Key> state_;
194template <FitsInStringOrPayload Key>
197 constexpr explicit SearchState(Key key)
noexcept : state_(key) {}
199 constexpr bool IsFound()
const noexcept {
return !state_.HasPayload(); }
201 constexpr Key GetKey()
const noexcept {
return state_.GetPayload(); }
203 constexpr void SetValue(
zstring_view value)
noexcept { state_ = StringOrPayload<Key>{value}; }
205 [[nodiscard]]
constexpr std::optional<
zstring_view> Extract()
noexcept {
211 StringOrPayload<Key> state_;
214template <FitsInStringOrPayload Key>
217 constexpr explicit SearchState(Key key)
noexcept : state_(key) {}
219 constexpr bool IsFound()
const noexcept {
return !state_.HasPayload(); }
221 constexpr Key GetKey()
const noexcept {
return state_.GetPayload(); }
223 constexpr void SetValue(
StringLiteral value)
noexcept { state_ = StringOrPayload<Key>{value}; }
225 [[nodiscard]]
constexpr std::optional<
StringLiteral> Extract()
noexcept {
231 StringOrPayload<Key> state_;
234template <
typename First,
typename Second>
235class SwitchByFirst
final {
237 USERVER_IMPL_NODEBUG_INLINE_FUNC
constexpr explicit SwitchByFirst(First search)
noexcept : state_(search) {}
239 constexpr SwitchByFirst& Case(First first, Second second)
noexcept {
240 if (!state_.IsFound() && state_.GetKey() == first) {
241 state_.SetValue(second);
246 template <
typename T,
typename U =
void>
247 constexpr SwitchByFirst& Type() {
251 [[nodiscard]]
constexpr std::optional<Second> Extract()
noexcept {
return state_.Extract(); }
254 SearchState<First, Second> state_;
257template <
typename First>
258class SwitchByFirst<First,
void>
final {
260 USERVER_IMPL_NODEBUG_INLINE_FUNC
constexpr explicit SwitchByFirst(First search)
noexcept : state_(search) {}
262 constexpr SwitchByFirst& Case(First first)
noexcept {
263 if (!state_.IsFound() && state_.GetKey() == first) {
264 state_.SetValue(Found{0});
269 template <
typename T,
typename U =
void>
270 constexpr SwitchByFirst& Type() {
274 [[nodiscard]]
constexpr bool Extract()
noexcept {
return state_.IsFound(); }
277 SearchState<First, Found> state_;
280template <
typename Second>
281class SwitchByFirstICase
final {
283 USERVER_IMPL_NODEBUG_INLINE_FUNC
constexpr explicit SwitchByFirstICase(std::string_view search)
noexcept : state_(search) {}
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)
290 if (!state_.IsFound() && state_.GetKey().size() == first.size() &&
291 impl::ICaseEqualLowercase(first, state_.GetKey()))
293 state_.SetValue(second);
298 template <
typename T,
typename U>
299 constexpr SwitchByFirstICase& Type() {
303 [[nodiscard]]
constexpr std::optional<Second> Extract()
noexcept {
return state_.Extract(); }
306 SearchState<std::string_view, Second> state_;
310class SwitchByFirstICase<
void>
final {
312 USERVER_IMPL_NODEBUG_INLINE_FUNC
constexpr explicit SwitchByFirstICase(std::string_view search)
noexcept : state_(search) {}
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)
319 if (!state_.IsFound() && state_.GetKey().size() == first.size() &&
320 impl::ICaseEqualLowercase(first, state_.GetKey()))
322 state_.SetValue(Found{0});
327 template <
typename T,
typename U>
328 constexpr SwitchByFirstICase& Type() {
332 [[nodiscard]]
constexpr bool Extract()
const noexcept {
return state_.IsFound(); }
335 SearchState<std::string_view, Found> state_;
338template <
typename First>
339class SwitchBySecondICase
final {
341 USERVER_IMPL_NODEBUG_INLINE_FUNC
constexpr explicit SwitchBySecondICase(std::string_view search)
noexcept : state_(search) {}
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)
348 if (!state_.IsFound() && state_.GetKey().size() == second.size() &&
349 impl::ICaseEqualLowercase(second, state_.GetKey()))
351 state_.SetValue(first);
356 template <
typename T,
typename U>
357 constexpr SwitchBySecondICase& Type() {
361 [[nodiscard]]
constexpr std::optional<First> Extract()
noexcept {
return state_.Extract(); }
364 SearchState<std::string_view, First> state_;
367template <
typename First,
typename Second>
368class SwitchBySecond
final {
370 USERVER_IMPL_NODEBUG_INLINE_FUNC
constexpr explicit SwitchBySecond(Second search)
noexcept : state_(search) {}
372 constexpr SwitchBySecond& Case(First first, Second second)
noexcept {
373 if (!state_.IsFound() && state_.GetKey() == second) {
374 state_.SetValue(first);
379 template <
typename T,
typename U>
380 constexpr SwitchBySecond& Type() {
384 [[nodiscard]]
constexpr std::optional<First> Extract()
noexcept {
return state_.Extract(); }
387 SearchState<Second, First> state_;
390template <
typename First,
typename Second>
391class SwitchTypesDetected
final {
393 using first_type = First;
394 using second_type = Second;
396 constexpr SwitchTypesDetected& Case(First, Second)
noexcept {
return *
this; }
399template <
typename First>
400class SwitchTypesDetected<First,
void>
final {
402 using first_type = First;
403 using second_type =
void;
405 constexpr SwitchTypesDetected& Case(First)
noexcept {
return *
this; }
408class SwitchTypesDetector
final {
411 using DetectType = std::conditional_t<
417 std::conditional_t<std::is_convertible_v<T, std::string_view>, std::string_view, T>>>;
419 constexpr SwitchTypesDetector& operator()()
noexcept {
return *
this; }
421 template <
typename First,
typename Second>
422 constexpr auto Case(First, Second)
noexcept {
423 return Type<First, Second>();
426 template <
typename First>
427 constexpr auto Case(First)
noexcept {
428 return Type<First,
void>();
431 template <
typename First,
typename Second =
void>
432 constexpr auto Type()
noexcept {
433 return SwitchTypesDetected<DetectType<First>, DetectType<Second>>{};
437class CaseCounter
final {
439 template <
typename First,
typename Second>
440 constexpr CaseCounter& Case(First, Second)
noexcept {
445 template <
typename First>
446 constexpr CaseCounter& Case(First)
noexcept {
451 template <
typename T,
typename U>
452 constexpr CaseCounter& Type() {
456 [[nodiscard]]
constexpr std::size_t Extract()
const noexcept {
return count_; }
459 std::size_t count_{0};
462class CaseDescriber
final {
464 template <
typename First,
typename Second>
465 CaseDescriber& Case(First first, Second second)
noexcept {
466 if (!description_.empty()) {
467 description_ +=
", ";
470 description_ += fmt::format(
"('{}', '{}')", first, second);
475 template <
typename T,
typename U>
476 constexpr CaseDescriber& Type() {
480 [[nodiscard]] std::string Extract() &&
noexcept {
return std::move(description_); }
483 std::string description_{};
486class CaseFirstDescriber
final {
488 template <
typename First>
489 CaseFirstDescriber& Case(First first)
noexcept {
490 if (!description_.empty()) {
491 description_ +=
", ";
494 description_ += fmt::format(
"'{}'", first);
499 template <
typename First,
typename Second>
500 CaseFirstDescriber& Case(First first, Second )
noexcept {
504 template <
typename T,
typename U =
void>
505 constexpr CaseFirstDescriber& Type() {
509 [[nodiscard]] std::string Extract() &&
noexcept {
return std::move(description_); }
512 std::string description_{};
515class CaseSecondDescriber
final {
517 template <
typename First,
typename Second>
518 CaseSecondDescriber& Case(First , Second second)
noexcept {
519 if (!description_.empty()) {
520 description_ +=
", ";
523 description_ += fmt::format(
"'{}'", second);
528 template <
typename T,
typename U>
529 constexpr CaseSecondDescriber& Type() {
533 [[nodiscard]] std::string Extract() &&
noexcept {
return std::move(description_); }
536 std::string description_{};
539template <
typename First,
typename Second>
540class CaseGetValuesByIndex
final {
542 USERVER_IMPL_NODEBUG_INLINE_FUNC
explicit constexpr CaseGetValuesByIndex(std::size_t search_index)
543 : index_(search_index + 1)
546 constexpr CaseGetValuesByIndex& Case(First first, Second second)
noexcept {
551 lazy_ = Lazy{Storage{first, second}};
558 template <
typename T,
typename U>
559 constexpr CaseGetValuesByIndex& Type() {
563 [[nodiscard]]
constexpr First GetFirst()
noexcept {
return std::move(lazy_.storage.first); }
565 [[nodiscard]]
constexpr Second GetSecond()
noexcept {
return std::move(lazy_.storage.second); }
577 USERVER_IMPL_NODEBUG_INLINE_FUNC
constexpr Lazy()
noexcept : empty{} {}
578 USERVER_IMPL_NODEBUG_INLINE_FUNC
constexpr Lazy(Storage s)
noexcept : storage{s} {}
586template <
typename First>
587class CaseFirstIndexer
final {
589 USERVER_IMPL_NODEBUG_INLINE_FUNC
constexpr explicit CaseFirstIndexer(First search_value)
noexcept : state_(search_value) {}
591 constexpr CaseFirstIndexer& Case(First first)
noexcept {
592 if (!state_.IsFound() && state_.GetKey() == first) {
593 state_.SetValue(index_);
599 template <
typename T,
typename U =
void>
600 constexpr CaseFirstIndexer& Type() {
604 [[nodiscard]]
constexpr std::optional<std::size_t> Extract() &&
noexcept {
return state_.Extract(); }
607 SearchState<First, std::size_t> state_;
608 std::size_t index_ = 0;
611template <
typename First>
612class CaseFirstIndexerICase
final {
614 USERVER_IMPL_NODEBUG_INLINE_FUNC
constexpr explicit CaseFirstIndexerICase(First search_value)
noexcept : state_(search_value) {}
616 constexpr CaseFirstIndexerICase& Case(First first)
noexcept {
617 if (!state_.IsFound() && state_.GetKey().size() == first.size() &&
618 impl::ICaseEqualLowercase(first, state_.GetKey()))
620 state_.SetValue(index_);
626 template <
typename T,
typename U =
void>
627 constexpr CaseFirstIndexerICase& Type() {
631 [[nodiscard]]
constexpr std::optional<std::size_t> Extract() &&
noexcept {
return state_.Extract(); }
634 SearchState<First, std::size_t> state_;
635 std::size_t index_ = 0;
642using DecayToStringView =
672template <
typename BuilderFunc>
673class TrivialBiMap
final {
674 using TypesPair = std::invoke_result_t<
const BuilderFunc&, impl::SwitchTypesDetector>;
677 using First =
typename TypesPair::first_type;
678 using Second =
typename TypesPair::second_type;
687 using MappedTypeFor = std::conditional_t<std::is_convertible_v<T, DecayToStringView<First>>, Second, First>;
689 USERVER_IMPL_NODEBUG_INLINE_FUNC
constexpr TrivialBiMap(BuilderFunc&& func)
noexcept : func_(std::move(func)) {
690 static_assert(std::is_empty_v<BuilderFunc>,
"Mapping function should not capture variables");
691 static_assert(std::is_trivially_copyable_v<First>,
"First type in Case must be trivially copyable");
693 !std::is_void_v<Second>,
694 "If second type in Case is missing, use utils::TrivialSet instead of utils::TrivialBiMap"
696 static_assert(std::is_trivially_copyable_v<Second>,
"Second type in Case must be trivially copyable");
699 constexpr std::optional<Second> TryFindByFirst(DecayToStringView<First> value)
const noexcept {
700 return func_([value]() {
return impl::SwitchByFirst<DecayToStringView<First>, Second>{value}; }).Extract();
703 constexpr std::optional<First> TryFindBySecond(DecayToStringView<Second> value)
const noexcept {
704 return func_([value]() {
return impl::SwitchBySecond<First, DecayToStringView<Second>>{value}; }).Extract();
708 constexpr std::optional<MappedTypeFor<T>> TryFind(T value)
const noexcept {
709 if constexpr (std::is_convertible_v<T, DecayToStringView<First>>) {
711 !std::is_convertible_v<T, DecayToStringView<Second>>,
712 "Ambiguous conversion, use TryFindByFirst/TryFindBySecond instead"
714 return TryFindByFirst(value);
716 return TryFindBySecond(value);
725 return func_([value]() {
return impl::SwitchByFirstICase<Second>{value}; }).Extract();
733 return func_([value]() {
return impl::SwitchBySecondICase<First>{value}; }).Extract();
738 constexpr std::optional<MappedTypeFor<std::string_view>>
TryFindICase(std::string_view value)
const noexcept {
739 if constexpr (std::is_convertible_v<std::string_view, DecayToStringView<First>>) {
741 !std::is_convertible_v<std::string_view, DecayToStringView<Second>>,
742 "Ambiguous conversion, use TryFindICaseByFirst/TryFindICaseBySecond"
751 constexpr std::size_t
size()
const noexcept {
752 return func_([]() {
return impl::CaseCounter{}; }).Extract();
761 return func_([]() {
return impl::CaseDescriber{}; }).Extract();
771 return func_([]() {
return impl::CaseFirstDescriber{}; }).Extract();
781 return func_([]() {
return impl::CaseSecondDescriber{}; }).Extract();
790 template <
typename T>
792 if constexpr (std::is_convertible_v<T, DecayToStringView<First>>) {
799 constexpr ValueType GetValuesByIndex(std::size_t index)
const {
801 auto result = func_([index]() {
return impl::CaseGetValuesByIndex<First, Second>{index}; });
802 return ValueType{.first = result.GetFirst(), .second = result.GetSecond()};
807 using iterator_category = std::input_iterator_tag;
808 using difference_type = std::ptrdiff_t;
810 explicit constexpr Iterator(
const TrivialBiMap& map, std::size_t position)
815 constexpr bool operator==(
Iterator other)
const {
return position_ == other.position_; }
817 constexpr bool operator!=(
Iterator other)
const {
return position_ != other.position_; }
824 constexpr Iterator operator++(
int) {
830 constexpr ValueType operator*()
const {
return map_.GetValuesByIndex(position_); }
833 const TrivialBiMap& map_;
834 std::size_t position_;
839 constexpr Iterator cbegin()
const {
return begin(); }
840 constexpr Iterator cend()
const {
return end(); }
843 const BuilderFunc func_;
846template <
typename BuilderFunc>
847TrivialBiMap(BuilderFunc) -> TrivialBiMap<BuilderFunc>;
855template <
typename BuilderFunc>
856class TrivialSet
final {
857 using TypesPair = std::invoke_result_t<
const BuilderFunc&, impl::SwitchTypesDetector>;
860 using First =
typename TypesPair::first_type;
861 using Second =
typename TypesPair::second_type;
863 USERVER_IMPL_NODEBUG_INLINE_FUNC
constexpr TrivialSet(BuilderFunc&& func)
noexcept : func_(std::move(func)) {
864 static_assert(std::is_empty_v<BuilderFunc>,
"Mapping function should not capture variables");
865 static_assert(std::is_trivially_copyable_v<First>,
"First type in Case must be trivially copyable");
866 static_assert(std::is_void_v<Second>,
"Second type in Case should be skipped in utils::TrivialSet");
869 constexpr bool Contains(DecayToStringView<First> value)
const noexcept {
870 return func_([value]() {
return impl::SwitchByFirst<DecayToStringView<First>, Second>{value}; }).Extract();
873 constexpr bool ContainsICase(std::string_view value)
const noexcept {
875 std::is_convertible_v<DecayToStringView<First>, std::string_view>,
876 "ContainsICase works only with std::string_view"
879 return func_([value]() {
return impl::SwitchByFirstICase<
void>{value}; }).Extract();
882 constexpr std::size_t size()
const noexcept {
883 return func_([]() {
return impl::CaseCounter{}; }).Extract();
892 return func_([]() {
return impl::CaseFirstDescriber{}; }).Extract();
897 constexpr std::optional<std::size_t>
GetIndex(DecayToStringView<First> value)
const {
898 return func_([value]() {
return impl::CaseFirstIndexer{value}; }).Extract();
903 constexpr std::optional<std::size_t>
GetIndexICase(std::string_view value)
const {
904 return func_([value]() {
return impl::CaseFirstIndexerICase{value}; }).Extract();
908 const BuilderFunc func_;
911template <
typename BuilderFunc>
912TrivialSet(BuilderFunc) -> TrivialSet<BuilderFunc>;
919template <
typename ExceptionType =
void,
typename Value,
typename BuilderFunc>
921 if constexpr (!std::is_void_v<ExceptionType>) {
922 if (!value.IsString()) {
923 throw ExceptionType(fmt::format(
"Invalid value at '{}': expected a string", value.GetPath()));
927 const auto string = value.
template As<std::string>();
928 const auto parsed = map.TryFind(string);
933 using Exception = std::conditional_t<std::is_void_v<ExceptionType>,
typename Value::Exception, ExceptionType>;
935 throw Exception(fmt::format(
936 "Invalid value of {} at '{}': '{}' is not one of {}",
937 compiler::GetTypeName<std::decay_t<
decltype(*parsed)>>(),
940 map.
template DescribeByType<std::string_view>()
949template <
typename Enum,
typename BuilderFunc>
950std::string_view EnumToStringView(Enum value, TrivialBiMap<BuilderFunc> map) {
951 static_assert(std::is_enum_v<Enum>);
952 if (
const auto string = map.TryFind(value)) {
959 "Invalid value of enum {}: {}",
961 static_cast<std::underlying_type_t<Enum>>(value)
966template <
typename Selector,
class Keys,
typename Values, std::size_t... Indices>
968TrivialBiMapMultiCase(Selector selector,
const Keys& keys,
const Values& values, std::index_sequence<0, Indices...>) {
969 auto selector2 = selector.Case(std::data(keys)[0], std::data(values)[0]);
970 ((selector2 = selector2.Case(std::data(keys)[Indices], std::data(values)[Indices])), ...);
974template <
const auto& Keys,
const auto& Values>
975struct TrivialBiMapMultiCaseDispatch {
976 template <
class Selector>
977 constexpr auto operator()(Selector selector)
const {
978 constexpr auto kKeysSize = std::size(Keys);
979 return impl::TrivialBiMapMultiCase(selector(), Keys, Values, std::make_index_sequence<kKeysSize>{});
983template <
typename Selector,
class Values, std::size_t... Indices>
984constexpr auto TrivialSetMultiCase(Selector selector,
const Values& values, std::index_sequence<0, Indices...>) {
985 auto selector2 = selector.Case(std::data(values)[0]);
986 ((selector2 = selector2.Case(std::data(values)[Indices])), ...);
990template <
const auto& Values>
991struct TrivialSetMultiCaseDispatch {
992 template <
class Selector>
993 constexpr auto operator()(Selector selector)
const {
994 constexpr auto kValuesSize = std::size(Values);
995 return impl::TrivialSetMultiCase(selector(), Values, std::make_index_sequence<kValuesSize>{});
1002template <
const auto& Keys,
const auto& Values>
1004 static_assert(std::size(Keys) == std::size(Values));
1005 static_assert(std::size(Keys) >= 1);
1006 return TrivialBiMap(impl::TrivialBiMapMultiCaseDispatch<Keys, Values>{});
1009template <
const auto& Values>
1010consteval auto MakeTrivialSet() {
1011 return TrivialSet(impl::TrivialSetMultiCaseDispatch<Values>{});
1016USERVER_NAMESPACE_END