1 // Formatting library for C++ - range and tuple support
3 // Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors
4 // All rights reserved.
6 // For the license information refer to format.h.
11 #include <initializer_list>
13 #include <type_traits>
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
)
28 template <typename OutputIt
>
29 auto copy(const char* str
, OutputIt out
) -> OutputIt
{
30 while (*str
) *out
++ = *str
++;
34 template <typename OutputIt
> auto copy(char ch
, OutputIt out
) -> OutputIt
{
39 template <typename OutputIt
> auto copy(wchar_t ch
, OutputIt out
) -> OutputIt
{
44 // Returns true if T has a std::string-like interface, like std::string_view.
45 template <typename T
> class is_std_string_like
{
47 static auto check(U
* p
)
48 -> decltype((void)p
->find('a'), p
->length(), (void)p
->data(), int());
49 template <typename
> static void check(...);
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
;
58 template <typename Char
>
59 struct is_std_string_like
<fmt::basic_string_view
<Char
>> : std::true_type
{};
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(...);
66 #ifdef FMT_FORMAT_MAP_AS_LIST // DEPRECATED!
67 static constexpr const bool value
= false;
69 static constexpr const bool value
=
70 !std::is_void
<decltype(check
<T
>(nullptr))>::value
;
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(...);
79 #ifdef FMT_FORMAT_SET_AS_LIST // DEPRECATED!
80 static constexpr const bool value
= false;
82 static constexpr const bool value
=
83 !std::is_void
<decltype(check
<T
>(nullptr))>::value
&& !is_map
<T
>::value
;
87 template <typename
... Ts
> struct conditional_helper
{};
89 template <typename T
, typename _
= void> struct is_range_
: std::false_type
{};
91 #if !FMT_MSC_VERSION || FMT_MSC_VERSION > 1800
93 # define FMT_DECLTYPE_RETURN(val) \
94 ->decltype(val) { return val; } \
96 true, "") // This makes it so that a semicolon is required after the
97 // macro, which helps clang-format handle the formatting.
100 template <typename T
, std::size_t N
>
101 auto range_begin(const T (&arr
)[N
]) -> const T
* {
104 template <typename T
, std::size_t N
>
105 auto range_end(const T (&arr
)[N
]) -> const T
* {
109 template <typename T
, typename Enable
= void>
110 struct has_member_fn_begin_end_t
: std::false_type
{};
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())>>
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());
123 // ADL overload. Only participates in overload resolution if member functions
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
));
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
));
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
{};
142 template <typename T
>
143 struct has_const_begin_end
<
146 decltype(detail::range_begin(std::declval
<const remove_cvref_t
<T
>&>())),
147 decltype(detail::range_end(std::declval
<const remove_cvref_t
<T
>&>()))>>
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
{};
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
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(...);
172 static constexpr const bool value
=
173 !std::is_void
<decltype(check
<T
>(nullptr))>::value
;
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
>;
183 template <typename T
, T
... N
> struct integer_sequence
{
184 using value_type
= T
;
186 static FMT_CONSTEXPR
auto size() -> size_t { return sizeof...(N
); }
189 template <size_t... N
> using index_sequence
= integer_sequence
<size_t, N
...>;
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
...> {};
197 using make_index_sequence
= make_integer_sequence
<size_t, N
>;
200 template <typename T
>
201 using tuple_index_sequence
= make_index_sequence
<std::tuple_size
<T
>::value
>;
203 template <typename T
, typename C
, bool = is_tuple_like_
<T
>::value
>
204 class is_tuple_formattable_
{
206 static constexpr const bool value
= false;
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
,
221 static constexpr const bool value
=
222 decltype(check(tuple_index_sequence
<T
>{}))::value
;
225 template <typename Tuple
, typename F
, size_t... Is
>
226 FMT_CONSTEXPR
void for_each(index_sequence
<Is
...>, Tuple
&& t
, F
&& f
) {
228 // Using a free function get<Is>(Tuple) now.
229 const int unused
[] = {0, ((void)f(get
<Is
>(t
)), 0)...};
230 ignore_unused(unused
);
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
));
239 template <typename Tuple1
, typename Tuple2
, typename F
, size_t... Is
>
240 void for_each2(index_sequence
<Is
...>, Tuple1
&& t1
, Tuple2
&& t2
, F
&& f
) {
242 const int unused
[] = {0, ((void)f(get
<Is
>(t1
), get
<Is
>(t2
)), 0)...};
243 ignore_unused(unused
);
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
),
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
>...>;
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
>()))...>;
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
&>()));
270 template <typename T
, std::size_t N
> struct range_reference_type_impl
<T
[N
]> {
274 template <typename T
>
275 using range_reference_type
= typename range_reference_type_impl
<T
>::type
;
277 template <typename Range
>
278 using range_reference_type
=
279 decltype(*detail::range_begin(std::declval
<Range
&>()));
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
>>;
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
);
292 template <typename Formatter
>
293 FMT_CONSTEXPR
void maybe_set_debug_format(Formatter
&, ...) {}
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
) {
299 detail::maybe_set_debug_format(f
, true);
303 template <typename FormatContext
> struct format_tuple_element
{
304 using char_type
= typename
FormatContext::char_type
;
306 template <typename T
>
307 void operator()(const formatter
<T
, char_type
>& f
, const T
& v
) {
309 ctx
.advance_to(detail::copy_str
<char_type
>(separator
, ctx
.out()));
310 ctx
.advance_to(f
.format(v
, ctx
));
316 basic_string_view
<char_type
> separator
;
319 } // namespace detail
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
;
326 template <typename T
, typename C
> struct is_tuple_formattable
{
327 static constexpr const bool value
=
328 detail::is_tuple_formattable_
<T
, C
>::value
;
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
>> {
336 decltype(detail::tuple::get_formatters
<Tuple
, Char
>(
337 detail::tuple_index_sequence
<Tuple
>())) formatters_
;
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
, ')'>{};
346 FMT_CONSTEXPR
formatter() {}
348 FMT_CONSTEXPR
void set_separator(basic_string_view
<Char
> sep
) {
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
;
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
});
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()));
373 detail::format_tuple_element
<FormatContext
>{0, ctx
, separator_
});
374 return detail::copy_str
<Char
>(closing_bracket_
, ctx
.out());
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
;
386 template <typename Context
> struct range_mapper
{
387 using mapper
= arg_mapper
<Context
>;
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
);
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
));
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
>()))>,
408 template <typename R
>
409 using maybe_const_range
=
410 conditional_t
<has_const_begin_end
<R
>::value
, const R
, R
>;
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
> {};
418 } // namespace detail
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
> {};
426 template <typename T
, typename Char
, typename Enable
= void>
427 struct range_formatter
;
429 template <typename T
, typename Char
>
430 struct range_formatter
<
432 enable_if_t
<conjunction
<std::is_same
<T
, remove_cvref_t
<T
>>,
433 is_formattable
<T
, Char
>>::value
>> {
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
, ']'>{};
443 FMT_CONSTEXPR
range_formatter() {}
445 FMT_CONSTEXPR
auto underlying() -> detail::range_formatter_type
<Char
, T
>& {
449 FMT_CONSTEXPR
void set_separator(basic_string_view
<Char
> sep
) {
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
;
459 template <typename ParseContext
>
460 FMT_CONSTEXPR
auto parse(ParseContext
& ctx
) -> decltype(ctx
.begin()) {
461 auto it
= ctx
.begin();
462 auto end
= ctx
.end();
464 if (it
!= end
&& *it
== 'n') {
465 set_brackets({}, {});
469 if (it
!= end
&& *it
!= '}') {
470 if (*it
!= ':') FMT_THROW(format_error("invalid format specifier"));
473 detail::maybe_set_debug_format(underlying_
, true);
477 return underlying_
.parse(ctx
);
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
);
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
);
492 out
= underlying_
.format(mapper
.map(item
), ctx
);
495 out
= detail::copy_str
<Char
>(closing_bracket_
, out
);
500 enum class range_format
{ disabled
, map
, set
, sequence
, string
, debug_string
};
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
> {};
512 template <range_format K
, typename R
, typename Char
, typename Enable
= void>
513 struct range_default_formatter
;
515 template <range_format K
>
516 using range_format_constant
= std::integral_constant
<range_format
, K
>;
518 template <range_format K
, typename R
, typename Char
>
519 struct range_default_formatter
<
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_
;
526 FMT_CONSTEXPR
range_default_formatter() { init(range_format_constant
<K
>()); }
528 FMT_CONSTEXPR
void init(range_format_constant
<range_format::set
>) {
529 underlying_
.set_brackets(detail::string_literal
<Char
, '{'>{},
530 detail::string_literal
<Char
, '}'>{});
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
, ':', ' '>{});
541 FMT_CONSTEXPR
void init(range_format_constant
<range_format::sequence
>) {}
543 template <typename ParseContext
>
544 FMT_CONSTEXPR
auto parse(ParseContext
& ctx
) -> decltype(ctx
.begin()) {
545 return underlying_
.parse(ctx
);
548 template <typename FormatContext
>
549 auto format(range_type
& range
, FormatContext
& ctx
) const
550 -> decltype(ctx
.out()) {
551 return underlying_
.format(range
, ctx
);
554 } // namespace detail
556 template <typename T
, typename Char
, typename Enable
= void>
557 struct range_format_kind
559 is_range
<T
, Char
>::value
, detail::range_format_kind_
<T
>,
560 std::integral_constant
<range_format
, range_format::disabled
>> {};
562 template <typename R
, typename 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
570 detail::is_formattable_delayed
<R
, Char
>
573 : detail::range_default_formatter
<range_format_kind
<R
, Char
>::value
, R
,
577 template <typename Char
, typename
... T
> struct tuple_join_view
: detail::view
{
578 const std::tuple
<T
...>& tuple
;
579 basic_string_view
<Char
> sep
;
581 tuple_join_view(const std::tuple
<T
...>& t
, basic_string_view
<Char
> s
)
582 : tuple(t
), sep
{s
} {}
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
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
)>());
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
)>());
607 std::tuple
<formatter
<typename
std::decay
<T
>::type
, Char
>...> formatters_
;
609 template <typename ParseContext
>
610 FMT_CONSTEXPR
auto do_parse(ParseContext
& ctx
,
611 std::integral_constant
<size_t, 0>)
612 -> decltype(ctx
.begin()) {
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
);
624 auto end1
= do_parse(ctx
, std::integral_constant
<size_t, N
- 1>());
626 FMT_THROW(format_error("incompatible format specs for tuple elements"));
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
{
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
);
646 out
= std::copy(value
.sep
.begin(), value
.sep
.end(), out
);
648 return do_format(value
, ctx
, std::integral_constant
<size_t, N
- 1>());
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(...);
662 static constexpr const bool value
=
663 !std::is_void
<decltype(check
<T
>(nullptr))>::value
;
666 template <typename Container
> struct all
{
668 auto begin() const -> typename
Container::const_iterator
{ return c
.begin(); }
669 auto end() const -> typename
Container::const_iterator
{ return c
.end(); }
671 } // namespace detail
673 template <typename T
, typename 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()) {
684 static auto get(const T
& t
) -> all
{
685 return {t
.*(&getter::c
)}; // Access c through the derived class.
688 return formatter
<all
>::format(getter::get(t
), ctx
);
696 Returns an object that formats `tuple` with elements separated by `sep`.
700 std::tuple<int, char> t = {1, 'a'};
701 fmt::print("{}", fmt::join(t, ", "));
705 template <typename
... T
>
706 FMT_CONSTEXPR
auto join(const std::tuple
<T
...>& tuple
, string_view sep
)
707 -> tuple_join_view
<char, T
...> {
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
...> {
720 Returns an object that formats `initializer_list` with elements separated by
725 fmt::print("{}", fmt::join({1, 2, 3}, ", "));
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
);
738 #endif // FMT_RANGES_H_