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.
12 # include <initializer_list>
16 # include <type_traits>
25 enum class range_format
{ disabled
, map
, set
, sequence
, string
, debug_string
};
29 template <typename T
> class is_map
{
30 template <typename U
> static auto check(U
*) -> typename
U::mapped_type
;
31 template <typename
> static void check(...);
34 static constexpr const bool value
=
35 !std::is_void
<decltype(check
<T
>(nullptr))>::value
;
38 template <typename T
> class is_set
{
39 template <typename U
> static auto check(U
*) -> typename
U::key_type
;
40 template <typename
> static void check(...);
43 static constexpr const bool value
=
44 !std::is_void
<decltype(check
<T
>(nullptr))>::value
&& !is_map
<T
>::value
;
47 template <typename
... Ts
> struct conditional_helper
{};
49 template <typename T
, typename _
= void> struct is_range_
: std::false_type
{};
51 #if !FMT_MSC_VERSION || FMT_MSC_VERSION > 1800
53 # define FMT_DECLTYPE_RETURN(val) \
54 ->decltype(val) { return val; } \
56 true, "") // This makes it so that a semicolon is required after the
57 // macro, which helps clang-format handle the formatting.
60 template <typename T
, std::size_t N
>
61 auto range_begin(const T (&arr
)[N
]) -> const T
* {
64 template <typename T
, std::size_t N
>
65 auto range_end(const T (&arr
)[N
]) -> const T
* {
69 template <typename T
, typename Enable
= void>
70 struct has_member_fn_begin_end_t
: std::false_type
{};
73 struct has_member_fn_begin_end_t
<T
, void_t
<decltype(*std::declval
<T
>().begin()),
74 decltype(std::declval
<T
>().end())>>
77 // Member function overloads.
79 auto range_begin(T
&& rng
) FMT_DECLTYPE_RETURN(static_cast<T
&&>(rng
).begin());
81 auto range_end(T
&& rng
) FMT_DECLTYPE_RETURN(static_cast<T
&&>(rng
).end());
83 // ADL overloads. Only participate in overload resolution if member functions
86 auto range_begin(T
&& rng
)
87 -> enable_if_t
<!has_member_fn_begin_end_t
<T
&&>::value
,
88 decltype(begin(static_cast<T
&&>(rng
)))> {
89 return begin(static_cast<T
&&>(rng
));
92 auto range_end(T
&& rng
) -> enable_if_t
<!has_member_fn_begin_end_t
<T
&&>::value
,
93 decltype(end(static_cast<T
&&>(rng
)))> {
94 return end(static_cast<T
&&>(rng
));
97 template <typename T
, typename Enable
= void>
98 struct has_const_begin_end
: std::false_type
{};
99 template <typename T
, typename Enable
= void>
100 struct has_mutable_begin_end
: std::false_type
{};
102 template <typename T
>
103 struct has_const_begin_end
<
104 T
, void_t
<decltype(*detail::range_begin(
105 std::declval
<const remove_cvref_t
<T
>&>())),
106 decltype(detail::range_end(
107 std::declval
<const remove_cvref_t
<T
>&>()))>>
110 template <typename T
>
111 struct has_mutable_begin_end
<
112 T
, void_t
<decltype(*detail::range_begin(std::declval
<T
&>())),
113 decltype(detail::range_end(std::declval
<T
&>())),
114 // the extra int here is because older versions of MSVC don't
115 // SFINAE properly unless there are distinct types
116 int>> : std::true_type
{};
118 template <typename T
>
119 struct is_range_
<T
, void>
120 : std::integral_constant
<bool, (has_const_begin_end
<T
>::value
||
121 has_mutable_begin_end
<T
>::value
)> {};
122 # undef FMT_DECLTYPE_RETURN
125 // tuple_size and tuple_element check.
126 template <typename T
> class is_tuple_like_
{
127 template <typename U
>
128 static auto check(U
* p
) -> decltype(std::tuple_size
<U
>::value
, int());
129 template <typename
> static void check(...);
132 static constexpr const bool value
=
133 !std::is_void
<decltype(check
<T
>(nullptr))>::value
;
136 // Check for integer_sequence
137 #if defined(__cpp_lib_integer_sequence) || FMT_MSC_VERSION >= 1900
138 template <typename T
, T
... N
>
139 using integer_sequence
= std::integer_sequence
<T
, N
...>;
140 template <size_t... N
> using index_sequence
= std::index_sequence
<N
...>;
141 template <size_t N
> using make_index_sequence
= std::make_index_sequence
<N
>;
143 template <typename T
, T
... N
> struct integer_sequence
{
144 using value_type
= T
;
146 static FMT_CONSTEXPR
auto size() -> size_t { return sizeof...(N
); }
149 template <size_t... N
> using index_sequence
= integer_sequence
<size_t, N
...>;
151 template <typename T
, size_t N
, T
... Ns
>
152 struct make_integer_sequence
: make_integer_sequence
<T
, N
- 1, N
- 1, Ns
...> {};
153 template <typename T
, T
... Ns
>
154 struct make_integer_sequence
<T
, 0, Ns
...> : integer_sequence
<T
, Ns
...> {};
157 using make_index_sequence
= make_integer_sequence
<size_t, N
>;
160 template <typename T
>
161 using tuple_index_sequence
= make_index_sequence
<std::tuple_size
<T
>::value
>;
163 template <typename T
, typename C
, bool = is_tuple_like_
<T
>::value
>
164 class is_tuple_formattable_
{
166 static constexpr const bool value
= false;
168 template <typename T
, typename C
> class is_tuple_formattable_
<T
, C
, true> {
169 template <size_t... Is
>
170 static auto all_true(index_sequence
<Is
...>,
171 integer_sequence
<bool, (Is
>= 0)...>) -> std::true_type
;
172 static auto all_true(...) -> std::false_type
;
174 template <size_t... Is
>
175 static auto check(index_sequence
<Is
...>) -> decltype(all_true(
176 index_sequence
<Is
...>{},
177 integer_sequence
<bool,
178 (is_formattable
<typename
std::tuple_element
<Is
, T
>::type
,
182 static constexpr const bool value
=
183 decltype(check(tuple_index_sequence
<T
>{}))::value
;
186 template <typename Tuple
, typename F
, size_t... Is
>
187 FMT_CONSTEXPR
void for_each(index_sequence
<Is
...>, Tuple
&& t
, F
&& f
) {
189 // Using a free function get<Is>(Tuple) now.
190 const int unused
[] = {0, ((void)f(get
<Is
>(t
)), 0)...};
191 ignore_unused(unused
);
194 template <typename Tuple
, typename F
>
195 FMT_CONSTEXPR
void for_each(Tuple
&& t
, F
&& f
) {
196 for_each(tuple_index_sequence
<remove_cvref_t
<Tuple
>>(),
197 std::forward
<Tuple
>(t
), std::forward
<F
>(f
));
200 template <typename Tuple1
, typename Tuple2
, typename F
, size_t... Is
>
201 void for_each2(index_sequence
<Is
...>, Tuple1
&& t1
, Tuple2
&& t2
, F
&& f
) {
203 const int unused
[] = {0, ((void)f(get
<Is
>(t1
), get
<Is
>(t2
)), 0)...};
204 ignore_unused(unused
);
207 template <typename Tuple1
, typename Tuple2
, typename F
>
208 void for_each2(Tuple1
&& t1
, Tuple2
&& t2
, F
&& f
) {
209 for_each2(tuple_index_sequence
<remove_cvref_t
<Tuple1
>>(),
210 std::forward
<Tuple1
>(t1
), std::forward
<Tuple2
>(t2
),
215 // Workaround a bug in MSVC 2019 (v140).
216 template <typename Char
, typename
... T
>
217 using result_t
= std::tuple
<formatter
<remove_cvref_t
<T
>, Char
>...>;
220 template <typename Tuple
, typename Char
, std::size_t... Is
>
221 auto get_formatters(index_sequence
<Is
...>)
222 -> result_t
<Char
, decltype(get
<Is
>(std::declval
<Tuple
>()))...>;
225 #if FMT_MSC_VERSION && FMT_MSC_VERSION < 1920
226 // Older MSVC doesn't get the reference type correctly for arrays.
227 template <typename R
> struct range_reference_type_impl
{
228 using type
= decltype(*detail::range_begin(std::declval
<R
&>()));
231 template <typename T
, std::size_t N
> struct range_reference_type_impl
<T
[N
]> {
235 template <typename T
>
236 using range_reference_type
= typename range_reference_type_impl
<T
>::type
;
238 template <typename Range
>
239 using range_reference_type
=
240 decltype(*detail::range_begin(std::declval
<Range
&>()));
243 // We don't use the Range's value_type for anything, but we do need the Range's
244 // reference type, with cv-ref stripped.
245 template <typename Range
>
246 using uncvref_type
= remove_cvref_t
<range_reference_type
<Range
>>;
248 template <typename Formatter
>
249 FMT_CONSTEXPR
auto maybe_set_debug_format(Formatter
& f
, bool set
)
250 -> decltype(f
.set_debug_format(set
)) {
251 f
.set_debug_format(set
);
253 template <typename Formatter
>
254 FMT_CONSTEXPR
void maybe_set_debug_format(Formatter
&, ...) {}
256 template <typename T
>
257 struct range_format_kind_
258 : std::integral_constant
<range_format
,
259 std::is_same
<uncvref_type
<T
>, T
>::value
260 ? range_format::disabled
261 : is_map
<T
>::value
? range_format::map
262 : is_set
<T
>::value
? range_format::set
263 : range_format::sequence
> {};
265 template <range_format K
>
266 using range_format_constant
= std::integral_constant
<range_format
, K
>;
268 // These are not generic lambdas for compatibility with C++11.
269 template <typename ParseContext
> struct parse_empty_specs
{
270 template <typename Formatter
> FMT_CONSTEXPR
void operator()(Formatter
& f
) {
272 detail::maybe_set_debug_format(f
, true);
276 template <typename FormatContext
> struct format_tuple_element
{
277 using char_type
= typename
FormatContext::char_type
;
279 template <typename T
>
280 void operator()(const formatter
<T
, char_type
>& f
, const T
& v
) {
281 if (i
> 0) ctx
.advance_to(detail::copy
<char_type
>(separator
, ctx
.out()));
282 ctx
.advance_to(f
.format(v
, ctx
));
288 basic_string_view
<char_type
> separator
;
291 } // namespace detail
293 template <typename T
> struct is_tuple_like
{
294 static constexpr const bool value
=
295 detail::is_tuple_like_
<T
>::value
&& !detail::is_range_
<T
>::value
;
298 template <typename T
, typename C
> struct is_tuple_formattable
{
299 static constexpr const bool value
=
300 detail::is_tuple_formattable_
<T
, C
>::value
;
303 template <typename Tuple
, typename Char
>
304 struct formatter
<Tuple
, Char
,
305 enable_if_t
<fmt::is_tuple_like
<Tuple
>::value
&&
306 fmt::is_tuple_formattable
<Tuple
, Char
>::value
>> {
308 decltype(detail::tuple::get_formatters
<Tuple
, Char
>(
309 detail::tuple_index_sequence
<Tuple
>())) formatters_
;
311 basic_string_view
<Char
> separator_
= detail::string_literal
<Char
, ',', ' '>{};
312 basic_string_view
<Char
> opening_bracket_
=
313 detail::string_literal
<Char
, '('>{};
314 basic_string_view
<Char
> closing_bracket_
=
315 detail::string_literal
<Char
, ')'>{};
318 FMT_CONSTEXPR
formatter() {}
320 FMT_CONSTEXPR
void set_separator(basic_string_view
<Char
> sep
) {
324 FMT_CONSTEXPR
void set_brackets(basic_string_view
<Char
> open
,
325 basic_string_view
<Char
> close
) {
326 opening_bracket_
= open
;
327 closing_bracket_
= close
;
330 template <typename ParseContext
>
331 FMT_CONSTEXPR
auto parse(ParseContext
& ctx
) -> decltype(ctx
.begin()) {
332 auto it
= ctx
.begin();
333 if (it
!= ctx
.end() && *it
!= '}') report_error("invalid format specifier");
334 detail::for_each(formatters_
, detail::parse_empty_specs
<ParseContext
>{ctx
});
338 template <typename FormatContext
>
339 auto format(const Tuple
& value
, FormatContext
& ctx
) const
340 -> decltype(ctx
.out()) {
341 ctx
.advance_to(detail::copy
<Char
>(opening_bracket_
, ctx
.out()));
344 detail::format_tuple_element
<FormatContext
>{0, ctx
, separator_
});
345 return detail::copy
<Char
>(closing_bracket_
, ctx
.out());
349 template <typename T
, typename Char
> struct is_range
{
350 static constexpr const bool value
=
351 detail::is_range_
<T
>::value
&& !detail::has_to_string_view
<T
>::value
;
355 template <typename Context
> struct range_mapper
{
356 using mapper
= arg_mapper
<Context
>;
358 template <typename T
,
359 FMT_ENABLE_IF(has_formatter
<remove_cvref_t
<T
>, Context
>::value
)>
360 static auto map(T
&& value
) -> T
&& {
361 return static_cast<T
&&>(value
);
363 template <typename T
,
364 FMT_ENABLE_IF(!has_formatter
<remove_cvref_t
<T
>, Context
>::value
)>
365 static auto map(T
&& value
)
366 -> decltype(mapper().map(static_cast<T
&&>(value
))) {
367 return mapper().map(static_cast<T
&&>(value
));
371 template <typename Char
, typename Element
>
372 using range_formatter_type
=
373 formatter
<remove_cvref_t
<decltype(range_mapper
<buffered_context
<Char
>>{}
374 .map(std::declval
<Element
>()))>,
377 template <typename R
>
378 using maybe_const_range
=
379 conditional_t
<has_const_begin_end
<R
>::value
, const R
, R
>;
381 // Workaround a bug in MSVC 2015 and earlier.
382 #if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
383 template <typename R
, typename Char
>
384 struct is_formattable_delayed
385 : is_formattable
<uncvref_type
<maybe_const_range
<R
>>, Char
> {};
387 } // namespace detail
389 template <typename
...> struct conjunction
: std::true_type
{};
390 template <typename P
> struct conjunction
<P
> : P
{};
391 template <typename P1
, typename
... Pn
>
392 struct conjunction
<P1
, Pn
...>
393 : conditional_t
<bool(P1::value
), conjunction
<Pn
...>, P1
> {};
395 template <typename T
, typename Char
, typename Enable
= void>
396 struct range_formatter
;
398 template <typename T
, typename Char
>
399 struct range_formatter
<
401 enable_if_t
<conjunction
<std::is_same
<T
, remove_cvref_t
<T
>>,
402 is_formattable
<T
, Char
>>::value
>> {
404 detail::range_formatter_type
<Char
, T
> underlying_
;
405 basic_string_view
<Char
> separator_
= detail::string_literal
<Char
, ',', ' '>{};
406 basic_string_view
<Char
> opening_bracket_
=
407 detail::string_literal
<Char
, '['>{};
408 basic_string_view
<Char
> closing_bracket_
=
409 detail::string_literal
<Char
, ']'>{};
410 bool is_debug
= false;
412 template <typename Output
, typename It
, typename Sentinel
, typename U
= T
,
413 FMT_ENABLE_IF(std::is_same
<U
, Char
>::value
)>
414 auto write_debug_string(Output
& out
, It it
, Sentinel end
) const -> Output
{
415 auto buf
= basic_memory_buffer
<Char
>();
416 for (; it
!= end
; ++it
) buf
.push_back(*it
);
417 auto specs
= format_specs();
418 specs
.type
= presentation_type::debug
;
419 return detail::write
<Char
>(
420 out
, basic_string_view
<Char
>(buf
.data(), buf
.size()), specs
);
423 template <typename Output
, typename It
, typename Sentinel
, typename U
= T
,
424 FMT_ENABLE_IF(!std::is_same
<U
, Char
>::value
)>
425 auto write_debug_string(Output
& out
, It
, Sentinel
) const -> Output
{
430 FMT_CONSTEXPR
range_formatter() {}
432 FMT_CONSTEXPR
auto underlying() -> detail::range_formatter_type
<Char
, T
>& {
436 FMT_CONSTEXPR
void set_separator(basic_string_view
<Char
> sep
) {
440 FMT_CONSTEXPR
void set_brackets(basic_string_view
<Char
> open
,
441 basic_string_view
<Char
> close
) {
442 opening_bracket_
= open
;
443 closing_bracket_
= close
;
446 template <typename ParseContext
>
447 FMT_CONSTEXPR
auto parse(ParseContext
& ctx
) -> decltype(ctx
.begin()) {
448 auto it
= ctx
.begin();
449 auto end
= ctx
.end();
450 detail::maybe_set_debug_format(underlying_
, true);
451 if (it
== end
) return underlying_
.parse(ctx
);
453 switch (detail::to_ascii(*it
)) {
455 set_brackets({}, {});
460 set_brackets({}, {});
462 if (it
== end
|| *it
!= 's') report_error("invalid format specifier");
465 if (!std::is_same
<T
, Char
>::value
)
466 report_error("invalid format specifier");
468 set_brackets(detail::string_literal
<Char
, '"'>{},
469 detail::string_literal
<Char
, '"'>{});
471 detail::maybe_set_debug_format(underlying_
, false);
477 if (it
!= end
&& *it
!= '}') {
478 if (*it
!= ':') report_error("invalid format specifier");
479 detail::maybe_set_debug_format(underlying_
, false);
484 return underlying_
.parse(ctx
);
487 template <typename R
, typename FormatContext
>
488 auto format(R
&& range
, FormatContext
& ctx
) const -> decltype(ctx
.out()) {
489 auto mapper
= detail::range_mapper
<buffered_context
<Char
>>();
490 auto out
= ctx
.out();
491 auto it
= detail::range_begin(range
);
492 auto end
= detail::range_end(range
);
493 if (is_debug
) return write_debug_string(out
, std::move(it
), end
);
495 out
= detail::copy
<Char
>(opening_bracket_
, out
);
497 for (; it
!= end
; ++it
) {
498 if (i
> 0) out
= detail::copy
<Char
>(separator_
, out
);
500 auto&& item
= *it
; // Need an lvalue
501 out
= underlying_
.format(mapper
.map(item
), ctx
);
504 out
= detail::copy
<Char
>(closing_bracket_
, out
);
510 template <typename T
, typename Char
, typename Enable
= void>
511 struct range_format_kind
513 is_range
<T
, Char
>::value
, detail::range_format_kind_
<T
>,
514 std::integral_constant
<range_format
, range_format::disabled
>> {};
516 template <typename R
, typename Char
>
519 enable_if_t
<conjunction
<
521 range_format_kind
<R
, Char
>::value
!= range_format::disabled
&&
522 range_format_kind
<R
, Char
>::value
!= range_format::map
&&
523 range_format_kind
<R
, Char
>::value
!= range_format::string
&&
524 range_format_kind
<R
, Char
>::value
!= range_format::debug_string
>
525 // Workaround a bug in MSVC 2015 and earlier.
526 #if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
528 detail::is_formattable_delayed
<R
, Char
>
532 using range_type
= detail::maybe_const_range
<R
>;
533 range_formatter
<detail::uncvref_type
<range_type
>, Char
> range_formatter_
;
536 using nonlocking
= void;
538 FMT_CONSTEXPR
formatter() {
539 if (detail::const_check(range_format_kind
<R
, Char
>::value
!=
542 range_formatter_
.set_brackets(detail::string_literal
<Char
, '{'>{},
543 detail::string_literal
<Char
, '}'>{});
546 template <typename ParseContext
>
547 FMT_CONSTEXPR
auto parse(ParseContext
& ctx
) -> decltype(ctx
.begin()) {
548 return range_formatter_
.parse(ctx
);
551 template <typename FormatContext
>
552 auto format(range_type
& range
, FormatContext
& ctx
) const
553 -> decltype(ctx
.out()) {
554 return range_formatter_
.format(range
, ctx
);
559 template <typename R
, typename Char
>
562 enable_if_t
<range_format_kind
<R
, Char
>::value
== range_format::map
>> {
564 using map_type
= detail::maybe_const_range
<R
>;
565 using element_type
= detail::uncvref_type
<map_type
>;
567 decltype(detail::tuple::get_formatters
<element_type
, Char
>(
568 detail::tuple_index_sequence
<element_type
>())) formatters_
;
569 bool no_delimiters_
= false;
572 FMT_CONSTEXPR
formatter() {}
574 template <typename ParseContext
>
575 FMT_CONSTEXPR
auto parse(ParseContext
& ctx
) -> decltype(ctx
.begin()) {
576 auto it
= ctx
.begin();
577 auto end
= ctx
.end();
579 if (detail::to_ascii(*it
) == 'n') {
580 no_delimiters_
= true;
583 if (it
!= end
&& *it
!= '}') {
584 if (*it
!= ':') report_error("invalid format specifier");
589 detail::for_each(formatters_
, detail::parse_empty_specs
<ParseContext
>{ctx
});
593 template <typename FormatContext
>
594 auto format(map_type
& map
, FormatContext
& ctx
) const -> decltype(ctx
.out()) {
595 auto out
= ctx
.out();
596 basic_string_view
<Char
> open
= detail::string_literal
<Char
, '{'>{};
597 if (!no_delimiters_
) out
= detail::copy
<Char
>(open
, out
);
599 auto mapper
= detail::range_mapper
<buffered_context
<Char
>>();
600 basic_string_view
<Char
> sep
= detail::string_literal
<Char
, ',', ' '>{};
601 for (auto&& value
: map
) {
602 if (i
> 0) out
= detail::copy
<Char
>(sep
, out
);
604 detail::for_each2(formatters_
, mapper
.map(value
),
605 detail::format_tuple_element
<FormatContext
>{
606 0, ctx
, detail::string_literal
<Char
, ':', ' '>{}});
609 basic_string_view
<Char
> close
= detail::string_literal
<Char
, '}'>{};
610 if (!no_delimiters_
) out
= detail::copy
<Char
>(close
, out
);
615 // A (debug_)string formatter.
616 template <typename R
, typename Char
>
619 enable_if_t
<range_format_kind
<R
, Char
>::value
== range_format::string
||
620 range_format_kind
<R
, Char
>::value
==
621 range_format::debug_string
>> {
623 using range_type
= detail::maybe_const_range
<R
>;
625 conditional_t
<std::is_constructible
<
626 detail::std_string_view
<Char
>,
627 decltype(detail::range_begin(std::declval
<R
>())),
628 decltype(detail::range_end(std::declval
<R
>()))>::value
,
629 detail::std_string_view
<Char
>, std::basic_string
<Char
>>;
631 formatter
<string_type
, Char
> underlying_
;
634 template <typename ParseContext
>
635 FMT_CONSTEXPR
auto parse(ParseContext
& ctx
) -> decltype(ctx
.begin()) {
636 return underlying_
.parse(ctx
);
639 template <typename FormatContext
>
640 auto format(range_type
& range
, FormatContext
& ctx
) const
641 -> decltype(ctx
.out()) {
642 auto out
= ctx
.out();
643 if (detail::const_check(range_format_kind
<R
, Char
>::value
==
644 range_format::debug_string
))
646 out
= underlying_
.format(
647 string_type
{detail::range_begin(range
), detail::range_end(range
)}, ctx
);
648 if (detail::const_check(range_format_kind
<R
, Char
>::value
==
649 range_format::debug_string
))
655 template <typename It
, typename Sentinel
, typename Char
= char>
656 struct join_view
: detail::view
{
659 basic_string_view
<Char
> sep
;
661 join_view(It b
, Sentinel e
, basic_string_view
<Char
> s
)
662 : begin(std::move(b
)), end(e
), sep(s
) {}
665 template <typename It
, typename Sentinel
, typename Char
>
666 struct formatter
<join_view
<It
, Sentinel
, Char
>, Char
> {
669 #ifdef __cpp_lib_ranges
670 std::iter_value_t
<It
>;
672 typename
std::iterator_traits
<It
>::value_type
;
674 formatter
<remove_cvref_t
<value_type
>, Char
> value_formatter_
;
676 using view_ref
= conditional_t
<std::is_copy_constructible
<It
>::value
,
677 const join_view
<It
, Sentinel
, Char
>&,
678 join_view
<It
, Sentinel
, Char
>&&>;
681 using nonlocking
= void;
683 template <typename ParseContext
>
684 FMT_CONSTEXPR
auto parse(ParseContext
& ctx
) -> const Char
* {
685 return value_formatter_
.parse(ctx
);
688 template <typename FormatContext
>
689 auto format(view_ref
& value
, FormatContext
& ctx
) const
690 -> decltype(ctx
.out()) {
691 auto it
= std::forward
<view_ref
>(value
).begin
;
692 auto out
= ctx
.out();
693 if (it
== value
.end
) return out
;
694 out
= value_formatter_
.format(*it
, ctx
);
696 while (it
!= value
.end
) {
697 out
= detail::copy
<Char
>(value
.sep
.begin(), value
.sep
.end(), out
);
699 out
= value_formatter_
.format(*it
, ctx
);
706 /// Returns a view that formats the iterator range `[begin, end)` with elements
707 /// separated by `sep`.
708 template <typename It
, typename Sentinel
>
709 auto join(It begin
, Sentinel end
, string_view sep
) -> join_view
<It
, Sentinel
> {
710 return {std::move(begin
), end
, sep
};
714 * Returns a view that formats `range` with elements separated by `sep`.
718 * auto v = std::vector<int>{1, 2, 3};
719 * fmt::print("{}", fmt::join(v, ", "));
722 * `fmt::join` applies passed format specifiers to the range elements:
724 * fmt::print("{:02}", fmt::join(v, ", "));
725 * // Output: 01, 02, 03
727 template <typename Range
>
728 auto join(Range
&& r
, string_view sep
)
729 -> join_view
<decltype(detail::range_begin(r
)),
730 decltype(detail::range_end(r
))> {
731 return {detail::range_begin(r
), detail::range_end(r
), sep
};
734 template <typename Char
, typename
... T
> struct tuple_join_view
: detail::view
{
735 const std::tuple
<T
...>& tuple
;
736 basic_string_view
<Char
> sep
;
738 tuple_join_view(const std::tuple
<T
...>& t
, basic_string_view
<Char
> s
)
739 : tuple(t
), sep
{s
} {}
742 // Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers
743 // support in tuple_join. It is disabled by default because of issues with
744 // the dynamic width and precision.
745 #ifndef FMT_TUPLE_JOIN_SPECIFIERS
746 # define FMT_TUPLE_JOIN_SPECIFIERS 0
749 template <typename Char
, typename
... T
>
750 struct formatter
<tuple_join_view
<Char
, T
...>, Char
> {
751 template <typename ParseContext
>
752 FMT_CONSTEXPR
auto parse(ParseContext
& ctx
) -> decltype(ctx
.begin()) {
753 return do_parse(ctx
, std::integral_constant
<size_t, sizeof...(T
)>());
756 template <typename FormatContext
>
757 auto format(const tuple_join_view
<Char
, T
...>& value
,
758 FormatContext
& ctx
) const -> typename
FormatContext::iterator
{
759 return do_format(value
, ctx
,
760 std::integral_constant
<size_t, sizeof...(T
)>());
764 std::tuple
<formatter
<typename
std::decay
<T
>::type
, Char
>...> formatters_
;
766 template <typename ParseContext
>
767 FMT_CONSTEXPR
auto do_parse(ParseContext
& ctx
,
768 std::integral_constant
<size_t, 0>)
769 -> decltype(ctx
.begin()) {
773 template <typename ParseContext
, size_t N
>
774 FMT_CONSTEXPR
auto do_parse(ParseContext
& ctx
,
775 std::integral_constant
<size_t, N
>)
776 -> decltype(ctx
.begin()) {
777 auto end
= ctx
.begin();
778 #if FMT_TUPLE_JOIN_SPECIFIERS
779 end
= std::get
<sizeof...(T
) - N
>(formatters_
).parse(ctx
);
781 auto end1
= do_parse(ctx
, std::integral_constant
<size_t, N
- 1>());
783 report_error("incompatible format specs for tuple elements");
789 template <typename FormatContext
>
790 auto do_format(const tuple_join_view
<Char
, T
...>&, FormatContext
& ctx
,
791 std::integral_constant
<size_t, 0>) const ->
792 typename
FormatContext::iterator
{
796 template <typename FormatContext
, size_t N
>
797 auto do_format(const tuple_join_view
<Char
, T
...>& value
, FormatContext
& ctx
,
798 std::integral_constant
<size_t, N
>) const ->
799 typename
FormatContext::iterator
{
800 auto out
= std::get
<sizeof...(T
) - N
>(formatters_
)
801 .format(std::get
<sizeof...(T
) - N
>(value
.tuple
), ctx
);
802 if (N
<= 1) return out
;
803 out
= detail::copy
<Char
>(value
.sep
, out
);
805 return do_format(value
, ctx
, std::integral_constant
<size_t, N
- 1>());
810 // Check if T has an interface like a container adaptor (e.g. std::stack,
811 // std::queue, std::priority_queue).
812 template <typename T
> class is_container_adaptor_like
{
813 template <typename U
> static auto check(U
* p
) -> typename
U::container_type
;
814 template <typename
> static void check(...);
817 static constexpr const bool value
=
818 !std::is_void
<decltype(check
<T
>(nullptr))>::value
;
821 template <typename Container
> struct all
{
823 auto begin() const -> typename
Container::const_iterator
{ return c
.begin(); }
824 auto end() const -> typename
Container::const_iterator
{ return c
.end(); }
826 } // namespace detail
828 template <typename T
, typename Char
>
831 enable_if_t
<conjunction
<detail::is_container_adaptor_like
<T
>,
832 bool_constant
<range_format_kind
<T
, Char
>::value
==
833 range_format::disabled
>>::value
>>
834 : formatter
<detail::all
<typename
T::container_type
>, Char
> {
835 using all
= detail::all
<typename
T::container_type
>;
836 template <typename FormatContext
>
837 auto format(const T
& t
, FormatContext
& ctx
) const -> decltype(ctx
.out()) {
839 static auto get(const T
& t
) -> all
{
840 return {t
.*(&getter::c
)}; // Access c through the derived class.
843 return formatter
<all
>::format(getter::get(t
), ctx
);
850 * Returns an object that formats `std::tuple` with elements separated by `sep`.
854 * auto t = std::tuple<int, char>{1, 'a'};
855 * fmt::print("{}", fmt::join(t, ", "));
858 template <typename
... T
>
859 FMT_CONSTEXPR
auto join(const std::tuple
<T
...>& tuple
, string_view sep
)
860 -> tuple_join_view
<char, T
...> {
865 * Returns an object that formats `std::initializer_list` with elements
866 * separated by `sep`.
870 * fmt::print("{}", fmt::join({1, 2, 3}, ", "));
871 * // Output: "1, 2, 3"
873 template <typename T
>
874 auto join(std::initializer_list
<T
> list
, string_view sep
)
875 -> join_view
<const T
*, const T
*> {
876 return join(std::begin(list
), std::end(list
), sep
);
882 #endif // FMT_RANGES_H_