1 // Formatting library for C++ - range and tuple support 2 // 3 // Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors 4 // All rights reserved. 5 // 6 // For the license information refer to format.h. 7 8 #ifndef FMT_RANGES_H_ 9 #define FMT_RANGES_H_ 10 11 #include <initializer_list> 12 #include <tuple> 13 #include <type_traits> 14 15 #include "format.h" 16 17 FMT_BEGIN_NAMESPACE 18 19 namespace detail { 20 21 template <typename Range, typename OutputIt> 22 auto copy(const Range& range, OutputIt out) -> OutputIt { 23 for (auto it = range.begin(), end = range.end(); it != end; ++it) 24 *out++ = *it; 25 return out; 26 } 27 28 template <typename OutputIt> 29 auto copy(const char* str, OutputIt out) -> OutputIt { 30 while (*str) *out++ = *str++; 31 return out; 32 } 33 34 template <typename OutputIt> auto copy(char ch, OutputIt out) -> OutputIt { 35 *out++ = ch; 36 return out; 37 } 38 39 template <typename OutputIt> auto copy(wchar_t ch, OutputIt out) -> OutputIt { 40 *out++ = ch; 41 return out; 42 } 43 44 // Returns true if T has a std::string-like interface, like std::string_view. 45 template <typename T> class is_std_string_like { 46 template <typename U> 47 static auto check(U* p) 48 -> decltype((void)p->find('a'), p->length(), (void)p->data(), int()); 49 template <typename> static void check(...); 50 51 public: 52 static constexpr const bool value = 53 is_string<T>::value || 54 std::is_convertible<T, std_string_view<char>>::value || 55 !std::is_void<decltype(check<T>(nullptr))>::value; 56 }; 57 58 template <typename Char> 59 struct is_std_string_like<fmt::basic_string_view<Char>> : std::true_type {}; 60 61 template <typename T> class is_map { 62 template <typename U> static auto check(U*) -> typename U::mapped_type; 63 template <typename> static void check(...); 64 65 public: 66 #ifdef FMT_FORMAT_MAP_AS_LIST // DEPRECATED! 67 static constexpr const bool value = false; 68 #else 69 static constexpr const bool value = 70 !std::is_void<decltype(check<T>(nullptr))>::value; 71 #endif 72 }; 73 74 template <typename T> class is_set { 75 template <typename U> static auto check(U*) -> typename U::key_type; 76 template <typename> static void check(...); 77 78 public: 79 #ifdef FMT_FORMAT_SET_AS_LIST // DEPRECATED! 80 static constexpr const bool value = false; 81 #else 82 static constexpr const bool value = 83 !std::is_void<decltype(check<T>(nullptr))>::value && !is_map<T>::value; 84 #endif 85 }; 86 87 template <typename... Ts> struct conditional_helper {}; 88 89 template <typename T, typename _ = void> struct is_range_ : std::false_type {}; 90 91 #if !FMT_MSC_VERSION || FMT_MSC_VERSION > 1800 92 93 # define FMT_DECLTYPE_RETURN(val) \ 94 ->decltype(val) { return val; } \ 95 static_assert( \ 96 true, "") // This makes it so that a semicolon is required after the 97 // macro, which helps clang-format handle the formatting. 98 99 // C array overload 100 template <typename T, std::size_t N> 101 auto range_begin(const T (&arr)[N]) -> const T* { 102 return arr; 103 } 104 template <typename T, std::size_t N> 105 auto range_end(const T (&arr)[N]) -> const T* { 106 return arr + N; 107 } 108 109 template <typename T, typename Enable = void> 110 struct has_member_fn_begin_end_t : std::false_type {}; 111 112 template <typename T> 113 struct has_member_fn_begin_end_t<T, void_t<decltype(std::declval<T>().begin()), 114 decltype(std::declval<T>().end())>> 115 : std::true_type {}; 116 117 // Member function overload 118 template <typename T> 119 auto range_begin(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).begin()); 120 template <typename T> 121 auto range_end(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).end()); 122 123 // ADL overload. Only participates in overload resolution if member functions 124 // are not found. 125 template <typename T> 126 auto range_begin(T&& rng) 127 -> enable_if_t<!has_member_fn_begin_end_t<T&&>::value, 128 decltype(begin(static_cast<T&&>(rng)))> { 129 return begin(static_cast<T&&>(rng)); 130 } 131 template <typename T> 132 auto range_end(T&& rng) -> enable_if_t<!has_member_fn_begin_end_t<T&&>::value, 133 decltype(end(static_cast<T&&>(rng)))> { 134 return end(static_cast<T&&>(rng)); 135 } 136 137 template <typename T, typename Enable = void> 138 struct has_const_begin_end : std::false_type {}; 139 template <typename T, typename Enable = void> 140 struct has_mutable_begin_end : std::false_type {}; 141 142 template <typename T> 143 struct has_const_begin_end< 144 T, 145 void_t< 146 decltype(detail::range_begin(std::declval<const remove_cvref_t<T>&>())), 147 decltype(detail::range_end(std::declval<const remove_cvref_t<T>&>()))>> 148 : std::true_type {}; 149 150 template <typename T> 151 struct has_mutable_begin_end< 152 T, void_t<decltype(detail::range_begin(std::declval<T>())), 153 decltype(detail::range_end(std::declval<T>())), 154 // the extra int here is because older versions of MSVC don't 155 // SFINAE properly unless there are distinct types 156 int>> : std::true_type {}; 157 158 template <typename T> 159 struct is_range_<T, void> 160 : std::integral_constant<bool, (has_const_begin_end<T>::value || 161 has_mutable_begin_end<T>::value)> {}; 162 # undef FMT_DECLTYPE_RETURN 163 #endif 164 165 // tuple_size and tuple_element check. 166 template <typename T> class is_tuple_like_ { 167 template <typename U> 168 static auto check(U* p) -> decltype(std::tuple_size<U>::value, int()); 169 template <typename> static void check(...); 170 171 public: 172 static constexpr const bool value = 173 !std::is_void<decltype(check<T>(nullptr))>::value; 174 }; 175 176 // Check for integer_sequence 177 #if defined(__cpp_lib_integer_sequence) || FMT_MSC_VERSION >= 1900 178 template <typename T, T... N> 179 using integer_sequence = std::integer_sequence<T, N...>; 180 template <size_t... N> using index_sequence = std::index_sequence<N...>; 181 template <size_t N> using make_index_sequence = std::make_index_sequence<N>; 182 #else 183 template <typename T, T... N> struct integer_sequence { 184 using value_type = T; 185 186 static FMT_CONSTEXPR auto size() -> size_t { return sizeof...(N); } 187 }; 188 189 template <size_t... N> using index_sequence = integer_sequence<size_t, N...>; 190 191 template <typename T, size_t N, T... Ns> 192 struct make_integer_sequence : make_integer_sequence<T, N - 1, N - 1, Ns...> {}; 193 template <typename T, T... Ns> 194 struct make_integer_sequence<T, 0, Ns...> : integer_sequence<T, Ns...> {}; 195 196 template <size_t N> 197 using make_index_sequence = make_integer_sequence<size_t, N>; 198 #endif 199 200 template <typename T> 201 using tuple_index_sequence = make_index_sequence<std::tuple_size<T>::value>; 202 203 template <typename T, typename C, bool = is_tuple_like_<T>::value> 204 class is_tuple_formattable_ { 205 public: 206 static constexpr const bool value = false; 207 }; 208 template <typename T, typename C> class is_tuple_formattable_<T, C, true> { 209 template <std::size_t... Is> 210 static auto check2(index_sequence<Is...>, 211 integer_sequence<bool, (Is == Is)...>) -> std::true_type; 212 static auto check2(...) -> std::false_type; 213 template <std::size_t... Is> 214 static auto check(index_sequence<Is...>) -> decltype(check2( 215 index_sequence<Is...>{}, 216 integer_sequence<bool, 217 (is_formattable<typename std::tuple_element<Is, T>::type, 218 C>::value)...>{})); 219 220 public: 221 static constexpr const bool value = 222 decltype(check(tuple_index_sequence<T>{}))::value; 223 }; 224 225 template <typename Tuple, typename F, size_t... Is> 226 FMT_CONSTEXPR void for_each(index_sequence<Is...>, Tuple&& t, F&& f) { 227 using std::get; 228 // Using a free function get<Is>(Tuple) now. 229 const int unused[] = {0, ((void)f(get<Is>(t)), 0)...}; 230 ignore_unused(unused); 231 } 232 233 template <typename Tuple, typename F> 234 FMT_CONSTEXPR void for_each(Tuple&& t, F&& f) { 235 for_each(tuple_index_sequence<remove_cvref_t<Tuple>>(), 236 std::forward<Tuple>(t), std::forward<F>(f)); 237 } 238 239 template <typename Tuple1, typename Tuple2, typename F, size_t... Is> 240 void for_each2(index_sequence<Is...>, Tuple1&& t1, Tuple2&& t2, F&& f) { 241 using std::get; 242 const int unused[] = {0, ((void)f(get<Is>(t1), get<Is>(t2)), 0)...}; 243 ignore_unused(unused); 244 } 245 246 template <typename Tuple1, typename Tuple2, typename F> 247 void for_each2(Tuple1&& t1, Tuple2&& t2, F&& f) { 248 for_each2(tuple_index_sequence<remove_cvref_t<Tuple1>>(), 249 std::forward<Tuple1>(t1), std::forward<Tuple2>(t2), 250 std::forward<F>(f)); 251 } 252 253 namespace tuple { 254 // Workaround a bug in MSVC 2019 (v140). 255 template <typename Char, typename... T> 256 using result_t = std::tuple<formatter<remove_cvref_t<T>, Char>...>; 257 258 using std::get; 259 template <typename Tuple, typename Char, std::size_t... Is> 260 auto get_formatters(index_sequence<Is...>) 261 -> result_t<Char, decltype(get<Is>(std::declval<Tuple>()))...>; 262 } // namespace tuple 263 264 #if FMT_MSC_VERSION && FMT_MSC_VERSION < 1920 265 // Older MSVC doesn't get the reference type correctly for arrays. 266 template <typename R> struct range_reference_type_impl { 267 using type = decltype(*detail::range_begin(std::declval<R&>())); 268 }; 269 270 template <typename T, std::size_t N> struct range_reference_type_impl<T[N]> { 271 using type = T&; 272 }; 273 274 template <typename T> 275 using range_reference_type = typename range_reference_type_impl<T>::type; 276 #else 277 template <typename Range> 278 using range_reference_type = 279 decltype(*detail::range_begin(std::declval<Range&>())); 280 #endif 281 282 // We don't use the Range's value_type for anything, but we do need the Range's 283 // reference type, with cv-ref stripped. 284 template <typename Range> 285 using uncvref_type = remove_cvref_t<range_reference_type<Range>>; 286 287 template <typename Formatter> 288 FMT_CONSTEXPR auto maybe_set_debug_format(Formatter& f, bool set) 289 -> decltype(f.set_debug_format(set)) { 290 f.set_debug_format(set); 291 } 292 template <typename Formatter> 293 FMT_CONSTEXPR void maybe_set_debug_format(Formatter&, ...) {} 294 295 // These are not generic lambdas for compatibility with C++11. 296 template <typename ParseContext> struct parse_empty_specs { 297 template <typename Formatter> FMT_CONSTEXPR void operator()(Formatter& f) { 298 f.parse(ctx); 299 detail::maybe_set_debug_format(f, true); 300 } 301 ParseContext& ctx; 302 }; 303 template <typename FormatContext> struct format_tuple_element { 304 using char_type = typename FormatContext::char_type; 305 306 template <typename T> 307 void operator()(const formatter<T, char_type>& f, const T& v) { 308 if (i > 0) 309 ctx.advance_to(detail::copy_str<char_type>(separator, ctx.out())); 310 ctx.advance_to(f.format(v, ctx)); 311 ++i; 312 } 313 314 int i; 315 FormatContext& ctx; 316 basic_string_view<char_type> separator; 317 }; 318 319 } // namespace detail 320 321 template <typename T> struct is_tuple_like { 322 static constexpr const bool value = 323 detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value; 324 }; 325 326 template <typename T, typename C> struct is_tuple_formattable { 327 static constexpr const bool value = 328 detail::is_tuple_formattable_<T, C>::value; 329 }; 330 331 template <typename Tuple, typename Char> 332 struct formatter<Tuple, Char, 333 enable_if_t<fmt::is_tuple_like<Tuple>::value && 334 fmt::is_tuple_formattable<Tuple, Char>::value>> { 335 private: 336 decltype(detail::tuple::get_formatters<Tuple, Char>( 337 detail::tuple_index_sequence<Tuple>())) formatters_; 338 339 basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{}; 340 basic_string_view<Char> opening_bracket_ = 341 detail::string_literal<Char, '('>{}; 342 basic_string_view<Char> closing_bracket_ = 343 detail::string_literal<Char, ')'>{}; 344 345 public: 346 FMT_CONSTEXPR formatter() {} 347 348 FMT_CONSTEXPR void set_separator(basic_string_view<Char> sep) { 349 separator_ = sep; 350 } 351 352 FMT_CONSTEXPR void set_brackets(basic_string_view<Char> open, 353 basic_string_view<Char> close) { 354 opening_bracket_ = open; 355 closing_bracket_ = close; 356 } 357 358 template <typename ParseContext> 359 FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { 360 auto it = ctx.begin(); 361 if (it != ctx.end() && *it != '}') 362 FMT_THROW(format_error("invalid format specifier")); 363 detail::for_each(formatters_, detail::parse_empty_specs<ParseContext>{ctx}); 364 return it; 365 } 366 367 template <typename FormatContext> 368 auto format(const Tuple& value, FormatContext& ctx) const 369 -> decltype(ctx.out()) { 370 ctx.advance_to(detail::copy_str<Char>(opening_bracket_, ctx.out())); 371 detail::for_each2( 372 formatters_, value, 373 detail::format_tuple_element<FormatContext>{0, ctx, separator_}); 374 return detail::copy_str<Char>(closing_bracket_, ctx.out()); 375 } 376 }; 377 378 template <typename T, typename Char> struct is_range { 379 static constexpr const bool value = 380 detail::is_range_<T>::value && !detail::is_std_string_like<T>::value && 381 !std::is_convertible<T, std::basic_string<Char>>::value && 382 !std::is_convertible<T, detail::std_string_view<Char>>::value; 383 }; 384 385 namespace detail { 386 template <typename Context> struct range_mapper { 387 using mapper = arg_mapper<Context>; 388 389 template <typename T, 390 FMT_ENABLE_IF(has_formatter<remove_cvref_t<T>, Context>::value)> 391 static auto map(T&& value) -> T&& { 392 return static_cast<T&&>(value); 393 } 394 template <typename T, 395 FMT_ENABLE_IF(!has_formatter<remove_cvref_t<T>, Context>::value)> 396 static auto map(T&& value) 397 -> decltype(mapper().map(static_cast<T&&>(value))) { 398 return mapper().map(static_cast<T&&>(value)); 399 } 400 }; 401 402 template <typename Char, typename Element> 403 using range_formatter_type = 404 formatter<remove_cvref_t<decltype(range_mapper<buffer_context<Char>>{}.map( 405 std::declval<Element>()))>, 406 Char>; 407 408 template <typename R> 409 using maybe_const_range = 410 conditional_t<has_const_begin_end<R>::value, const R, R>; 411 412 // Workaround a bug in MSVC 2015 and earlier. 413 #if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910 414 template <typename R, typename Char> 415 struct is_formattable_delayed 416 : is_formattable<uncvref_type<maybe_const_range<R>>, Char> {}; 417 #endif 418 } // namespace detail 419 420 template <typename...> struct conjunction : std::true_type {}; 421 template <typename P> struct conjunction<P> : P {}; 422 template <typename P1, typename... Pn> 423 struct conjunction<P1, Pn...> 424 : conditional_t<bool(P1::value), conjunction<Pn...>, P1> {}; 425 426 template <typename T, typename Char, typename Enable = void> 427 struct range_formatter; 428 429 template <typename T, typename Char> 430 struct range_formatter< 431 T, Char, 432 enable_if_t<conjunction<std::is_same<T, remove_cvref_t<T>>, 433 is_formattable<T, Char>>::value>> { 434 private: 435 detail::range_formatter_type<Char, T> underlying_; 436 basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{}; 437 basic_string_view<Char> opening_bracket_ = 438 detail::string_literal<Char, '['>{}; 439 basic_string_view<Char> closing_bracket_ = 440 detail::string_literal<Char, ']'>{}; 441 442 public: 443 FMT_CONSTEXPR range_formatter() {} 444 445 FMT_CONSTEXPR auto underlying() -> detail::range_formatter_type<Char, T>& { 446 return underlying_; 447 } 448 449 FMT_CONSTEXPR void set_separator(basic_string_view<Char> sep) { 450 separator_ = sep; 451 } 452 453 FMT_CONSTEXPR void set_brackets(basic_string_view<Char> open, 454 basic_string_view<Char> close) { 455 opening_bracket_ = open; 456 closing_bracket_ = close; 457 } 458 459 template <typename ParseContext> 460 FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { 461 auto it = ctx.begin(); 462 auto end = ctx.end(); 463 464 if (it != end && *it == 'n') { 465 set_brackets({}, {}); 466 ++it; 467 } 468 469 if (it != end && *it != '}') { 470 if (*it != ':') FMT_THROW(format_error("invalid format specifier")); 471 ++it; 472 } else { 473 detail::maybe_set_debug_format(underlying_, true); 474 } 475 476 ctx.advance_to(it); 477 return underlying_.parse(ctx); 478 } 479 480 template <typename R, typename FormatContext> 481 auto format(R&& range, FormatContext& ctx) const -> decltype(ctx.out()) { 482 detail::range_mapper<buffer_context<Char>> mapper; 483 auto out = ctx.out(); 484 out = detail::copy_str<Char>(opening_bracket_, out); 485 int i = 0; 486 auto it = detail::range_begin(range); 487 auto end = detail::range_end(range); 488 for (; it != end; ++it) { 489 if (i > 0) out = detail::copy_str<Char>(separator_, out); 490 ctx.advance_to(out); 491 auto&& item = *it; 492 out = underlying_.format(mapper.map(item), ctx); 493 ++i; 494 } 495 out = detail::copy_str<Char>(closing_bracket_, out); 496 return out; 497 } 498 }; 499 500 enum class range_format { disabled, map, set, sequence, string, debug_string }; 501 502 namespace detail { 503 template <typename T> 504 struct range_format_kind_ 505 : std::integral_constant<range_format, 506 std::is_same<uncvref_type<T>, T>::value 507 ? range_format::disabled 508 : is_map<T>::value ? range_format::map 509 : is_set<T>::value ? range_format::set 510 : range_format::sequence> {}; 511 512 template <range_format K, typename R, typename Char, typename Enable = void> 513 struct range_default_formatter; 514 515 template <range_format K> 516 using range_format_constant = std::integral_constant<range_format, K>; 517 518 template <range_format K, typename R, typename Char> 519 struct range_default_formatter< 520 K, R, Char, 521 enable_if_t<(K == range_format::sequence || K == range_format::map || 522 K == range_format::set)>> { 523 using range_type = detail::maybe_const_range<R>; 524 range_formatter<detail::uncvref_type<range_type>, Char> underlying_; 525 526 FMT_CONSTEXPR range_default_formatter() { init(range_format_constant<K>()); } 527 528 FMT_CONSTEXPR void init(range_format_constant<range_format::set>) { 529 underlying_.set_brackets(detail::string_literal<Char, '{'>{}, 530 detail::string_literal<Char, '}'>{}); 531 } 532 533 FMT_CONSTEXPR void init(range_format_constant<range_format::map>) { 534 underlying_.set_brackets(detail::string_literal<Char, '{'>{}, 535 detail::string_literal<Char, '}'>{}); 536 underlying_.underlying().set_brackets({}, {}); 537 underlying_.underlying().set_separator( 538 detail::string_literal<Char, ':', ' '>{}); 539 } 540 541 FMT_CONSTEXPR void init(range_format_constant<range_format::sequence>) {} 542 543 template <typename ParseContext> 544 FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { 545 return underlying_.parse(ctx); 546 } 547 548 template <typename FormatContext> 549 auto format(range_type& range, FormatContext& ctx) const 550 -> decltype(ctx.out()) { 551 return underlying_.format(range, ctx); 552 } 553 }; 554 } // namespace detail 555 556 template <typename T, typename Char, typename Enable = void> 557 struct range_format_kind 558 : conditional_t< 559 is_range<T, Char>::value, detail::range_format_kind_<T>, 560 std::integral_constant<range_format, range_format::disabled>> {}; 561 562 template <typename R, typename Char> 563 struct formatter< 564 R, Char, 565 enable_if_t<conjunction<bool_constant<range_format_kind<R, Char>::value != 566 range_format::disabled> 567 // Workaround a bug in MSVC 2015 and earlier. 568 #if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910 569 , 570 detail::is_formattable_delayed<R, Char> 571 #endif 572 >::value>> 573 : detail::range_default_formatter<range_format_kind<R, Char>::value, R, 574 Char> { 575 }; 576 577 template <typename Char, typename... T> struct tuple_join_view : detail::view { 578 const std::tuple<T...>& tuple; 579 basic_string_view<Char> sep; 580 581 tuple_join_view(const std::tuple<T...>& t, basic_string_view<Char> s) 582 : tuple(t), sep{s} {} 583 }; 584 585 // Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers 586 // support in tuple_join. It is disabled by default because of issues with 587 // the dynamic width and precision. 588 #ifndef FMT_TUPLE_JOIN_SPECIFIERS 589 # define FMT_TUPLE_JOIN_SPECIFIERS 0 590 #endif 591 592 template <typename Char, typename... T> 593 struct formatter<tuple_join_view<Char, T...>, Char> { 594 template <typename ParseContext> 595 FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { 596 return do_parse(ctx, std::integral_constant<size_t, sizeof...(T)>()); 597 } 598 599 template <typename FormatContext> 600 auto format(const tuple_join_view<Char, T...>& value, 601 FormatContext& ctx) const -> typename FormatContext::iterator { 602 return do_format(value, ctx, 603 std::integral_constant<size_t, sizeof...(T)>()); 604 } 605 606 private: 607 std::tuple<formatter<typename std::decay<T>::type, Char>...> formatters_; 608 609 template <typename ParseContext> 610 FMT_CONSTEXPR auto do_parse(ParseContext& ctx, 611 std::integral_constant<size_t, 0>) 612 -> decltype(ctx.begin()) { 613 return ctx.begin(); 614 } 615 616 template <typename ParseContext, size_t N> 617 FMT_CONSTEXPR auto do_parse(ParseContext& ctx, 618 std::integral_constant<size_t, N>) 619 -> decltype(ctx.begin()) { 620 auto end = ctx.begin(); 621 #if FMT_TUPLE_JOIN_SPECIFIERS 622 end = std::get<sizeof...(T) - N>(formatters_).parse(ctx); 623 if (N > 1) { 624 auto end1 = do_parse(ctx, std::integral_constant<size_t, N - 1>()); 625 if (end != end1) 626 FMT_THROW(format_error("incompatible format specs for tuple elements")); 627 } 628 #endif 629 return end; 630 } 631 632 template <typename FormatContext> 633 auto do_format(const tuple_join_view<Char, T...>&, FormatContext& ctx, 634 std::integral_constant<size_t, 0>) const -> 635 typename FormatContext::iterator { 636 return ctx.out(); 637 } 638 639 template <typename FormatContext, size_t N> 640 auto do_format(const tuple_join_view<Char, T...>& value, FormatContext& ctx, 641 std::integral_constant<size_t, N>) const -> 642 typename FormatContext::iterator { 643 auto out = std::get<sizeof...(T) - N>(formatters_) 644 .format(std::get<sizeof...(T) - N>(value.tuple), ctx); 645 if (N > 1) { 646 out = std::copy(value.sep.begin(), value.sep.end(), out); 647 ctx.advance_to(out); 648 return do_format(value, ctx, std::integral_constant<size_t, N - 1>()); 649 } 650 return out; 651 } 652 }; 653 654 namespace detail { 655 // Check if T has an interface like a container adaptor (e.g. std::stack, 656 // std::queue, std::priority_queue). 657 template <typename T> class is_container_adaptor_like { 658 template <typename U> static auto check(U* p) -> typename U::container_type; 659 template <typename> static void check(...); 660 661 public: 662 static constexpr const bool value = 663 !std::is_void<decltype(check<T>(nullptr))>::value; 664 }; 665 666 template <typename Container> struct all { 667 const Container& c; 668 auto begin() const -> typename Container::const_iterator { return c.begin(); } 669 auto end() const -> typename Container::const_iterator { return c.end(); } 670 }; 671 } // namespace detail 672 673 template <typename T, typename Char> 674 struct formatter< 675 T, Char, 676 enable_if_t<conjunction<detail::is_container_adaptor_like<T>, 677 bool_constant<range_format_kind<T, Char>::value == 678 range_format::disabled>>::value>> 679 : formatter<detail::all<typename T::container_type>, Char> { 680 using all = detail::all<typename T::container_type>; 681 template <typename FormatContext> 682 auto format(const T& t, FormatContext& ctx) const -> decltype(ctx.out()) { 683 struct getter : T { 684 static auto get(const T& t) -> all { 685 return {t.*(&getter::c)}; // Access c through the derived class. 686 } 687 }; 688 return formatter<all>::format(getter::get(t), ctx); 689 } 690 }; 691 692 FMT_BEGIN_EXPORT 693 694 /** 695 \rst 696 Returns an object that formats `tuple` with elements separated by `sep`. 697 698 **Example**:: 699 700 std::tuple<int, char> t = {1, 'a'}; 701 fmt::print("{}", fmt::join(t, ", ")); 702 // Output: "1, a" 703 \endrst 704 */ 705 template <typename... T> 706 FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple, string_view sep) 707 -> tuple_join_view<char, T...> { 708 return {tuple, sep}; 709 } 710 711 template <typename... T> 712 FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple, 713 basic_string_view<wchar_t> sep) 714 -> tuple_join_view<wchar_t, T...> { 715 return {tuple, sep}; 716 } 717 718 /** 719 \rst 720 Returns an object that formats `initializer_list` with elements separated by 721 `sep`. 722 723 **Example**:: 724 725 fmt::print("{}", fmt::join({1, 2, 3}, ", ")); 726 // Output: "1, 2, 3" 727 \endrst 728 */ 729 template <typename T> 730 auto join(std::initializer_list<T> list, string_view sep) 731 -> join_view<const T*, const T*> { 732 return join(std::begin(list), std::end(list), sep); 733 } 734 735 FMT_END_EXPORT 736 FMT_END_NAMESPACE 737 738 #endif // FMT_RANGES_H_ 739