1 // Formatting library for C++ - experimental range support
3 // Copyright (c) 2012 - present, Victor Zverovich
4 // All rights reserved.
6 // For the license information refer to format.h.
8 // Copyright (c) 2018 - present, Remotion (Igor Schulz)
10 // {fmt} support for ranges, containers and types tuple interface.
15 #include <initializer_list>
17 #include <type_traits>
25 template <typename Range
, typename OutputIt
>
26 auto copy(const Range
& range
, OutputIt out
) -> OutputIt
{
27 for (auto it
= range
.begin(), end
= range
.end(); it
!= end
; ++it
)
32 template <typename OutputIt
>
33 auto copy(const char* str
, OutputIt out
) -> OutputIt
{
34 while (*str
) *out
++ = *str
++;
38 template <typename OutputIt
> auto copy(char ch
, OutputIt out
) -> OutputIt
{
43 template <typename OutputIt
> auto copy(wchar_t ch
, OutputIt out
) -> OutputIt
{
48 // Returns true if T has a std::string-like interface, like std::string_view.
49 template <typename T
> class is_std_string_like
{
51 static auto check(U
* p
)
52 -> decltype((void)p
->find('a'), p
->length(), (void)p
->data(), int());
53 template <typename
> static void check(...);
56 static constexpr const bool value
=
57 is_string
<T
>::value
||
58 std::is_convertible
<T
, std_string_view
<char>>::value
||
59 !std::is_void
<decltype(check
<T
>(nullptr))>::value
;
62 template <typename Char
>
63 struct is_std_string_like
<fmt::basic_string_view
<Char
>> : std::true_type
{};
65 template <typename T
> class is_map
{
66 template <typename U
> static auto check(U
*) -> typename
U::mapped_type
;
67 template <typename
> static void check(...);
70 #ifdef FMT_FORMAT_MAP_AS_LIST // DEPRECATED!
71 static constexpr const bool value
= false;
73 static constexpr const bool value
=
74 !std::is_void
<decltype(check
<T
>(nullptr))>::value
;
78 template <typename T
> class is_set
{
79 template <typename U
> static auto check(U
*) -> typename
U::key_type
;
80 template <typename
> static void check(...);
83 #ifdef FMT_FORMAT_SET_AS_LIST // DEPRECATED!
84 static constexpr const bool value
= false;
86 static constexpr const bool value
=
87 !std::is_void
<decltype(check
<T
>(nullptr))>::value
&& !is_map
<T
>::value
;
91 template <typename
... Ts
> struct conditional_helper
{};
93 template <typename T
, typename _
= void> struct is_range_
: std::false_type
{};
95 #if !FMT_MSC_VERSION || FMT_MSC_VERSION > 1800
97 # define FMT_DECLTYPE_RETURN(val) \
98 ->decltype(val) { return val; } \
100 true, "") // This makes it so that a semicolon is required after the
101 // macro, which helps clang-format handle the formatting.
104 template <typename T
, std::size_t N
>
105 auto range_begin(const T (&arr
)[N
]) -> const T
* {
108 template <typename T
, std::size_t N
>
109 auto range_end(const T (&arr
)[N
]) -> const T
* {
113 template <typename T
, typename Enable
= void>
114 struct has_member_fn_begin_end_t
: std::false_type
{};
116 template <typename T
>
117 struct has_member_fn_begin_end_t
<T
, void_t
<decltype(std::declval
<T
>().begin()),
118 decltype(std::declval
<T
>().end())>>
121 // Member function overload
122 template <typename T
>
123 auto range_begin(T
&& rng
) FMT_DECLTYPE_RETURN(static_cast<T
&&>(rng
).begin());
124 template <typename T
>
125 auto range_end(T
&& rng
) FMT_DECLTYPE_RETURN(static_cast<T
&&>(rng
).end());
127 // ADL overload. Only participates in overload resolution if member functions
129 template <typename T
>
130 auto range_begin(T
&& rng
)
131 -> enable_if_t
<!has_member_fn_begin_end_t
<T
&&>::value
,
132 decltype(begin(static_cast<T
&&>(rng
)))> {
133 return begin(static_cast<T
&&>(rng
));
135 template <typename T
>
136 auto range_end(T
&& rng
) -> enable_if_t
<!has_member_fn_begin_end_t
<T
&&>::value
,
137 decltype(end(static_cast<T
&&>(rng
)))> {
138 return end(static_cast<T
&&>(rng
));
141 template <typename T
, typename Enable
= void>
142 struct has_const_begin_end
: std::false_type
{};
143 template <typename T
, typename Enable
= void>
144 struct has_mutable_begin_end
: std::false_type
{};
146 template <typename T
>
147 struct has_const_begin_end
<
150 decltype(detail::range_begin(std::declval
<const remove_cvref_t
<T
>&>())),
151 decltype(detail::range_end(std::declval
<const remove_cvref_t
<T
>&>()))>>
154 template <typename T
>
155 struct has_mutable_begin_end
<
156 T
, void_t
<decltype(detail::range_begin(std::declval
<T
>())),
157 decltype(detail::range_end(std::declval
<T
>())),
158 // the extra int here is because older versions of MSVC don't
159 // SFINAE properly unless there are distinct types
160 int>> : std::true_type
{};
162 template <typename T
>
163 struct is_range_
<T
, void>
164 : std::integral_constant
<bool, (has_const_begin_end
<T
>::value
||
165 has_mutable_begin_end
<T
>::value
)> {};
166 # undef FMT_DECLTYPE_RETURN
169 // tuple_size and tuple_element check.
170 template <typename T
> class is_tuple_like_
{
171 template <typename U
>
172 static auto check(U
* p
) -> decltype(std::tuple_size
<U
>::value
, int());
173 template <typename
> static void check(...);
176 static constexpr const bool value
=
177 !std::is_void
<decltype(check
<T
>(nullptr))>::value
;
180 // Check for integer_sequence
181 #if defined(__cpp_lib_integer_sequence) || FMT_MSC_VERSION >= 1900
182 template <typename T
, T
... N
>
183 using integer_sequence
= std::integer_sequence
<T
, N
...>;
184 template <size_t... N
> using index_sequence
= std::index_sequence
<N
...>;
185 template <size_t N
> using make_index_sequence
= std::make_index_sequence
<N
>;
187 template <typename T
, T
... N
> struct integer_sequence
{
188 using value_type
= T
;
190 static FMT_CONSTEXPR
size_t size() { return sizeof...(N
); }
193 template <size_t... N
> using index_sequence
= integer_sequence
<size_t, N
...>;
195 template <typename T
, size_t N
, T
... Ns
>
196 struct make_integer_sequence
: make_integer_sequence
<T
, N
- 1, N
- 1, Ns
...> {};
197 template <typename T
, T
... Ns
>
198 struct make_integer_sequence
<T
, 0, Ns
...> : integer_sequence
<T
, Ns
...> {};
201 using make_index_sequence
= make_integer_sequence
<size_t, N
>;
204 template <typename T
>
205 using tuple_index_sequence
= make_index_sequence
<std::tuple_size
<T
>::value
>;
207 template <typename T
, typename C
, bool = is_tuple_like_
<T
>::value
>
208 class is_tuple_formattable_
{
210 static constexpr const bool value
= false;
212 template <typename T
, typename C
> class is_tuple_formattable_
<T
, C
, true> {
213 template <std::size_t... Is
>
214 static std::true_type
check2(index_sequence
<Is
...>,
215 integer_sequence
<bool, (Is
== Is
)...>);
216 static std::false_type
check2(...);
217 template <std::size_t... Is
>
218 static decltype(check2(
219 index_sequence
<Is
...>{},
221 bool, (is_formattable
<typename
std::tuple_element
<Is
, T
>::type
,
222 C
>::value
)...>{})) check(index_sequence
<Is
...>);
225 static constexpr const bool value
=
226 decltype(check(tuple_index_sequence
<T
>{}))::value
;
229 template <typename Tuple
, typename F
, size_t... Is
>
230 FMT_CONSTEXPR
void for_each(index_sequence
<Is
...>, Tuple
&& t
, F
&& f
) {
232 // Using a free function get<Is>(Tuple) now.
233 const int unused
[] = {0, ((void)f(get
<Is
>(t
)), 0)...};
234 ignore_unused(unused
);
237 template <typename Tuple
, typename F
>
238 FMT_CONSTEXPR
void for_each(Tuple
&& t
, F
&& f
) {
239 for_each(tuple_index_sequence
<remove_cvref_t
<Tuple
>>(),
240 std::forward
<Tuple
>(t
), std::forward
<F
>(f
));
243 template <typename Tuple1
, typename Tuple2
, typename F
, size_t... Is
>
244 void for_each2(index_sequence
<Is
...>, Tuple1
&& t1
, Tuple2
&& t2
, F
&& f
) {
246 const int unused
[] = {0, ((void)f(get
<Is
>(t1
), get
<Is
>(t2
)), 0)...};
247 ignore_unused(unused
);
250 template <typename Tuple1
, typename Tuple2
, typename F
>
251 void for_each2(Tuple1
&& t1
, Tuple2
&& t2
, F
&& f
) {
252 for_each2(tuple_index_sequence
<remove_cvref_t
<Tuple1
>>(),
253 std::forward
<Tuple1
>(t1
), std::forward
<Tuple2
>(t2
),
258 // Workaround a bug in MSVC 2019 (v140).
259 template <typename Char
, typename
... T
>
260 using result_t
= std::tuple
<formatter
<remove_cvref_t
<T
>, Char
>...>;
263 template <typename Tuple
, typename Char
, std::size_t... Is
>
264 auto get_formatters(index_sequence
<Is
...>)
265 -> result_t
<Char
, decltype(get
<Is
>(std::declval
<Tuple
>()))...>;
268 #if FMT_MSC_VERSION && FMT_MSC_VERSION < 1920
269 // Older MSVC doesn't get the reference type correctly for arrays.
270 template <typename R
> struct range_reference_type_impl
{
271 using type
= decltype(*detail::range_begin(std::declval
<R
&>()));
274 template <typename T
, std::size_t N
> struct range_reference_type_impl
<T
[N
]> {
278 template <typename T
>
279 using range_reference_type
= typename range_reference_type_impl
<T
>::type
;
281 template <typename Range
>
282 using range_reference_type
=
283 decltype(*detail::range_begin(std::declval
<Range
&>()));
286 // We don't use the Range's value_type for anything, but we do need the Range's
287 // reference type, with cv-ref stripped.
288 template <typename Range
>
289 using uncvref_type
= remove_cvref_t
<range_reference_type
<Range
>>;
291 template <typename Formatter
>
292 FMT_CONSTEXPR
auto maybe_set_debug_format(Formatter
& f
, bool set
)
293 -> decltype(f
.set_debug_format(set
)) {
294 f
.set_debug_format(set
);
296 template <typename Formatter
>
297 FMT_CONSTEXPR
void maybe_set_debug_format(Formatter
&, ...) {}
299 // These are not generic lambdas for compatibility with C++11.
300 template <typename ParseContext
> struct parse_empty_specs
{
301 template <typename Formatter
> FMT_CONSTEXPR
void operator()(Formatter
& f
) {
303 detail::maybe_set_debug_format(f
, true);
307 template <typename FormatContext
> struct format_tuple_element
{
308 using char_type
= typename
FormatContext::char_type
;
310 template <typename T
>
311 void operator()(const formatter
<T
, char_type
>& f
, const T
& v
) {
313 ctx
.advance_to(detail::copy_str
<char_type
>(separator
, ctx
.out()));
314 ctx
.advance_to(f
.format(v
, ctx
));
320 basic_string_view
<char_type
> separator
;
323 } // namespace detail
325 template <typename T
> struct is_tuple_like
{
326 static constexpr const bool value
=
327 detail::is_tuple_like_
<T
>::value
&& !detail::is_range_
<T
>::value
;
330 template <typename T
, typename C
> struct is_tuple_formattable
{
331 static constexpr const bool value
=
332 detail::is_tuple_formattable_
<T
, C
>::value
;
335 template <typename Tuple
, typename Char
>
336 struct formatter
<Tuple
, Char
,
337 enable_if_t
<fmt::is_tuple_like
<Tuple
>::value
&&
338 fmt::is_tuple_formattable
<Tuple
, Char
>::value
>> {
340 decltype(detail::tuple::get_formatters
<Tuple
, Char
>(
341 detail::tuple_index_sequence
<Tuple
>())) formatters_
;
343 basic_string_view
<Char
> separator_
= detail::string_literal
<Char
, ',', ' '>{};
344 basic_string_view
<Char
> opening_bracket_
=
345 detail::string_literal
<Char
, '('>{};
346 basic_string_view
<Char
> closing_bracket_
=
347 detail::string_literal
<Char
, ')'>{};
350 FMT_CONSTEXPR
formatter() {}
352 FMT_CONSTEXPR
void set_separator(basic_string_view
<Char
> sep
) {
356 FMT_CONSTEXPR
void set_brackets(basic_string_view
<Char
> open
,
357 basic_string_view
<Char
> close
) {
358 opening_bracket_
= open
;
359 closing_bracket_
= close
;
362 template <typename ParseContext
>
363 FMT_CONSTEXPR
auto parse(ParseContext
& ctx
) -> decltype(ctx
.begin()) {
364 auto it
= ctx
.begin();
365 if (it
!= ctx
.end() && *it
!= '}')
366 FMT_THROW(format_error("invalid format specifier"));
367 detail::for_each(formatters_
, detail::parse_empty_specs
<ParseContext
>{ctx
});
371 template <typename FormatContext
>
372 auto format(const Tuple
& value
, FormatContext
& ctx
) const
373 -> decltype(ctx
.out()) {
374 ctx
.advance_to(detail::copy_str
<Char
>(opening_bracket_
, ctx
.out()));
377 detail::format_tuple_element
<FormatContext
>{0, ctx
, separator_
});
378 return detail::copy_str
<Char
>(closing_bracket_
, ctx
.out());
382 template <typename T
, typename Char
> struct is_range
{
383 static constexpr const bool value
=
384 detail::is_range_
<T
>::value
&& !detail::is_std_string_like
<T
>::value
&&
385 !std::is_convertible
<T
, std::basic_string
<Char
>>::value
&&
386 !std::is_convertible
<T
, detail::std_string_view
<Char
>>::value
;
390 template <typename Context
> struct range_mapper
{
391 using mapper
= arg_mapper
<Context
>;
393 template <typename T
,
394 FMT_ENABLE_IF(has_formatter
<remove_cvref_t
<T
>, Context
>::value
)>
395 static auto map(T
&& value
) -> T
&& {
396 return static_cast<T
&&>(value
);
398 template <typename T
,
399 FMT_ENABLE_IF(!has_formatter
<remove_cvref_t
<T
>, Context
>::value
)>
400 static auto map(T
&& value
)
401 -> decltype(mapper().map(static_cast<T
&&>(value
))) {
402 return mapper().map(static_cast<T
&&>(value
));
406 template <typename Char
, typename Element
>
407 using range_formatter_type
=
408 formatter
<remove_cvref_t
<decltype(range_mapper
<buffer_context
<Char
>>{}.map(
409 std::declval
<Element
>()))>,
412 template <typename R
>
413 using maybe_const_range
=
414 conditional_t
<has_const_begin_end
<R
>::value
, const R
, R
>;
416 // Workaround a bug in MSVC 2015 and earlier.
417 #if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
418 template <typename R
, typename Char
>
419 struct is_formattable_delayed
420 : is_formattable
<uncvref_type
<maybe_const_range
<R
>>, Char
> {};
422 } // namespace detail
424 template <typename T
, typename Char
, typename Enable
= void>
425 struct range_formatter
;
427 template <typename T
, typename Char
>
428 struct range_formatter
<
430 enable_if_t
<conjunction
<std::is_same
<T
, remove_cvref_t
<T
>>,
431 is_formattable
<T
, Char
>>::value
>> {
433 detail::range_formatter_type
<Char
, T
> underlying_
;
434 basic_string_view
<Char
> separator_
= detail::string_literal
<Char
, ',', ' '>{};
435 basic_string_view
<Char
> opening_bracket_
=
436 detail::string_literal
<Char
, '['>{};
437 basic_string_view
<Char
> closing_bracket_
=
438 detail::string_literal
<Char
, ']'>{};
441 FMT_CONSTEXPR
range_formatter() {}
443 FMT_CONSTEXPR
auto underlying() -> detail::range_formatter_type
<Char
, T
>& {
447 FMT_CONSTEXPR
void set_separator(basic_string_view
<Char
> sep
) {
451 FMT_CONSTEXPR
void set_brackets(basic_string_view
<Char
> open
,
452 basic_string_view
<Char
> close
) {
453 opening_bracket_
= open
;
454 closing_bracket_
= close
;
457 template <typename ParseContext
>
458 FMT_CONSTEXPR
auto parse(ParseContext
& ctx
) -> decltype(ctx
.begin()) {
459 auto it
= ctx
.begin();
460 auto end
= ctx
.end();
462 if (it
!= end
&& *it
== 'n') {
463 set_brackets({}, {});
467 if (it
!= end
&& *it
!= '}') {
468 if (*it
!= ':') FMT_THROW(format_error("invalid format specifier"));
471 detail::maybe_set_debug_format(underlying_
, true);
475 return underlying_
.parse(ctx
);
478 template <typename R
, typename FormatContext
>
479 auto format(R
&& range
, FormatContext
& ctx
) const -> decltype(ctx
.out()) {
480 detail::range_mapper
<buffer_context
<Char
>> mapper
;
481 auto out
= ctx
.out();
482 out
= detail::copy_str
<Char
>(opening_bracket_
, out
);
484 auto it
= detail::range_begin(range
);
485 auto end
= detail::range_end(range
);
486 for (; it
!= end
; ++it
) {
487 if (i
> 0) out
= detail::copy_str
<Char
>(separator_
, out
);
489 out
= underlying_
.format(mapper
.map(*it
), ctx
);
492 out
= detail::copy_str
<Char
>(closing_bracket_
, out
);
497 enum class range_format
{ disabled
, map
, set
, sequence
, string
, debug_string
};
500 template <typename T
>
501 struct range_format_kind_
502 : std::integral_constant
<range_format
,
503 std::is_same
<uncvref_type
<T
>, T
>::value
504 ? range_format::disabled
505 : is_map
<T
>::value
? range_format::map
506 : is_set
<T
>::value
? range_format::set
507 : range_format::sequence
> {};
509 template <range_format K
, typename R
, typename Char
, typename Enable
= void>
510 struct range_default_formatter
;
512 template <range_format K
>
513 using range_format_constant
= std::integral_constant
<range_format
, K
>;
515 template <range_format K
, typename R
, typename Char
>
516 struct range_default_formatter
<
518 enable_if_t
<(K
== range_format::sequence
|| K
== range_format::map
||
519 K
== range_format::set
)>> {
520 using range_type
= detail::maybe_const_range
<R
>;
521 range_formatter
<detail::uncvref_type
<range_type
>, Char
> underlying_
;
523 FMT_CONSTEXPR
range_default_formatter() { init(range_format_constant
<K
>()); }
525 FMT_CONSTEXPR
void init(range_format_constant
<range_format::set
>) {
526 underlying_
.set_brackets(detail::string_literal
<Char
, '{'>{},
527 detail::string_literal
<Char
, '}'>{});
530 FMT_CONSTEXPR
void init(range_format_constant
<range_format::map
>) {
531 underlying_
.set_brackets(detail::string_literal
<Char
, '{'>{},
532 detail::string_literal
<Char
, '}'>{});
533 underlying_
.underlying().set_brackets({}, {});
534 underlying_
.underlying().set_separator(
535 detail::string_literal
<Char
, ':', ' '>{});
538 FMT_CONSTEXPR
void init(range_format_constant
<range_format::sequence
>) {}
540 template <typename ParseContext
>
541 FMT_CONSTEXPR
auto parse(ParseContext
& ctx
) -> decltype(ctx
.begin()) {
542 return underlying_
.parse(ctx
);
545 template <typename FormatContext
>
546 auto format(range_type
& range
, FormatContext
& ctx
) const
547 -> decltype(ctx
.out()) {
548 return underlying_
.format(range
, ctx
);
551 } // namespace detail
553 template <typename T
, typename Char
, typename Enable
= void>
554 struct range_format_kind
556 is_range
<T
, Char
>::value
, detail::range_format_kind_
<T
>,
557 std::integral_constant
<range_format
, range_format::disabled
>> {};
559 template <typename R
, typename Char
>
562 enable_if_t
<conjunction
<bool_constant
<range_format_kind
<R
, Char
>::value
!=
563 range_format::disabled
>
564 // Workaround a bug in MSVC 2015 and earlier.
565 #if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
567 detail::is_formattable_delayed
<R
, Char
>
570 : detail::range_default_formatter
<range_format_kind
<R
, Char
>::value
, R
,
574 template <typename Char
, typename
... T
> struct tuple_join_view
: detail::view
{
575 const std::tuple
<T
...>& tuple
;
576 basic_string_view
<Char
> sep
;
578 tuple_join_view(const std::tuple
<T
...>& t
, basic_string_view
<Char
> s
)
579 : tuple(t
), sep
{s
} {}
582 // Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers
583 // support in tuple_join. It is disabled by default because of issues with
584 // the dynamic width and precision.
585 #ifndef FMT_TUPLE_JOIN_SPECIFIERS
586 # define FMT_TUPLE_JOIN_SPECIFIERS 0
589 template <typename Char
, typename
... T
>
590 struct formatter
<tuple_join_view
<Char
, T
...>, Char
> {
591 template <typename ParseContext
>
592 FMT_CONSTEXPR
auto parse(ParseContext
& ctx
) -> decltype(ctx
.begin()) {
593 return do_parse(ctx
, std::integral_constant
<size_t, sizeof...(T
)>());
596 template <typename FormatContext
>
597 auto format(const tuple_join_view
<Char
, T
...>& value
,
598 FormatContext
& ctx
) const -> typename
FormatContext::iterator
{
599 return do_format(value
, ctx
,
600 std::integral_constant
<size_t, sizeof...(T
)>());
604 std::tuple
<formatter
<typename
std::decay
<T
>::type
, Char
>...> formatters_
;
606 template <typename ParseContext
>
607 FMT_CONSTEXPR
auto do_parse(ParseContext
& ctx
,
608 std::integral_constant
<size_t, 0>)
609 -> decltype(ctx
.begin()) {
613 template <typename ParseContext
, size_t N
>
614 FMT_CONSTEXPR
auto do_parse(ParseContext
& ctx
,
615 std::integral_constant
<size_t, N
>)
616 -> decltype(ctx
.begin()) {
617 auto end
= ctx
.begin();
618 #if FMT_TUPLE_JOIN_SPECIFIERS
619 end
= std::get
<sizeof...(T
) - N
>(formatters_
).parse(ctx
);
621 auto end1
= do_parse(ctx
, std::integral_constant
<size_t, N
- 1>());
623 FMT_THROW(format_error("incompatible format specs for tuple elements"));
629 template <typename FormatContext
>
630 auto do_format(const tuple_join_view
<Char
, T
...>&, FormatContext
& ctx
,
631 std::integral_constant
<size_t, 0>) const ->
632 typename
FormatContext::iterator
{
636 template <typename FormatContext
, size_t N
>
637 auto do_format(const tuple_join_view
<Char
, T
...>& value
, FormatContext
& ctx
,
638 std::integral_constant
<size_t, N
>) const ->
639 typename
FormatContext::iterator
{
640 auto out
= std::get
<sizeof...(T
) - N
>(formatters_
)
641 .format(std::get
<sizeof...(T
) - N
>(value
.tuple
), ctx
);
643 out
= std::copy(value
.sep
.begin(), value
.sep
.end(), out
);
645 return do_format(value
, ctx
, std::integral_constant
<size_t, N
- 1>());
652 // Check if T has an interface like a container adaptor (e.g. std::stack,
653 // std::queue, std::priority_queue).
654 template <typename T
> class is_container_adaptor_like
{
655 template <typename U
> static auto check(U
* p
) -> typename
U::container_type
;
656 template <typename
> static void check(...);
659 static constexpr const bool value
=
660 !std::is_void
<decltype(check
<T
>(nullptr))>::value
;
663 template <typename Container
> struct all
{
665 auto begin() const -> typename
Container::const_iterator
{ return c
.begin(); }
666 auto end() const -> typename
Container::const_iterator
{ return c
.end(); }
668 } // namespace detail
670 template <typename T
, typename Char
>
673 enable_if_t
<conjunction
<detail::is_container_adaptor_like
<T
>,
674 bool_constant
<range_format_kind
<T
, Char
>::value
==
675 range_format::disabled
>>::value
>>
676 : formatter
<detail::all
<typename
T::container_type
>, Char
> {
677 using all
= detail::all
<typename
T::container_type
>;
678 template <typename FormatContext
>
679 auto format(const T
& t
, FormatContext
& ctx
) const -> decltype(ctx
.out()) {
681 static auto get(const T
& t
) -> all
{
682 return {t
.*(&getter::c
)}; // Access c through the derived class.
685 return formatter
<all
>::format(getter::get(t
), ctx
);
693 Returns an object that formats `tuple` with elements separated by `sep`.
697 std::tuple<int, char> t = {1, 'a'};
698 fmt::print("{}", fmt::join(t, ", "));
702 template <typename
... T
>
703 FMT_CONSTEXPR
auto join(const std::tuple
<T
...>& tuple
, string_view sep
)
704 -> tuple_join_view
<char, T
...> {
708 template <typename
... T
>
709 FMT_CONSTEXPR
auto join(const std::tuple
<T
...>& tuple
,
710 basic_string_view
<wchar_t> sep
)
711 -> tuple_join_view
<wchar_t, T
...> {
717 Returns an object that formats `initializer_list` with elements separated by
722 fmt::print("{}", fmt::join({1, 2, 3}, ", "));
726 template <typename T
>
727 auto join(std::initializer_list
<T
> list
, string_view sep
)
728 -> join_view
<const T
*, const T
*> {
729 return join(std::begin(list
), std::end(list
), sep
);
735 #endif // FMT_RANGES_H_