File indexing completed on 2025-01-18 09:57:24
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012 #ifndef FMT_RANGES_H_
0013 #define FMT_RANGES_H_
0014
0015 #include <initializer_list>
0016 #include <tuple>
0017 #include <type_traits>
0018
0019 #include "format.h"
0020
0021 FMT_BEGIN_NAMESPACE
0022
0023 namespace detail {
0024
0025 template <typename RangeT, typename OutputIterator>
0026 OutputIterator copy(const RangeT& range, OutputIterator out) {
0027 for (auto it = range.begin(), end = range.end(); it != end; ++it)
0028 *out++ = *it;
0029 return out;
0030 }
0031
0032 template <typename OutputIterator>
0033 OutputIterator copy(const char* str, OutputIterator out) {
0034 while (*str) *out++ = *str++;
0035 return out;
0036 }
0037
0038 template <typename OutputIterator>
0039 OutputIterator copy(char ch, OutputIterator out) {
0040 *out++ = ch;
0041 return out;
0042 }
0043
0044 template <typename OutputIterator>
0045 OutputIterator copy(wchar_t ch, OutputIterator out) {
0046 *out++ = ch;
0047 return out;
0048 }
0049
0050
0051 template <typename T> class is_std_string_like {
0052 template <typename U>
0053 static auto check(U* p)
0054 -> decltype((void)p->find('a'), p->length(), (void)p->data(), int());
0055 template <typename> static void check(...);
0056
0057 public:
0058 static constexpr const bool value =
0059 is_string<T>::value ||
0060 std::is_convertible<T, std_string_view<char>>::value ||
0061 !std::is_void<decltype(check<T>(nullptr))>::value;
0062 };
0063
0064 template <typename Char>
0065 struct is_std_string_like<fmt::basic_string_view<Char>> : std::true_type {};
0066
0067 template <typename T> class is_map {
0068 template <typename U> static auto check(U*) -> typename U::mapped_type;
0069 template <typename> static void check(...);
0070
0071 public:
0072 #ifdef FMT_FORMAT_MAP_AS_LIST
0073 static constexpr const bool value = false;
0074 #else
0075 static constexpr const bool value =
0076 !std::is_void<decltype(check<T>(nullptr))>::value;
0077 #endif
0078 };
0079
0080 template <typename T> class is_set {
0081 template <typename U> static auto check(U*) -> typename U::key_type;
0082 template <typename> static void check(...);
0083
0084 public:
0085 #ifdef FMT_FORMAT_SET_AS_LIST
0086 static constexpr const bool value = false;
0087 #else
0088 static constexpr const bool value =
0089 !std::is_void<decltype(check<T>(nullptr))>::value && !is_map<T>::value;
0090 #endif
0091 };
0092
0093 template <typename... Ts> struct conditional_helper {};
0094
0095 template <typename T, typename _ = void> struct is_range_ : std::false_type {};
0096
0097 #if !FMT_MSC_VERSION || FMT_MSC_VERSION > 1800
0098
0099 # define FMT_DECLTYPE_RETURN(val) \
0100 ->decltype(val) { return val; } \
0101 static_assert( \
0102 true, "")
0103
0104
0105
0106 template <typename T, std::size_t N>
0107 auto range_begin(const T (&arr)[N]) -> const T* {
0108 return arr;
0109 }
0110 template <typename T, std::size_t N>
0111 auto range_end(const T (&arr)[N]) -> const T* {
0112 return arr + N;
0113 }
0114
0115 template <typename T, typename Enable = void>
0116 struct has_member_fn_begin_end_t : std::false_type {};
0117
0118 template <typename T>
0119 struct has_member_fn_begin_end_t<T, void_t<decltype(std::declval<T>().begin()),
0120 decltype(std::declval<T>().end())>>
0121 : std::true_type {};
0122
0123
0124 template <typename T>
0125 auto range_begin(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).begin());
0126 template <typename T>
0127 auto range_end(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).end());
0128
0129
0130
0131 template <typename T>
0132 auto range_begin(T&& rng)
0133 -> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
0134 decltype(begin(static_cast<T&&>(rng)))> {
0135 return begin(static_cast<T&&>(rng));
0136 }
0137 template <typename T>
0138 auto range_end(T&& rng) -> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
0139 decltype(end(static_cast<T&&>(rng)))> {
0140 return end(static_cast<T&&>(rng));
0141 }
0142
0143 template <typename T, typename Enable = void>
0144 struct has_const_begin_end : std::false_type {};
0145 template <typename T, typename Enable = void>
0146 struct has_mutable_begin_end : std::false_type {};
0147
0148 template <typename T>
0149 struct has_const_begin_end<
0150 T,
0151 void_t<
0152 decltype(detail::range_begin(std::declval<const remove_cvref_t<T>&>())),
0153 decltype(detail::range_end(std::declval<const remove_cvref_t<T>&>()))>>
0154 : std::true_type {};
0155
0156 template <typename T>
0157 struct has_mutable_begin_end<
0158 T, void_t<decltype(detail::range_begin(std::declval<T>())),
0159 decltype(detail::range_end(std::declval<T>())),
0160 enable_if_t<std::is_copy_constructible<T>::value>>>
0161 : std::true_type {};
0162
0163 template <typename T>
0164 struct is_range_<T, void>
0165 : std::integral_constant<bool, (has_const_begin_end<T>::value ||
0166 has_mutable_begin_end<T>::value)> {};
0167 # undef FMT_DECLTYPE_RETURN
0168 #endif
0169
0170
0171 template <typename T> class is_tuple_like_ {
0172 template <typename U>
0173 static auto check(U* p) -> decltype(std::tuple_size<U>::value, int());
0174 template <typename> static void check(...);
0175
0176 public:
0177 static constexpr const bool value =
0178 !std::is_void<decltype(check<T>(nullptr))>::value;
0179 };
0180
0181
0182 #if defined(__cpp_lib_integer_sequence) || FMT_MSC_VERSION >= 1900
0183 template <typename T, T... N>
0184 using integer_sequence = std::integer_sequence<T, N...>;
0185 template <size_t... N> using index_sequence = std::index_sequence<N...>;
0186 template <size_t N> using make_index_sequence = std::make_index_sequence<N>;
0187 #else
0188 template <typename T, T... N> struct integer_sequence {
0189 using value_type = T;
0190
0191 static FMT_CONSTEXPR size_t size() { return sizeof...(N); }
0192 };
0193
0194 template <size_t... N> using index_sequence = integer_sequence<size_t, N...>;
0195
0196 template <typename T, size_t N, T... Ns>
0197 struct make_integer_sequence : make_integer_sequence<T, N - 1, N - 1, Ns...> {};
0198 template <typename T, T... Ns>
0199 struct make_integer_sequence<T, 0, Ns...> : integer_sequence<T, Ns...> {};
0200
0201 template <size_t N>
0202 using make_index_sequence = make_integer_sequence<size_t, N>;
0203 #endif
0204
0205 template <typename T>
0206 using tuple_index_sequence = make_index_sequence<std::tuple_size<T>::value>;
0207
0208 template <typename T, typename C, bool = is_tuple_like_<T>::value>
0209 class is_tuple_formattable_ {
0210 public:
0211 static constexpr const bool value = false;
0212 };
0213 template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
0214 template <std::size_t... I>
0215 static std::true_type check2(index_sequence<I...>,
0216 integer_sequence<bool, (I == I)...>);
0217 static std::false_type check2(...);
0218 template <std::size_t... I>
0219 static decltype(check2(
0220 index_sequence<I...>{},
0221 integer_sequence<
0222 bool, (is_formattable<typename std::tuple_element<I, T>::type,
0223 C>::value)...>{})) check(index_sequence<I...>);
0224
0225 public:
0226 static constexpr const bool value =
0227 decltype(check(tuple_index_sequence<T>{}))::value;
0228 };
0229
0230 template <class Tuple, class F, size_t... Is>
0231 void for_each(index_sequence<Is...>, Tuple&& tup, F&& f) noexcept {
0232 using std::get;
0233
0234 const int _[] = {0, ((void)f(get<Is>(tup)), 0)...};
0235 (void)_;
0236 }
0237
0238 template <class T>
0239 FMT_CONSTEXPR make_index_sequence<std::tuple_size<T>::value> get_indexes(
0240 T const&) {
0241 return {};
0242 }
0243
0244 template <class Tuple, class F> void for_each(Tuple&& tup, F&& f) {
0245 const auto indexes = get_indexes(tup);
0246 for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f));
0247 }
0248
0249 #if FMT_MSC_VERSION && FMT_MSC_VERSION < 1920
0250
0251 template <typename R> struct range_reference_type_impl {
0252 using type = decltype(*detail::range_begin(std::declval<R&>()));
0253 };
0254
0255 template <typename T, std::size_t N> struct range_reference_type_impl<T[N]> {
0256 using type = T&;
0257 };
0258
0259 template <typename T>
0260 using range_reference_type = typename range_reference_type_impl<T>::type;
0261 #else
0262 template <typename Range>
0263 using range_reference_type =
0264 decltype(*detail::range_begin(std::declval<Range&>()));
0265 #endif
0266
0267
0268
0269 template <typename Range>
0270 using uncvref_type = remove_cvref_t<range_reference_type<Range>>;
0271
0272 template <typename Range>
0273 using uncvref_first_type =
0274 remove_cvref_t<decltype(std::declval<range_reference_type<Range>>().first)>;
0275
0276 template <typename Range>
0277 using uncvref_second_type = remove_cvref_t<
0278 decltype(std::declval<range_reference_type<Range>>().second)>;
0279
0280 template <typename OutputIt> OutputIt write_delimiter(OutputIt out) {
0281 *out++ = ',';
0282 *out++ = ' ';
0283 return out;
0284 }
0285
0286 template <typename Char, typename OutputIt>
0287 auto write_range_entry(OutputIt out, basic_string_view<Char> str) -> OutputIt {
0288 return write_escaped_string(out, str);
0289 }
0290
0291 template <typename Char, typename OutputIt, typename T,
0292 FMT_ENABLE_IF(std::is_convertible<T, std_string_view<char>>::value)>
0293 inline auto write_range_entry(OutputIt out, const T& str) -> OutputIt {
0294 auto sv = std_string_view<Char>(str);
0295 return write_range_entry<Char>(out, basic_string_view<Char>(sv));
0296 }
0297
0298 template <typename Char, typename OutputIt, typename Arg,
0299 FMT_ENABLE_IF(std::is_same<Arg, Char>::value)>
0300 OutputIt write_range_entry(OutputIt out, const Arg v) {
0301 return write_escaped_char(out, v);
0302 }
0303
0304 template <
0305 typename Char, typename OutputIt, typename Arg,
0306 FMT_ENABLE_IF(!is_std_string_like<typename std::decay<Arg>::type>::value &&
0307 !std::is_same<Arg, Char>::value)>
0308 OutputIt write_range_entry(OutputIt out, const Arg& v) {
0309 return write<Char>(out, v);
0310 }
0311
0312 }
0313
0314 template <typename T> struct is_tuple_like {
0315 static constexpr const bool value =
0316 detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value;
0317 };
0318
0319 template <typename T, typename C> struct is_tuple_formattable {
0320 static constexpr const bool value =
0321 detail::is_tuple_formattable_<T, C>::value;
0322 };
0323
0324 template <typename TupleT, typename Char>
0325 struct formatter<TupleT, Char,
0326 enable_if_t<fmt::is_tuple_like<TupleT>::value &&
0327 fmt::is_tuple_formattable<TupleT, Char>::value>> {
0328 private:
0329 basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{};
0330 basic_string_view<Char> opening_bracket_ =
0331 detail::string_literal<Char, '('>{};
0332 basic_string_view<Char> closing_bracket_ =
0333 detail::string_literal<Char, ')'>{};
0334
0335
0336 template <typename FormatContext> struct format_each {
0337 template <typename T> void operator()(const T& v) {
0338 if (i > 0) out = detail::copy_str<Char>(separator, out);
0339 out = detail::write_range_entry<Char>(out, v);
0340 ++i;
0341 }
0342 int i;
0343 typename FormatContext::iterator& out;
0344 basic_string_view<Char> separator;
0345 };
0346
0347 public:
0348 FMT_CONSTEXPR formatter() {}
0349
0350 FMT_CONSTEXPR void set_separator(basic_string_view<Char> sep) {
0351 separator_ = sep;
0352 }
0353
0354 FMT_CONSTEXPR void set_brackets(basic_string_view<Char> open,
0355 basic_string_view<Char> close) {
0356 opening_bracket_ = open;
0357 closing_bracket_ = close;
0358 }
0359
0360 template <typename ParseContext>
0361 FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
0362 return ctx.begin();
0363 }
0364
0365 template <typename FormatContext = format_context>
0366 auto format(const TupleT& values, FormatContext& ctx) const
0367 -> decltype(ctx.out()) {
0368 auto out = ctx.out();
0369 out = detail::copy_str<Char>(opening_bracket_, out);
0370 detail::for_each(values, format_each<FormatContext>{0, out, separator_});
0371 out = detail::copy_str<Char>(closing_bracket_, out);
0372 return out;
0373 }
0374 };
0375
0376 template <typename T, typename Char> struct is_range {
0377 static constexpr const bool value =
0378 detail::is_range_<T>::value && !detail::is_std_string_like<T>::value &&
0379 !std::is_convertible<T, std::basic_string<Char>>::value &&
0380 !std::is_convertible<T, detail::std_string_view<Char>>::value;
0381 };
0382
0383 namespace detail {
0384 template <typename Context> struct range_mapper {
0385 using mapper = arg_mapper<Context>;
0386
0387 template <typename T,
0388 FMT_ENABLE_IF(has_formatter<remove_cvref_t<T>, Context>::value)>
0389 static auto map(T&& value) -> T&& {
0390 return static_cast<T&&>(value);
0391 }
0392 template <typename T,
0393 FMT_ENABLE_IF(!has_formatter<remove_cvref_t<T>, Context>::value)>
0394 static auto map(T&& value)
0395 -> decltype(mapper().map(static_cast<T&&>(value))) {
0396 return mapper().map(static_cast<T&&>(value));
0397 }
0398 };
0399
0400 template <typename Char, typename Element>
0401 using range_formatter_type = conditional_t<
0402 is_formattable<Element, Char>::value,
0403 formatter<remove_cvref_t<decltype(range_mapper<buffer_context<Char>>{}.map(
0404 std::declval<Element>()))>,
0405 Char>,
0406 fallback_formatter<Element, Char>>;
0407
0408 template <typename R>
0409 using maybe_const_range =
0410 conditional_t<has_const_begin_end<R>::value, const R, R>;
0411
0412
0413 #if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
0414 template <typename R, typename Char>
0415 struct is_formattable_delayed
0416 : disjunction<
0417 is_formattable<uncvref_type<maybe_const_range<R>>, Char>,
0418 has_fallback_formatter<uncvref_type<maybe_const_range<R>>, Char>> {};
0419 #endif
0420
0421 }
0422
0423 template <typename T, typename Char, typename Enable = void>
0424 struct range_formatter;
0425
0426 template <typename T, typename Char>
0427 struct range_formatter<
0428 T, Char,
0429 enable_if_t<conjunction<
0430 std::is_same<T, remove_cvref_t<T>>,
0431 disjunction<is_formattable<T, Char>,
0432 detail::has_fallback_formatter<T, Char>>>::value>> {
0433 private:
0434 detail::range_formatter_type<Char, T> underlying_;
0435 bool custom_specs_ = false;
0436 basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{};
0437 basic_string_view<Char> opening_bracket_ =
0438 detail::string_literal<Char, '['>{};
0439 basic_string_view<Char> closing_bracket_ =
0440 detail::string_literal<Char, ']'>{};
0441
0442 template <class U>
0443 FMT_CONSTEXPR static auto maybe_set_debug_format(U& u, int)
0444 -> decltype(u.set_debug_format()) {
0445 u.set_debug_format();
0446 }
0447
0448 template <class U>
0449 FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) {}
0450
0451 FMT_CONSTEXPR void maybe_set_debug_format() {
0452 maybe_set_debug_format(underlying_, 0);
0453 }
0454
0455 public:
0456 FMT_CONSTEXPR range_formatter() {}
0457
0458 FMT_CONSTEXPR auto underlying() -> detail::range_formatter_type<Char, T>& {
0459 return underlying_;
0460 }
0461
0462 FMT_CONSTEXPR void set_separator(basic_string_view<Char> sep) {
0463 separator_ = sep;
0464 }
0465
0466 FMT_CONSTEXPR void set_brackets(basic_string_view<Char> open,
0467 basic_string_view<Char> close) {
0468 opening_bracket_ = open;
0469 closing_bracket_ = close;
0470 }
0471
0472 template <typename ParseContext>
0473 FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
0474 auto it = ctx.begin();
0475 auto end = ctx.end();
0476 if (it == end || *it == '}') {
0477 maybe_set_debug_format();
0478 return it;
0479 }
0480
0481 if (*it == 'n') {
0482 set_brackets({}, {});
0483 ++it;
0484 }
0485
0486 if (*it == '}') {
0487 maybe_set_debug_format();
0488 return it;
0489 }
0490
0491 if (*it != ':')
0492 FMT_THROW(format_error("no other top-level range formatters supported"));
0493
0494 custom_specs_ = true;
0495 ++it;
0496 ctx.advance_to(it);
0497 return underlying_.parse(ctx);
0498 }
0499
0500 template <typename R, class FormatContext>
0501 auto format(R&& range, FormatContext& ctx) const -> decltype(ctx.out()) {
0502 detail::range_mapper<buffer_context<Char>> mapper;
0503 auto out = ctx.out();
0504 out = detail::copy_str<Char>(opening_bracket_, out);
0505 int i = 0;
0506 auto it = detail::range_begin(range);
0507 auto end = detail::range_end(range);
0508 for (; it != end; ++it) {
0509 if (i > 0) out = detail::copy_str<Char>(separator_, out);
0510 ;
0511 ctx.advance_to(out);
0512 out = underlying_.format(mapper.map(*it), ctx);
0513 ++i;
0514 }
0515 out = detail::copy_str<Char>(closing_bracket_, out);
0516 return out;
0517 }
0518 };
0519
0520 enum class range_format { disabled, map, set, sequence, string, debug_string };
0521
0522 namespace detail {
0523 template <typename T> struct range_format_kind_ {
0524 static constexpr auto value = std::is_same<range_reference_type<T>, T>::value
0525 ? range_format::disabled
0526 : is_map<T>::value ? range_format::map
0527 : is_set<T>::value ? range_format::set
0528 : range_format::sequence;
0529 };
0530
0531 template <range_format K, typename R, typename Char, typename Enable = void>
0532 struct range_default_formatter;
0533
0534 template <range_format K>
0535 using range_format_constant = std::integral_constant<range_format, K>;
0536
0537 template <range_format K, typename R, typename Char>
0538 struct range_default_formatter<
0539 K, R, Char,
0540 enable_if_t<(K == range_format::sequence || K == range_format::map ||
0541 K == range_format::set)>> {
0542 using range_type = detail::maybe_const_range<R>;
0543 range_formatter<detail::uncvref_type<range_type>, Char> underlying_;
0544
0545 FMT_CONSTEXPR range_default_formatter() { init(range_format_constant<K>()); }
0546
0547 FMT_CONSTEXPR void init(range_format_constant<range_format::set>) {
0548 underlying_.set_brackets(detail::string_literal<Char, '{'>{},
0549 detail::string_literal<Char, '}'>{});
0550 }
0551
0552 FMT_CONSTEXPR void init(range_format_constant<range_format::map>) {
0553 underlying_.set_brackets(detail::string_literal<Char, '{'>{},
0554 detail::string_literal<Char, '}'>{});
0555 underlying_.underlying().set_brackets({}, {});
0556 underlying_.underlying().set_separator(
0557 detail::string_literal<Char, ':', ' '>{});
0558 }
0559
0560 FMT_CONSTEXPR void init(range_format_constant<range_format::sequence>) {}
0561
0562 template <typename ParseContext>
0563 FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
0564 return underlying_.parse(ctx);
0565 }
0566
0567 template <typename FormatContext>
0568 auto format(range_type& range, FormatContext& ctx) const
0569 -> decltype(ctx.out()) {
0570 return underlying_.format(range, ctx);
0571 }
0572 };
0573 }
0574
0575 template <typename T, typename Char, typename Enable = void>
0576 struct range_format_kind
0577 : conditional_t<
0578 is_range<T, Char>::value, detail::range_format_kind_<T>,
0579 std::integral_constant<range_format, range_format::disabled>> {};
0580
0581 template <typename R, typename Char>
0582 struct formatter<
0583 R, Char,
0584 enable_if_t<conjunction<bool_constant<range_format_kind<R, Char>::value !=
0585 range_format::disabled>
0586
0587 #if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
0588 ,
0589 detail::is_formattable_delayed<R, Char>
0590 #endif
0591 >::value>>
0592 : detail::range_default_formatter<range_format_kind<R, Char>::value, R,
0593 Char> {
0594 };
0595
0596 template <typename Char, typename... T> struct tuple_join_view : detail::view {
0597 const std::tuple<T...>& tuple;
0598 basic_string_view<Char> sep;
0599
0600 tuple_join_view(const std::tuple<T...>& t, basic_string_view<Char> s)
0601 : tuple(t), sep{s} {}
0602 };
0603
0604 template <typename Char, typename... T>
0605 using tuple_arg_join = tuple_join_view<Char, T...>;
0606
0607
0608
0609
0610 #ifndef FMT_TUPLE_JOIN_SPECIFIERS
0611 # define FMT_TUPLE_JOIN_SPECIFIERS 0
0612 #endif
0613
0614 template <typename Char, typename... T>
0615 struct formatter<tuple_join_view<Char, T...>, Char> {
0616 template <typename ParseContext>
0617 FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
0618 return do_parse(ctx, std::integral_constant<size_t, sizeof...(T)>());
0619 }
0620
0621 template <typename FormatContext>
0622 auto format(const tuple_join_view<Char, T...>& value,
0623 FormatContext& ctx) const -> typename FormatContext::iterator {
0624 return do_format(value, ctx,
0625 std::integral_constant<size_t, sizeof...(T)>());
0626 }
0627
0628 private:
0629 std::tuple<formatter<typename std::decay<T>::type, Char>...> formatters_;
0630
0631 template <typename ParseContext>
0632 FMT_CONSTEXPR auto do_parse(ParseContext& ctx,
0633 std::integral_constant<size_t, 0>)
0634 -> decltype(ctx.begin()) {
0635 return ctx.begin();
0636 }
0637
0638 template <typename ParseContext, size_t N>
0639 FMT_CONSTEXPR auto do_parse(ParseContext& ctx,
0640 std::integral_constant<size_t, N>)
0641 -> decltype(ctx.begin()) {
0642 auto end = ctx.begin();
0643 #if FMT_TUPLE_JOIN_SPECIFIERS
0644 end = std::get<sizeof...(T) - N>(formatters_).parse(ctx);
0645 if (N > 1) {
0646 auto end1 = do_parse(ctx, std::integral_constant<size_t, N - 1>());
0647 if (end != end1)
0648 FMT_THROW(format_error("incompatible format specs for tuple elements"));
0649 }
0650 #endif
0651 return end;
0652 }
0653
0654 template <typename FormatContext>
0655 auto do_format(const tuple_join_view<Char, T...>&, FormatContext& ctx,
0656 std::integral_constant<size_t, 0>) const ->
0657 typename FormatContext::iterator {
0658 return ctx.out();
0659 }
0660
0661 template <typename FormatContext, size_t N>
0662 auto do_format(const tuple_join_view<Char, T...>& value, FormatContext& ctx,
0663 std::integral_constant<size_t, N>) const ->
0664 typename FormatContext::iterator {
0665 auto out = std::get<sizeof...(T) - N>(formatters_)
0666 .format(std::get<sizeof...(T) - N>(value.tuple), ctx);
0667 if (N > 1) {
0668 out = std::copy(value.sep.begin(), value.sep.end(), out);
0669 ctx.advance_to(out);
0670 return do_format(value, ctx, std::integral_constant<size_t, N - 1>());
0671 }
0672 return out;
0673 }
0674 };
0675
0676 FMT_MODULE_EXPORT_BEGIN
0677
0678
0679
0680
0681
0682
0683
0684
0685
0686
0687
0688
0689 template <typename... T>
0690 FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple, string_view sep)
0691 -> tuple_join_view<char, T...> {
0692 return {tuple, sep};
0693 }
0694
0695 template <typename... T>
0696 FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple,
0697 basic_string_view<wchar_t> sep)
0698 -> tuple_join_view<wchar_t, T...> {
0699 return {tuple, sep};
0700 }
0701
0702
0703
0704
0705
0706
0707
0708
0709
0710
0711
0712
0713 template <typename T>
0714 auto join(std::initializer_list<T> list, string_view sep)
0715 -> join_view<const T*, const T*> {
0716 return join(std::begin(list), std::end(list), sep);
0717 }
0718
0719 FMT_MODULE_EXPORT_END
0720 FMT_END_NAMESPACE
0721
0722 #endif