14#include <fmt/format.h>
16#include <userver/compiler/demangle.hpp>
17#include <userver/utils/assert.hpp>
18#include <userver/utils/string_literal.hpp>
20USERVER_NAMESPACE_BEGIN
26constexpr bool HasUppercaseAscii(std::string_view value)
noexcept {
27 for (
auto c : value) {
28 if (
'A' <= c && c <=
'Z')
return true;
34constexpr bool ICaseEqualLowercase(std::string_view lowercase, std::string_view y)
noexcept {
35 const auto size = lowercase.size();
37 constexpr char kLowerToUpperMask =
static_cast<
char>(~
unsigned{32});
38 for (std::size_t i = 0; i < size; ++i) {
39 const auto lowercase_c = lowercase[i];
40 UASSERT(!(
'A' <= lowercase_c && lowercase_c <=
'Z'));
41 if (lowercase_c != y[i]) {
42 if (!(
'a' <= lowercase_c && lowercase_c <=
'z') || (lowercase_c & kLowerToUpperMask) != y[i]) {
52 constexpr explicit Found(std::size_t value)
noexcept {
UASSERT(value == 0); }
54 constexpr explicit operator std::size_t()
const noexcept {
return 0; }
57template <
typename Key,
typename Value,
typename Enabled =
void>
58class SearchState
final {
60 constexpr explicit SearchState(Key key)
noexcept : key_or_result_(std::in_place_index<0>, key) {}
62 constexpr bool IsFound()
const noexcept {
return key_or_result_.index() != 0; }
64 constexpr Key GetKey()
const noexcept {
66 return std::get<0>(key_or_result_);
69 constexpr void SetValue(Value value)
noexcept {
70 key_or_result_ = std::variant<Key, Value>(std::in_place_index<1>, value);
73 [[nodiscard]]
constexpr std::optional<Value> Extract()
noexcept {
74 if (key_or_result_.index() == 1) {
75 return std::get<1>(key_or_result_);
82 std::variant<Key, Value> key_or_result_;
85inline constexpr std::size_t kInvalidSize = std::numeric_limits<std::size_t>::max();
87template <
typename Payload>
88inline constexpr bool kFitsInStringOrPayload =
89 sizeof(Payload) <=
sizeof(
const char*) &&
90 (std::is_integral_v<Payload> || std::is_enum_v<Payload> || std::is_same_v<Payload, Found>);
93template <
typename Payload>
94class StringOrPayload
final {
96 constexpr explicit StringOrPayload(std::string_view string)
noexcept
97 : data_or_payload_(string.data()), size_(string.size()) {
99 __builtin_assume(size_ != kInvalidSize);
100#elif defined(__GNUC__)
101 if (size_ == kInvalidSize) __builtin_unreachable();
105 constexpr explicit StringOrPayload(Payload payload)
noexcept : data_or_payload_(payload), size_(kInvalidSize) {}
107 constexpr bool HasPayload()
const noexcept {
return size_ == kInvalidSize; }
109 constexpr const char* GetStringPointer()
const noexcept {
111 UASSERT(data_or_payload_.data);
112 return data_or_payload_.data;
115 constexpr std::size_t GetStringSize()
const noexcept {
117 UASSERT(data_or_payload_.data);
121 constexpr Payload GetPayload()
const noexcept {
123 return data_or_payload_.payload;
127 static_assert(kFitsInStringOrPayload<Payload>);
129 union DataOrPayload {
130 constexpr explicit DataOrPayload(
const char* data)
noexcept : data(data) {}
132 constexpr explicit DataOrPayload(Payload payload)
noexcept : payload(payload) {}
138 DataOrPayload data_or_payload_;
142template <
typename Value>
143class SearchState<std::string_view, Value, std::enable_if_t<kFitsInStringOrPayload<Value>>> {
145 constexpr explicit SearchState(std::string_view key)
noexcept : state_(key) {}
147 constexpr bool IsFound()
const noexcept {
return state_.HasPayload(); }
149 constexpr std::string_view GetKey()
const noexcept {
150 return std::string_view{state_.GetStringPointer(), state_.GetStringSize()};
153 constexpr void SetValue(Value value)
noexcept { state_ = StringOrPayload<Value>{value}; }
155 [[nodiscard]]
constexpr std::optional<Value> Extract()
noexcept {
156 return IsFound() ? std::optional{state_.GetPayload()} : std::nullopt;
160 StringOrPayload<Value> state_;
163template <
typename Value>
164class SearchState<
zstring_view, Value, std::enable_if_t<kFitsInStringOrPayload<Value>>>
final
165 :
public SearchState<std::string_view, Value> {};
167template <
typename Value>
168class SearchState<
StringLiteral, Value, std::enable_if_t<kFitsInStringOrPayload<Value>>>
final
169 :
public SearchState<std::string_view, Value> {};
171template <
typename Key>
172class SearchState<Key, std::string_view, std::enable_if_t<kFitsInStringOrPayload<Key>>>
final {
174 constexpr explicit SearchState(Key key)
noexcept : state_(key) {}
176 constexpr bool IsFound()
const noexcept {
return !state_.HasPayload(); }
178 constexpr Key GetKey()
const noexcept {
return state_.GetPayload(); }
180 constexpr void SetValue(std::string_view value)
noexcept { state_ = StringOrPayload<Key>{value}; }
182 [[nodiscard]]
constexpr std::optional<std::string_view> Extract()
noexcept {
183 return IsFound() ? std::optional{std::string_view{state_.GetStringPointer(), state_.GetStringSize()}}
188 StringOrPayload<Key> state_;
191template <
typename Key>
192class SearchState<Key,
zstring_view, std::enable_if_t<kFitsInStringOrPayload<Key>>>
final {
194 constexpr explicit SearchState(Key key)
noexcept : state_(key) {}
196 constexpr bool IsFound()
const noexcept {
return !state_.HasPayload(); }
198 constexpr Key GetKey()
const noexcept {
return state_.GetPayload(); }
200 constexpr void SetValue(
zstring_view value)
noexcept { state_ = StringOrPayload<Key>{value}; }
202 [[nodiscard]]
constexpr std::optional<
zstring_view> Extract()
noexcept {
208 StringOrPayload<Key> state_;
211template <
typename Key>
212class SearchState<Key,
StringLiteral, std::enable_if_t<kFitsInStringOrPayload<Key>>>
final {
214 constexpr explicit SearchState(Key key)
noexcept : state_(key) {}
216 constexpr bool IsFound()
const noexcept {
return !state_.HasPayload(); }
218 constexpr Key GetKey()
const noexcept {
return state_.GetPayload(); }
220 constexpr void SetValue(
StringLiteral value)
noexcept { state_ = StringOrPayload<Key>{value}; }
222 [[nodiscard]]
constexpr std::optional<
StringLiteral> Extract()
noexcept {
228 StringOrPayload<Key> state_;
231template <
typename First,
typename Second>
232class SwitchByFirst
final {
234 constexpr explicit SwitchByFirst(First search)
noexcept : state_(search) {}
236 constexpr SwitchByFirst& Case(First first, Second second)
noexcept {
237 if (!state_.IsFound() && state_.GetKey() == first) {
238 state_.SetValue(second);
243 template <
typename T,
typename U =
void>
244 constexpr SwitchByFirst& Type() {
248 [[nodiscard]]
constexpr std::optional<Second> Extract()
noexcept {
return state_.Extract(); }
251 SearchState<First, Second> state_;
254template <
typename First>
255class SwitchByFirst<First,
void>
final {
257 constexpr explicit SwitchByFirst(First search)
noexcept : state_(search) {}
259 constexpr SwitchByFirst& Case(First first)
noexcept {
260 if (!state_.IsFound() && state_.GetKey() == first) {
261 state_.SetValue(Found{0});
266 template <
typename T,
typename U =
void>
267 constexpr SwitchByFirst& Type() {
271 [[nodiscard]]
constexpr bool Extract()
noexcept {
return state_.IsFound(); }
274 SearchState<First, Found> state_;
277template <
typename Second>
278class SwitchByFirstICase
final {
280 constexpr explicit SwitchByFirstICase(std::string_view search)
noexcept : state_(search) {}
282 constexpr SwitchByFirstICase& Case(std::string_view first, Second second)
noexcept {
284 !impl::HasUppercaseAscii(first),
285 fmt::format(
"String literal '{}' in utils::Switch*::Case() should be in lower case", first)
287 if (!state_.IsFound() && state_.GetKey().size() == first.size() &&
288 impl::ICaseEqualLowercase(first, state_.GetKey())) {
289 state_.SetValue(second);
294 template <
typename T,
typename U>
295 constexpr SwitchByFirstICase& Type() {
299 [[nodiscard]]
constexpr std::optional<Second> Extract()
noexcept {
return state_.Extract(); }
302 SearchState<std::string_view, Second> state_;
306class SwitchByFirstICase<
void>
final {
308 constexpr explicit SwitchByFirstICase(std::string_view search)
noexcept : state_(search) {}
310 constexpr SwitchByFirstICase& Case(std::string_view first)
noexcept {
312 !impl::HasUppercaseAscii(first),
313 fmt::format(
"String literal '{}' in utils::Switch*::Case() should be in lower case", first)
315 if (!state_.IsFound() && state_.GetKey().size() == first.size() &&
316 impl::ICaseEqualLowercase(first, state_.GetKey())) {
317 state_.SetValue(Found{0});
322 template <
typename T,
typename U>
323 constexpr SwitchByFirstICase& Type() {
327 [[nodiscard]]
constexpr bool Extract()
const noexcept {
return state_.IsFound(); }
330 SearchState<std::string_view, Found> state_;
333template <
typename First>
334class SwitchBySecondICase
final {
336 constexpr explicit SwitchBySecondICase(std::string_view search)
noexcept : state_(search) {}
338 constexpr SwitchBySecondICase& Case(First first, std::string_view second)
noexcept {
340 !impl::HasUppercaseAscii(second),
341 fmt::format(
"String literal '{}' in utils::Switch*::Case() should be in lower case", second)
343 if (!state_.IsFound() && state_.GetKey().size() == second.size() &&
344 impl::ICaseEqualLowercase(second, state_.GetKey())) {
345 state_.SetValue(first);
350 template <
typename T,
typename U>
351 constexpr SwitchBySecondICase& Type() {
355 [[nodiscard]]
constexpr std::optional<First> Extract()
noexcept {
return state_.Extract(); }
358 SearchState<std::string_view, First> state_;
361template <
typename First,
typename Second>
362class SwitchBySecond
final {
364 constexpr explicit SwitchBySecond(Second search)
noexcept : state_(search) {}
366 constexpr SwitchBySecond& Case(First first, Second second)
noexcept {
367 if (!state_.IsFound() && state_.GetKey() == second) {
368 state_.SetValue(first);
373 template <
typename T,
typename U>
374 constexpr SwitchBySecond& Type() {
378 [[nodiscard]]
constexpr std::optional<First> Extract()
noexcept {
return state_.Extract(); }
381 SearchState<Second, First> state_;
384template <
typename First,
typename Second>
385class SwitchTypesDetected
final {
387 using first_type = First;
388 using second_type = Second;
390 constexpr SwitchTypesDetected& Case(First, Second)
noexcept {
return *
this; }
393template <
typename First>
394class SwitchTypesDetected<First,
void>
final {
396 using first_type = First;
397 using second_type =
void;
399 constexpr SwitchTypesDetected& Case(First)
noexcept {
return *
this; }
402class SwitchTypesDetector
final {
405 using DetectType = std::conditional_t<
411 std::conditional_t<std::is_convertible_v<T, std::string_view>, std::string_view, T>>>;
413 constexpr SwitchTypesDetector& operator()()
noexcept {
return *
this; }
415 template <
typename First,
typename Second>
416 constexpr auto Case(First, Second)
noexcept {
417 return Type<First, Second>();
420 template <
typename First>
421 constexpr auto Case(First)
noexcept {
422 return Type<First,
void>();
425 template <
typename First,
typename Second =
void>
426 constexpr auto Type()
noexcept {
427 return SwitchTypesDetected<DetectType<First>, DetectType<Second>>{};
431class CaseCounter
final {
433 template <
typename First,
typename Second>
434 constexpr CaseCounter& Case(First, Second)
noexcept {
439 template <
typename First>
440 constexpr CaseCounter& Case(First)
noexcept {
445 template <
typename T,
typename U>
446 constexpr CaseCounter& Type() {
450 [[nodiscard]]
constexpr std::size_t Extract()
const noexcept {
return count_; }
453 std::size_t count_{0};
456class CaseDescriber
final {
458 template <
typename First,
typename Second>
459 CaseDescriber& Case(First first, Second second)
noexcept {
460 if (!description_.empty()) {
461 description_ +=
", ";
464 description_ += fmt::format(
"('{}', '{}')", first, second);
469 template <
typename T,
typename U>
470 constexpr CaseDescriber& Type() {
474 [[nodiscard]] std::string Extract() &&
noexcept {
return std::move(description_); }
477 std::string description_{};
480class CaseFirstDescriber
final {
482 template <
typename First>
483 CaseFirstDescriber& Case(First first)
noexcept {
484 if (!description_.empty()) {
485 description_ +=
", ";
488 description_ += fmt::format(
"'{}'", first);
493 template <
typename First,
typename Second>
494 CaseFirstDescriber& Case(First first, Second )
noexcept {
498 template <
typename T,
typename U =
void>
499 constexpr CaseFirstDescriber& Type() {
503 [[nodiscard]] std::string Extract() &&
noexcept {
return std::move(description_); }
506 std::string description_{};
509class CaseSecondDescriber
final {
511 template <
typename First,
typename Second>
512 CaseSecondDescriber& Case(First , Second second)
noexcept {
513 if (!description_.empty()) {
514 description_ +=
", ";
517 description_ += fmt::format(
"'{}'", second);
522 template <
typename T,
typename U>
523 constexpr CaseSecondDescriber& Type() {
527 [[nodiscard]] std::string Extract() &&
noexcept {
return std::move(description_); }
530 std::string description_{};
533template <
typename First,
typename Second>
534class CaseGetValuesByIndex
final {
536 explicit constexpr CaseGetValuesByIndex(std::size_t search_index) : index_(search_index + 1) {}
538 constexpr CaseGetValuesByIndex& Case(First first, Second second)
noexcept {
543 lazy_ = Lazy{Storage{first, second}};
550 template <
typename T,
typename U>
551 constexpr CaseGetValuesByIndex& Type() {
555 [[nodiscard]]
constexpr First GetFirst()
noexcept {
return std::move(lazy_.storage.first); }
557 [[nodiscard]]
constexpr Second GetSecond()
noexcept {
return std::move(lazy_.storage.second); }
569 constexpr Lazy()
noexcept : empty{} {}
570 constexpr Lazy(Storage s)
noexcept : storage{s} {}
578template <
typename First>
579class CaseFirstIndexer
final {
581 constexpr explicit CaseFirstIndexer(First search_value)
noexcept : state_(search_value) {}
583 constexpr CaseFirstIndexer& Case(First first)
noexcept {
584 if (!state_.IsFound() && state_.GetKey() == first) {
585 state_.SetValue(index_);
591 template <
typename T,
typename U =
void>
592 constexpr CaseFirstIndexer& Type() {
596 [[nodiscard]]
constexpr std::optional<std::size_t> Extract() &&
noexcept {
return state_.Extract(); }
599 SearchState<First, std::size_t> state_;
600 std::size_t index_ = 0;
603template <
typename First>
604class CaseFirstIndexerICase
final {
606 constexpr explicit CaseFirstIndexerICase(First search_value)
noexcept : state_(search_value) {}
608 constexpr CaseFirstIndexerICase& Case(First first)
noexcept {
609 if (!state_.IsFound() && state_.GetKey().size() == first.size() &&
610 impl::ICaseEqualLowercase(first, state_.GetKey())) {
611 state_.SetValue(index_);
617 template <
typename T,
typename U =
void>
618 constexpr CaseFirstIndexerICase& Type() {
622 [[nodiscard]]
constexpr std::optional<std::size_t> Extract() &&
noexcept {
return state_.Extract(); }
625 SearchState<First, std::size_t> state_;
626 std::size_t index_ = 0;
633using DecayToStringView =
663template <
typename BuilderFunc>
664class TrivialBiMap
final {
665 using TypesPair = std::invoke_result_t<
const BuilderFunc&, impl::SwitchTypesDetector>;
668 using First =
typename TypesPair::first_type;
669 using Second =
typename TypesPair::second_type;
679 using MappedTypeFor = std::conditional_t<std::is_convertible_v<T, DecayToStringView<First>>, Second, First>;
681 constexpr TrivialBiMap(BuilderFunc&& func)
noexcept : func_(std::move(func)) {
682 static_assert(std::is_empty_v<BuilderFunc>,
"Mapping function should not capture variables");
683 static_assert(std::is_trivially_copyable_v<First>,
"First type in Case must be trivially copyable");
685 !std::is_void_v<Second>,
686 "If second type in Case is missing, use utils::TrivialSet instead of utils::TrivialBiMap"
688 static_assert(std::is_trivially_copyable_v<Second>,
"Second type in Case must be trivially copyable");
691 constexpr std::optional<Second> TryFindByFirst(DecayToStringView<First> value)
const noexcept {
692 return func_([value]() {
return impl::SwitchByFirst<DecayToStringView<First>, Second>{value}; }).Extract();
695 constexpr std::optional<First> TryFindBySecond(DecayToStringView<Second> value)
const noexcept {
696 return func_([value]() {
return impl::SwitchBySecond<First, DecayToStringView<Second>>{value}; }).Extract();
700 constexpr std::optional<MappedTypeFor<T>> TryFind(T value)
const noexcept {
701 if constexpr (std::is_convertible_v<T, DecayToStringView<First>>) {
703 !std::is_convertible_v<T, DecayToStringView<Second>>,
704 "Ambiguous conversion, use TryFindByFirst/TryFindBySecond instead"
706 return TryFindByFirst(value);
708 return TryFindBySecond(value);
717 return func_([value]() {
return impl::SwitchByFirstICase<Second>{value}; }).Extract();
725 return func_([value]() {
return impl::SwitchBySecondICase<First>{value}; }).Extract();
730 constexpr std::optional<MappedTypeFor<std::string_view>>
TryFindICase(std::string_view value)
const noexcept {
731 if constexpr (std::is_convertible_v<std::string_view, DecayToStringView<First>>) {
733 !std::is_convertible_v<std::string_view, DecayToStringView<Second>>,
734 "Ambiguous conversion, use TryFindICaseByFirst/TryFindICaseBySecond"
743 constexpr std::size_t
size()
const noexcept {
744 return func_([]() {
return impl::CaseCounter{}; }).Extract();
753 return func_([]() {
return impl::CaseDescriber{}; }).Extract();
763 return func_([]() {
return impl::CaseFirstDescriber{}; }).Extract();
773 return func_([]() {
return impl::CaseSecondDescriber{}; }).Extract();
782 template <
typename T>
784 if constexpr (std::is_convertible_v<T, DecayToStringView<First>>) {
791 constexpr ValueType GetValuesByIndex(std::size_t index)
const {
793 auto result = func_([index]() {
return impl::CaseGetValuesByIndex<First, Second>{index}; });
794 return ValueType{result.GetFirst(), result.GetSecond()};
799 using iterator_category = std::input_iterator_tag;
800 using difference_type = std::ptrdiff_t;
802 explicit constexpr Iterator(
const TrivialBiMap& map, std::size_t position) : map_{map}, position_{position} {}
804 constexpr bool operator==(
Iterator other)
const {
return position_ == other.position_; }
806 constexpr bool operator!=(
Iterator other)
const {
return position_ != other.position_; }
813 constexpr Iterator operator++(
int) {
819 constexpr ValueType operator*()
const {
return map_.GetValuesByIndex(position_); }
822 const TrivialBiMap& map_;
823 std::size_t position_;
828 constexpr Iterator cbegin()
const {
return begin(); }
829 constexpr Iterator cend()
const {
return end(); }
832 const BuilderFunc func_;
835template <
typename BuilderFunc>
836TrivialBiMap(BuilderFunc) -> TrivialBiMap<BuilderFunc>;
844template <
typename BuilderFunc>
845class TrivialSet
final {
846 using TypesPair = std::invoke_result_t<
const BuilderFunc&, impl::SwitchTypesDetector>;
849 using First =
typename TypesPair::first_type;
850 using Second =
typename TypesPair::second_type;
852 constexpr TrivialSet(BuilderFunc&& func)
noexcept : func_(std::move(func)) {
853 static_assert(std::is_empty_v<BuilderFunc>,
"Mapping function should not capture variables");
854 static_assert(std::is_trivially_copyable_v<First>,
"First type in Case must be trivially copyable");
855 static_assert(std::is_void_v<Second>,
"Second type in Case should be skipped in utils::TrivialSet");
858 constexpr bool Contains(DecayToStringView<First> value)
const noexcept {
859 return func_([value]() {
return impl::SwitchByFirst<DecayToStringView<First>, Second>{value}; }).Extract();
862 constexpr bool ContainsICase(std::string_view value)
const noexcept {
864 std::is_convertible_v<DecayToStringView<First>, std::string_view>,
865 "ContainsICase works only with std::string_view"
868 return func_([value]() {
return impl::SwitchByFirstICase<
void>{value}; }).Extract();
871 constexpr std::size_t size()
const noexcept {
872 return func_([]() {
return impl::CaseCounter{}; }).Extract();
881 return func_([]() {
return impl::CaseFirstDescriber{}; }).Extract();
886 constexpr std::optional<std::size_t>
GetIndex(DecayToStringView<First> value)
const {
887 return func_([value]() {
return impl::CaseFirstIndexer{value}; }).Extract();
892 constexpr std::optional<std::size_t>
GetIndexICase(std::string_view value)
const {
893 return func_([value]() {
return impl::CaseFirstIndexerICase{value}; }).Extract();
897 const BuilderFunc func_;
900template <
typename BuilderFunc>
901TrivialSet(BuilderFunc) -> TrivialSet<BuilderFunc>;
908template <
typename ExceptionType =
void,
typename Value,
typename BuilderFunc>
910 if constexpr (!std::is_void_v<ExceptionType>) {
911 if (!value.IsString()) {
912 throw ExceptionType(fmt::format(
"Invalid value at '{}': expected a string", value.GetPath()));
916 const auto string = value.
template As<std::string>();
917 const auto parsed = map.TryFind(string);
918 if (parsed)
return *parsed;
920 using Exception = std::conditional_t<std::is_void_v<ExceptionType>,
typename Value::Exception, ExceptionType>;
922 throw Exception(fmt::format(
923 "Invalid value of {} at '{}': '{}' is not one of {}",
924 compiler::GetTypeName<std::decay_t<
decltype(*parsed)>>(),
927 map.
template DescribeByType<std::string_view>()
936template <
typename Enum,
typename BuilderFunc>
937std::string_view EnumToStringView(Enum value, TrivialBiMap<BuilderFunc> map) {
938 static_assert(std::is_enum_v<Enum>);
939 if (
const auto string = map.TryFind(value))
return *string;
944 "Invalid value of enum {}: {}",
946 static_cast<std::underlying_type_t<Enum>>(value)
951template <
typename Selector,
class Keys,
typename Values, std::size_t... Indices>
953TrivialBiMapMultiCase(Selector selector,
const Keys& keys,
const Values& values, std::index_sequence<0, Indices...>) {
954 auto selector2 = selector.Case(std::data(keys)[0], std::data(values)[0]);
955 ((selector2 = selector2.Case(std::data(keys)[Indices], std::data(values)[Indices])), ...);
959template <
const auto& Keys,
const auto& Values>
960struct TrivialBiMapMultiCaseDispatch {
961 template <
class Selector>
962 constexpr auto operator()(Selector selector)
const {
963 constexpr auto kKeysSize = std::size(Keys);
964 return impl::TrivialBiMapMultiCase(selector(), Keys, Values, std::make_index_sequence<kKeysSize>{});
968template <
typename Selector,
class Values, std::size_t... Indices>
969constexpr auto TrivialSetMultiCase(Selector selector,
const Values& values, std::index_sequence<0, Indices...>) {
970 auto selector2 = selector.Case(std::data(values)[0]);
971 ((selector2 = selector2.Case(std::data(values)[Indices])), ...);
975template <
const auto& Values>
976struct TrivialSetMultiCaseDispatch {
977 template <
class Selector>
978 constexpr auto operator()(Selector selector)
const {
979 constexpr auto kValuesSize = std::size(Values);
980 return impl::TrivialSetMultiCase(selector(), Values, std::make_index_sequence<kValuesSize>{});
987template <
const auto& Keys,
const auto& Values>
989 static_assert(std::size(Keys) == std::size(Values));
990 static_assert(std::size(Keys) >= 1);
991 return TrivialBiMap(impl::TrivialBiMapMultiCaseDispatch<Keys, Values>{});
994template <
const auto& Values>
995constexpr auto MakeTrivialSet() {
996 return TrivialSet(impl::TrivialSetMultiCaseDispatch<Values>{});
1001USERVER_NAMESPACE_END