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 RangeT
, typename OutputIterator
>
26 OutputIterator
copy(const RangeT
& range
, OutputIterator out
) {
27 for (auto it
= range
.begin(), end
= range
.end(); it
!= end
; ++it
)
32 template <typename OutputIterator
>
33 OutputIterator
copy(const char* str
, OutputIterator out
) {
34 while (*str
) *out
++ = *str
++;
38 template <typename OutputIterator
>
39 OutputIterator
copy(char ch
, OutputIterator out
) {
44 template <typename OutputIterator
>
45 OutputIterator
copy(wchar_t ch
, OutputIterator out
) {
50 // Returns true if T has a std::string-like interface, like std::string_view.
51 template <typename T
> class is_std_string_like
{
53 static auto check(U
* p
)
54 -> decltype((void)p
->find('a'), p
->length(), (void)p
->data(), int());
55 template <typename
> static void check(...);
58 static constexpr const bool value
=
59 is_string
<T
>::value
||
60 std::is_convertible
<T
, std_string_view
<char>>::value
||
61 !std::is_void
<decltype(check
<T
>(nullptr))>::value
;
64 template <typename Char
>
65 struct is_std_string_like
<fmt::basic_string_view
<Char
>> : std::true_type
{};
67 template <typename T
> class is_map
{
68 template <typename U
> static auto check(U
*) -> typename
U::mapped_type
;
69 template <typename
> static void check(...);
72 #ifdef FMT_FORMAT_MAP_AS_LIST
73 static constexpr const bool value
= false;
75 static constexpr const bool value
=
76 !std::is_void
<decltype(check
<T
>(nullptr))>::value
;
80 template <typename T
> class is_set
{
81 template <typename U
> static auto check(U
*) -> typename
U::key_type
;
82 template <typename
> static void check(...);
85 #ifdef FMT_FORMAT_SET_AS_LIST
86 static constexpr const bool value
= false;
88 static constexpr const bool value
=
89 !std::is_void
<decltype(check
<T
>(nullptr))>::value
&& !is_map
<T
>::value
;
93 template <typename
... Ts
> struct conditional_helper
{};
95 template <typename T
, typename _
= void> struct is_range_
: std::false_type
{};
97 #if !FMT_MSC_VERSION || FMT_MSC_VERSION > 1800
99 # define FMT_DECLTYPE_RETURN(val) \
100 ->decltype(val) { return val; } \
102 true, "") // This makes it so that a semicolon is required after the
103 // macro, which helps clang-format handle the formatting.
106 template <typename T
, std::size_t N
>
107 auto range_begin(const T (&arr
)[N
]) -> const T
* {
110 template <typename T
, std::size_t N
>
111 auto range_end(const T (&arr
)[N
]) -> const T
* {
115 template <typename T
, typename Enable
= void>
116 struct has_member_fn_begin_end_t
: std::false_type
{};
118 template <typename T
>
119 struct has_member_fn_begin_end_t
<T
, void_t
<decltype(std::declval
<T
>().begin()),
120 decltype(std::declval
<T
>().end())>>
123 // Member function overload
124 template <typename T
>
125 auto range_begin(T
&& rng
) FMT_DECLTYPE_RETURN(static_cast<T
&&>(rng
).begin());
126 template <typename T
>
127 auto range_end(T
&& rng
) FMT_DECLTYPE_RETURN(static_cast<T
&&>(rng
).end());
129 // ADL overload. Only participates in overload resolution if member functions
131 template <typename T
>
132 auto range_begin(T
&& rng
)
133 -> enable_if_t
<!has_member_fn_begin_end_t
<T
&&>::value
,
134 decltype(begin(static_cast<T
&&>(rng
)))> {
135 return begin(static_cast<T
&&>(rng
));
137 template <typename T
>
138 auto range_end(T
&& rng
) -> enable_if_t
<!has_member_fn_begin_end_t
<T
&&>::value
,
139 decltype(end(static_cast<T
&&>(rng
)))> {
140 return end(static_cast<T
&&>(rng
));
143 template <typename T
, typename Enable
= void>
144 struct has_const_begin_end
: std::false_type
{};
145 template <typename T
, typename Enable
= void>
146 struct has_mutable_begin_end
: std::false_type
{};
148 template <typename T
>
149 struct has_const_begin_end
<
152 decltype(detail::range_begin(std::declval
<const remove_cvref_t
<T
>&>())),
153 decltype(detail::range_end(std::declval
<const remove_cvref_t
<T
>&>()))>>
156 template <typename T
>
157 struct has_mutable_begin_end
<
158 T
, void_t
<decltype(detail::range_begin(std::declval
<T
>())),
159 decltype(detail::range_end(std::declval
<T
>())),
160 enable_if_t
<std::is_copy_constructible
<T
>::value
>>>
163 template <typename T
>
164 struct is_range_
<T
, void>
165 : std::integral_constant
<bool, (has_const_begin_end
<T
>::value
||
166 has_mutable_begin_end
<T
>::value
)> {};
167 # undef FMT_DECLTYPE_RETURN
170 // tuple_size and tuple_element check.
171 template <typename T
> class is_tuple_like_
{
172 template <typename U
>
173 static auto check(U
* p
) -> decltype(std::tuple_size
<U
>::value
, int());
174 template <typename
> static void check(...);
177 static constexpr const bool value
=
178 !std::is_void
<decltype(check
<T
>(nullptr))>::value
;
181 // Check for integer_sequence
182 #if defined(__cpp_lib_integer_sequence) || FMT_MSC_VERSION >= 1900
183 template <typename T
, T
... N
>
184 using integer_sequence
= std::integer_sequence
<T
, N
...>;
185 template <size_t... N
> using index_sequence
= std::index_sequence
<N
...>;
186 template <size_t N
> using make_index_sequence
= std::make_index_sequence
<N
>;
188 template <typename T
, T
... N
> struct integer_sequence
{
189 using value_type
= T
;
191 static FMT_CONSTEXPR
size_t size() { return sizeof...(N
); }
194 template <size_t... N
> using index_sequence
= integer_sequence
<size_t, N
...>;
196 template <typename T
, size_t N
, T
... Ns
>
197 struct make_integer_sequence
: make_integer_sequence
<T
, N
- 1, N
- 1, Ns
...> {};
198 template <typename T
, T
... Ns
>
199 struct make_integer_sequence
<T
, 0, Ns
...> : integer_sequence
<T
, Ns
...> {};
202 using make_index_sequence
= make_integer_sequence
<size_t, N
>;
205 template <typename T
>
206 using tuple_index_sequence
= make_index_sequence
<std::tuple_size
<T
>::value
>;
208 template <typename T
, typename C
, bool = is_tuple_like_
<T
>::value
>
209 class is_tuple_formattable_
{
211 static constexpr const bool value
= false;
213 template <typename T
, typename C
> class is_tuple_formattable_
<T
, C
, true> {
214 template <std::size_t... I
>
215 static std::true_type
check2(index_sequence
<I
...>,
216 integer_sequence
<bool, (I
== I
)...>);
217 static std::false_type
check2(...);
218 template <std::size_t... I
>
219 static decltype(check2(
220 index_sequence
<I
...>{},
222 bool, (is_formattable
<typename
std::tuple_element
<I
, T
>::type
,
223 C
>::value
)...>{})) check(index_sequence
<I
...>);
226 static constexpr const bool value
=
227 decltype(check(tuple_index_sequence
<T
>{}))::value
;
230 template <class Tuple
, class F
, size_t... Is
>
231 void for_each(index_sequence
<Is
...>, Tuple
&& tup
, F
&& f
) noexcept
{
233 // using free function get<I>(T) now.
234 const int _
[] = {0, ((void)f(get
<Is
>(tup
)), 0)...};
235 (void)_
; // blocks warnings
239 FMT_CONSTEXPR make_index_sequence
<std::tuple_size
<T
>::value
> get_indexes(
244 template <class Tuple
, class F
> void for_each(Tuple
&& tup
, F
&& f
) {
245 const auto indexes
= get_indexes(tup
);
246 for_each(indexes
, std::forward
<Tuple
>(tup
), std::forward
<F
>(f
));
249 #if FMT_MSC_VERSION && FMT_MSC_VERSION < 1920
250 // Older MSVC doesn't get the reference type correctly for arrays.
251 template <typename R
> struct range_reference_type_impl
{
252 using type
= decltype(*detail::range_begin(std::declval
<R
&>()));
255 template <typename T
, std::size_t N
> struct range_reference_type_impl
<T
[N
]> {
259 template <typename T
>
260 using range_reference_type
= typename range_reference_type_impl
<T
>::type
;
262 template <typename Range
>
263 using range_reference_type
=
264 decltype(*detail::range_begin(std::declval
<Range
&>()));
267 // We don't use the Range's value_type for anything, but we do need the Range's
268 // reference type, with cv-ref stripped.
269 template <typename Range
>
270 using uncvref_type
= remove_cvref_t
<range_reference_type
<Range
>>;
272 template <typename Range
>
273 using uncvref_first_type
=
274 remove_cvref_t
<decltype(std::declval
<range_reference_type
<Range
>>().first
)>;
276 template <typename Range
>
277 using uncvref_second_type
= remove_cvref_t
<
278 decltype(std::declval
<range_reference_type
<Range
>>().second
)>;
280 template <typename OutputIt
> OutputIt
write_delimiter(OutputIt out
) {
286 template <typename Char
, typename OutputIt
>
287 auto write_range_entry(OutputIt out
, basic_string_view
<Char
> str
) -> OutputIt
{
288 return write_escaped_string(out
, str
);
291 template <typename Char
, typename OutputIt
, typename T
,
292 FMT_ENABLE_IF(std::is_convertible
<T
, std_string_view
<char>>::value
)>
293 inline auto write_range_entry(OutputIt out
, const T
& str
) -> OutputIt
{
294 auto sv
= std_string_view
<Char
>(str
);
295 return write_range_entry
<Char
>(out
, basic_string_view
<Char
>(sv
));
298 template <typename Char
, typename OutputIt
, typename Arg
,
299 FMT_ENABLE_IF(std::is_same
<Arg
, Char
>::value
)>
300 OutputIt
write_range_entry(OutputIt out
, const Arg v
) {
301 return write_escaped_char(out
, v
);
305 typename Char
, typename OutputIt
, typename Arg
,
306 FMT_ENABLE_IF(!is_std_string_like
<typename
std::decay
<Arg
>::type
>::value
&&
307 !std::is_same
<Arg
, Char
>::value
)>
308 OutputIt
write_range_entry(OutputIt out
, const Arg
& v
) {
309 return write
<Char
>(out
, v
);
312 } // namespace detail
314 template <typename T
> struct is_tuple_like
{
315 static constexpr const bool value
=
316 detail::is_tuple_like_
<T
>::value
&& !detail::is_range_
<T
>::value
;
319 template <typename T
, typename C
> struct is_tuple_formattable
{
320 static constexpr const bool value
=
321 detail::is_tuple_formattable_
<T
, C
>::value
;
324 template <typename TupleT
, typename Char
>
325 struct formatter
<TupleT
, Char
,
326 enable_if_t
<fmt::is_tuple_like
<TupleT
>::value
&&
327 fmt::is_tuple_formattable
<TupleT
, Char
>::value
>> {
329 basic_string_view
<Char
> separator_
= detail::string_literal
<Char
, ',', ' '>{};
330 basic_string_view
<Char
> opening_bracket_
=
331 detail::string_literal
<Char
, '('>{};
332 basic_string_view
<Char
> closing_bracket_
=
333 detail::string_literal
<Char
, ')'>{};
335 // C++11 generic lambda for format().
336 template <typename FormatContext
> struct format_each
{
337 template <typename T
> void operator()(const T
& v
) {
338 if (i
> 0) out
= detail::copy_str
<Char
>(separator
, out
);
339 out
= detail::write_range_entry
<Char
>(out
, v
);
343 typename
FormatContext::iterator
& out
;
344 basic_string_view
<Char
> separator
;
348 FMT_CONSTEXPR
formatter() {}
350 FMT_CONSTEXPR
void set_separator(basic_string_view
<Char
> sep
) {
354 FMT_CONSTEXPR
void set_brackets(basic_string_view
<Char
> open
,
355 basic_string_view
<Char
> close
) {
356 opening_bracket_
= open
;
357 closing_bracket_
= close
;
360 template <typename ParseContext
>
361 FMT_CONSTEXPR
auto parse(ParseContext
& ctx
) -> decltype(ctx
.begin()) {
365 template <typename FormatContext
= format_context
>
366 auto format(const TupleT
& values
, FormatContext
& ctx
) const
367 -> decltype(ctx
.out()) {
368 auto out
= ctx
.out();
369 out
= detail::copy_str
<Char
>(opening_bracket_
, out
);
370 detail::for_each(values
, format_each
<FormatContext
>{0, out
, separator_
});
371 out
= detail::copy_str
<Char
>(closing_bracket_
, out
);
376 template <typename T
, typename Char
> struct is_range
{
377 static constexpr const bool value
=
378 detail::is_range_
<T
>::value
&& !detail::is_std_string_like
<T
>::value
&&
379 !std::is_convertible
<T
, std::basic_string
<Char
>>::value
&&
380 !std::is_convertible
<T
, detail::std_string_view
<Char
>>::value
;
384 template <typename Context
> struct range_mapper
{
385 using mapper
= arg_mapper
<Context
>;
387 template <typename T
,
388 FMT_ENABLE_IF(has_formatter
<remove_cvref_t
<T
>, Context
>::value
)>
389 static auto map(T
&& value
) -> T
&& {
390 return static_cast<T
&&>(value
);
392 template <typename T
,
393 FMT_ENABLE_IF(!has_formatter
<remove_cvref_t
<T
>, Context
>::value
)>
394 static auto map(T
&& value
)
395 -> decltype(mapper().map(static_cast<T
&&>(value
))) {
396 return mapper().map(static_cast<T
&&>(value
));
400 template <typename Char
, typename Element
>
401 using range_formatter_type
= conditional_t
<
402 is_formattable
<Element
, Char
>::value
,
403 formatter
<remove_cvref_t
<decltype(range_mapper
<buffer_context
<Char
>>{}.map(
404 std::declval
<Element
>()))>,
406 fallback_formatter
<Element
, Char
>>;
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
417 is_formattable
<uncvref_type
<maybe_const_range
<R
>>, Char
>,
418 has_fallback_formatter
<uncvref_type
<maybe_const_range
<R
>>, Char
>> {};
421 } // namespace detail
423 template <typename T
, typename Char
, typename Enable
= void>
424 struct range_formatter
;
426 template <typename T
, typename Char
>
427 struct range_formatter
<
429 enable_if_t
<conjunction
<
430 std::is_same
<T
, remove_cvref_t
<T
>>,
431 disjunction
<is_formattable
<T
, Char
>,
432 detail::has_fallback_formatter
<T
, Char
>>>::value
>> {
434 detail::range_formatter_type
<Char
, T
> underlying_
;
435 bool custom_specs_
= false;
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
static auto maybe_set_debug_format(U
& u
, int)
444 -> decltype(u
.set_debug_format()) {
445 u
.set_debug_format();
449 FMT_CONSTEXPR
static void maybe_set_debug_format(U
&, ...) {}
451 FMT_CONSTEXPR
void maybe_set_debug_format() {
452 maybe_set_debug_format(underlying_
, 0);
456 FMT_CONSTEXPR
range_formatter() {}
458 FMT_CONSTEXPR
auto underlying() -> detail::range_formatter_type
<Char
, T
>& {
462 FMT_CONSTEXPR
void set_separator(basic_string_view
<Char
> sep
) {
466 FMT_CONSTEXPR
void set_brackets(basic_string_view
<Char
> open
,
467 basic_string_view
<Char
> close
) {
468 opening_bracket_
= open
;
469 closing_bracket_
= close
;
472 template <typename ParseContext
>
473 FMT_CONSTEXPR
auto parse(ParseContext
& ctx
) -> decltype(ctx
.begin()) {
474 auto it
= ctx
.begin();
475 auto end
= ctx
.end();
476 if (it
== end
|| *it
== '}') {
477 maybe_set_debug_format();
482 set_brackets({}, {});
487 maybe_set_debug_format();
492 FMT_THROW(format_error("no other top-level range formatters supported"));
494 custom_specs_
= true;
497 return underlying_
.parse(ctx
);
500 template <typename R
, class FormatContext
>
501 auto format(R
&& range
, FormatContext
& ctx
) const -> decltype(ctx
.out()) {
502 detail::range_mapper
<buffer_context
<Char
>> mapper
;
503 auto out
= ctx
.out();
504 out
= detail::copy_str
<Char
>(opening_bracket_
, out
);
506 auto it
= detail::range_begin(range
);
507 auto end
= detail::range_end(range
);
508 for (; it
!= end
; ++it
) {
509 if (i
> 0) out
= detail::copy_str
<Char
>(separator_
, out
);
512 out
= underlying_
.format(mapper
.map(*it
), ctx
);
515 out
= detail::copy_str
<Char
>(closing_bracket_
, out
);
520 enum class range_format
{ disabled
, map
, set
, sequence
, string
, debug_string
};
523 template <typename T
> struct range_format_kind_
{
524 static constexpr auto value
= std::is_same
<range_reference_type
<T
>, T
>::value
525 ? range_format::disabled
526 : is_map
<T
>::value
? range_format::map
527 : is_set
<T
>::value
? range_format::set
528 : range_format::sequence
;
531 template <range_format K
, typename R
, typename Char
, typename Enable
= void>
532 struct range_default_formatter
;
534 template <range_format K
>
535 using range_format_constant
= std::integral_constant
<range_format
, K
>;
537 template <range_format K
, typename R
, typename Char
>
538 struct range_default_formatter
<
540 enable_if_t
<(K
== range_format::sequence
|| K
== range_format::map
||
541 K
== range_format::set
)>> {
542 using range_type
= detail::maybe_const_range
<R
>;
543 range_formatter
<detail::uncvref_type
<range_type
>, Char
> underlying_
;
545 FMT_CONSTEXPR
range_default_formatter() { init(range_format_constant
<K
>()); }
547 FMT_CONSTEXPR
void init(range_format_constant
<range_format::set
>) {
548 underlying_
.set_brackets(detail::string_literal
<Char
, '{'>{},
549 detail::string_literal
<Char
, '}'>{});
552 FMT_CONSTEXPR
void init(range_format_constant
<range_format::map
>) {
553 underlying_
.set_brackets(detail::string_literal
<Char
, '{'>{},
554 detail::string_literal
<Char
, '}'>{});
555 underlying_
.underlying().set_brackets({}, {});
556 underlying_
.underlying().set_separator(
557 detail::string_literal
<Char
, ':', ' '>{});
560 FMT_CONSTEXPR
void init(range_format_constant
<range_format::sequence
>) {}
562 template <typename ParseContext
>
563 FMT_CONSTEXPR
auto parse(ParseContext
& ctx
) -> decltype(ctx
.begin()) {
564 return underlying_
.parse(ctx
);
567 template <typename FormatContext
>
568 auto format(range_type
& range
, FormatContext
& ctx
) const
569 -> decltype(ctx
.out()) {
570 return underlying_
.format(range
, ctx
);
573 } // namespace detail
575 template <typename T
, typename Char
, typename Enable
= void>
576 struct range_format_kind
578 is_range
<T
, Char
>::value
, detail::range_format_kind_
<T
>,
579 std::integral_constant
<range_format
, range_format::disabled
>> {};
581 template <typename R
, typename Char
>
584 enable_if_t
<conjunction
<bool_constant
<range_format_kind
<R
, Char
>::value
!=
585 range_format::disabled
>
586 // Workaround a bug in MSVC 2015 and earlier.
587 #if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
589 detail::is_formattable_delayed
<R
, Char
>
592 : detail::range_default_formatter
<range_format_kind
<R
, Char
>::value
, R
,
596 template <typename Char
, typename
... T
> struct tuple_join_view
: detail::view
{
597 const std::tuple
<T
...>& tuple
;
598 basic_string_view
<Char
> sep
;
600 tuple_join_view(const std::tuple
<T
...>& t
, basic_string_view
<Char
> s
)
601 : tuple(t
), sep
{s
} {}
604 template <typename Char
, typename
... T
>
605 using tuple_arg_join
= tuple_join_view
<Char
, T
...>;
607 // Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers
608 // support in tuple_join. It is disabled by default because of issues with
609 // the dynamic width and precision.
610 #ifndef FMT_TUPLE_JOIN_SPECIFIERS
611 # define FMT_TUPLE_JOIN_SPECIFIERS 0
614 template <typename Char
, typename
... T
>
615 struct formatter
<tuple_join_view
<Char
, T
...>, Char
> {
616 template <typename ParseContext
>
617 FMT_CONSTEXPR
auto parse(ParseContext
& ctx
) -> decltype(ctx
.begin()) {
618 return do_parse(ctx
, std::integral_constant
<size_t, sizeof...(T
)>());
621 template <typename FormatContext
>
622 auto format(const tuple_join_view
<Char
, T
...>& value
,
623 FormatContext
& ctx
) const -> typename
FormatContext::iterator
{
624 return do_format(value
, ctx
,
625 std::integral_constant
<size_t, sizeof...(T
)>());
629 std::tuple
<formatter
<typename
std::decay
<T
>::type
, Char
>...> formatters_
;
631 template <typename ParseContext
>
632 FMT_CONSTEXPR
auto do_parse(ParseContext
& ctx
,
633 std::integral_constant
<size_t, 0>)
634 -> decltype(ctx
.begin()) {
638 template <typename ParseContext
, size_t N
>
639 FMT_CONSTEXPR
auto do_parse(ParseContext
& ctx
,
640 std::integral_constant
<size_t, N
>)
641 -> decltype(ctx
.begin()) {
642 auto end
= ctx
.begin();
643 #if FMT_TUPLE_JOIN_SPECIFIERS
644 end
= std::get
<sizeof...(T
) - N
>(formatters_
).parse(ctx
);
646 auto end1
= do_parse(ctx
, std::integral_constant
<size_t, N
- 1>());
648 FMT_THROW(format_error("incompatible format specs for tuple elements"));
654 template <typename FormatContext
>
655 auto do_format(const tuple_join_view
<Char
, T
...>&, FormatContext
& ctx
,
656 std::integral_constant
<size_t, 0>) const ->
657 typename
FormatContext::iterator
{
661 template <typename FormatContext
, size_t N
>
662 auto do_format(const tuple_join_view
<Char
, T
...>& value
, FormatContext
& ctx
,
663 std::integral_constant
<size_t, N
>) const ->
664 typename
FormatContext::iterator
{
665 auto out
= std::get
<sizeof...(T
) - N
>(formatters_
)
666 .format(std::get
<sizeof...(T
) - N
>(value
.tuple
), ctx
);
668 out
= std::copy(value
.sep
.begin(), value
.sep
.end(), out
);
670 return do_format(value
, ctx
, std::integral_constant
<size_t, N
- 1>());
676 FMT_MODULE_EXPORT_BEGIN
680 Returns an object that formats `tuple` with elements separated by `sep`.
684 std::tuple<int, char> t = {1, 'a'};
685 fmt::print("{}", fmt::join(t, ", "));
689 template <typename
... T
>
690 FMT_CONSTEXPR
auto join(const std::tuple
<T
...>& tuple
, string_view sep
)
691 -> tuple_join_view
<char, T
...> {
695 template <typename
... T
>
696 FMT_CONSTEXPR
auto join(const std::tuple
<T
...>& tuple
,
697 basic_string_view
<wchar_t> sep
)
698 -> tuple_join_view
<wchar_t, T
...> {
704 Returns an object that formats `initializer_list` with elements separated by
709 fmt::print("{}", fmt::join({1, 2, 3}, ", "));
713 template <typename T
>
714 auto join(std::initializer_list
<T
> list
, string_view sep
)
715 -> join_view
<const T
*, const T
*> {
716 return join(std::begin(list
), std::end(list
), sep
);
719 FMT_MODULE_EXPORT_END
722 #endif // FMT_RANGES_H_