[PATCH v2] libstdc++: Implement formatter for ranges and range_formatter [PR109162]
Jonathan Wakely
jwakely@redhat.com
Tue Apr 15 14:16:54 GMT 2025
On 15/04/25 15:05 +0100, Jonathan Wakely wrote:
>A few spelling and grammar fixes, and whitespace tweaks, but the only
>significant thing is to qualify some calls to prevent ADL ...
Doh, as pointed out in chat the ADL thing is irrelevant because that's
not std::format.
So I think everything else is just spelling or whitespace, and the
double spaces before "range" in the exception strings.
OK for trunk with those minor issues fixed then.
>
>On 14/04/25 16:13 +0200, Tomasz Kamiński wrote:
>>This patch implements formatter specialization for input_ranges and
>>range_formatter class form P2286R8, as adjusted by P2585R1. The formatter
>
>"form" should be "from"
>
>>for pair/tuple is not yet provided, making maps not formattable.
>>
>>To indicate partial support we define __glibcxx_format_ranges macro
>>value 1, without defining __cpp_lib_format_ranges.
>
>That was already pushed in an earlier commit, but this sounds like
>it's done here.
>
>>This introduces an new _M_format_range member to internal __formatter_str,
>>that formats range as _CharT as string, according the to the format spec.
>
>"the to the" should be "to the"
>
>>This function transform any contiguous range into basic_string_view direclty,
>
>"directly"
>
>>by computing size if necessary. Otherwise, for ranges for which size can be
>>computed (forward_range or sized_range) we use a stack buffer, if they are
>>sufficiently small. Finally, we create a basic_string<_CharT> from the range,
>>and format it content.
>
>Should be "its content"
>
>>
>>In case when padding is specified, this is handled by firstly formatting
>>the content of the range to the temporary string object. However, this can be
>>only implemented if the iterator of the basic_format_context is internal
>>type-erased iterator used by implementation. Otherwise a new basic_format_context
>>would need to be created, which would require rebinding of handles stored in
>>the arguments: note that format spec for element type could retrive any format
>
>"retrive" should be "retrieve"
>
>>argument from format context, visit and and user handle to format it.
>
>"and and"
>
>>As basic_format_context provide no user-facing constructor, the user are not able
>
>"the user are not" should be "users are not"
>
>>to cosntructor object of that type with arbitrally iterators.
>
>"cosntructor" should be "construct"
>"arbitrally" should be "arbitrary"
>
>>
>>The signatures of the user-facing parse and format method of the provided
>
>"method" should be "methods"
>
>>formatters deviate from the standard by constraining types of params:
>>* _CharT is constrained __formatter::__char
>>* basic_format_parse_context<_CharT> for parse argument
>>* basic_format_context<_Out, _CharT> for format second argument
>>The standard specifies last three of above as unconstrained types. This types
>
>"This types" should be "These types"
>
>>are later passed to possibly user-provided formatter specializations, that are
>>required via formattable concept to only accept above types.
>>
>>Finally, the formatter<input_range, _CharT> specialization is implemented
>>without using specialization of range-default-formatter exposition only
>>template as base class, while providing same functionality.
>>
>> PR libstdc++/109162
>>
>>libstdc++-v3/ChangeLog:
>>
>> * include/std/format (__format::__has_debug_format, _Pres_type::_Pres_seq)
>> (_Pres_type::_Pres_str, __format::__Stackbuf_size): Define.
>> (_Separators::_S_squares, _Separators::_S_parens, _Separators::_S_comma)
>> (_Separators::_S_colon): Define additional constants.
>> (_Spec::_M_parse_fill_and_align): Define overload accepting
>> list of excluded characters for fill, and forward existing overload.
>> (__formatter_str::_M_format_range): Define.
>> (__format::_Buf_sink) Use __Stackbuf_size for size of array.
>> (__format::__is_map_formattable, std::range_formatter)
>> (std::formatter<_Rg, _CharT>): Define.
>> * src/c++23/std.cc.in (std::format_kind, std::range_format)
>> (std::range_formatter): Export.
>> * testsuite/std/format/formatter/lwg3944.cc: Guarded tests with
>> __glibcxx_format_ranges.
>> * testsuite/std/format/formatter/requirements.cc: Adjusted for standard
>> behavior.
>> * testsuite/23_containers/vector/bool/format.cc: Test vector<bool> formatting.
>> * testsuite/std/format/ranges/format_kind.cc: New test.
>> * testsuite/std/format/ranges/formatter.cc: New test.
>> * testsuite/std/format/ranges/sequence.cc: New test.
>> * testsuite/std/format/ranges/string.cc: New test.
>>---
>>Adjusted the commit message and added test for result of formattable
>>check for ranges of types that are not formattable.
>>
>>libstdc++-v3/include/std/format | 511 ++++++++++++++++--
>>libstdc++-v3/src/c++23/std.cc.in | 6 +
>>.../23_containers/vector/bool/format.cc | 6 +
>>.../testsuite/std/format/formatter/lwg3944.cc | 4 +-
>>.../std/format/formatter/requirements.cc | 14 +-
>>.../std/format/ranges/format_kind.cc | 94 ++++
>>.../testsuite/std/format/ranges/formatter.cc | 145 +++++
>>.../testsuite/std/format/ranges/sequence.cc | 190 +++++++
>>.../testsuite/std/format/ranges/string.cc | 226 ++++++++
>>9 files changed, 1131 insertions(+), 65 deletions(-)
>>create mode 100644 libstdc++-v3/testsuite/std/format/ranges/format_kind.cc
>>create mode 100644 libstdc++-v3/testsuite/std/format/ranges/formatter.cc
>>create mode 100644 libstdc++-v3/testsuite/std/format/ranges/sequence.cc
>>create mode 100644 libstdc++-v3/testsuite/std/format/ranges/string.cc
>>
>>diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format
>>index 23f00970840..e2b02a804e1 100644
>>--- a/libstdc++-v3/include/std/format
>>+++ b/libstdc++-v3/include/std/format
>>@@ -97,6 +97,10 @@ namespace __format
>>#define _GLIBCXX_WIDEN_(C, S) ::std::__format::_Widen<C>(S, L##S)
>>#define _GLIBCXX_WIDEN(S) _GLIBCXX_WIDEN_(_CharT, S)
>>
>>+ // Size for stack located buffer
>>+ template<typename _CharT>
>>+ constexpr size_t __Stackbuf_size = 32 * sizeof(void*) / sizeof(_CharT);
>
>We don't usually use double underscore _and_ uppercase, so this would
>be more conventional as __stackbuf_size.
>
>>+
>> // Type-erased character sinks.
>> template<typename _CharT> class _Sink;
>> template<typename _CharT> class _Fixedbuf_sink;
>>@@ -475,9 +479,10 @@ namespace __format
>> _Pres_d = 1, _Pres_b, _Pres_B, _Pres_o, _Pres_x, _Pres_X, _Pres_c,
>> // Presentation types for floating-point types.
>> _Pres_a = 1, _Pres_A, _Pres_e, _Pres_E, _Pres_f, _Pres_F, _Pres_g, _Pres_G,
>>- _Pres_p = 0, _Pres_P, // For pointers.
>>- _Pres_s = 0, // For strings and bool.
>>- _Pres_esc = 0xf, // For strings and charT.
>>+ _Pres_p = 0, _Pres_P, // For pointers.
>>+ _Pres_s = 0, // For strings, bool
>>+ _Pres_seq = 0, _Pres_str, // For ranges
>>+ _Pres_esc = 0xf, // For strings, charT and ranges
>> };
>>
>> enum _Align {
>>@@ -544,42 +549,48 @@ namespace __format
>> // pre: __first != __last
>> constexpr iterator
>> _M_parse_fill_and_align(iterator __first, iterator __last) noexcept
>>+ { return _M_parse_fill_and_align(__first, __last, "{"); }
>>+
>>+ // pre: __first != __last
>>+ constexpr iterator
>>+ _M_parse_fill_and_align(iterator __first, iterator __last, string_view __not_fill) noexcept
>> {
>>- if (*__first != '{')
>>+ for (char c : __not_fill)
>>+ if (*__first == c)
>>+ return __first;
>>+
>>+ using namespace __unicode;
>>+ if constexpr (__literal_encoding_is_unicode<_CharT>())
>> {
>>- using namespace __unicode;
>>- if constexpr (__literal_encoding_is_unicode<_CharT>())
>>- {
>>- // Accept any UCS scalar value as fill character.
>>- _Utf32_view<ranges::subrange<iterator>> __uv({__first, __last});
>>- if (!__uv.empty())
>>- {
>>- auto __beg = __uv.begin();
>>- char32_t __c = *__beg++;
>>- if (__is_scalar_value(__c))
>>- if (auto __next = __beg.base(); __next != __last)
>>- if (_Align __align = _S_align(*__next))
>>- {
>>- _M_fill = __c;
>>- _M_align = __align;
>>- return ++__next;
>>- }
>>- }
>>- }
>>- else if (__last - __first >= 2)
>>- if (_Align __align = _S_align(__first[1]))
>>- {
>>- _M_fill = *__first;
>>- _M_align = __align;
>>- return __first + 2;
>>- }
>>+ // Accept any UCS scalar value as fill character.
>>+ _Utf32_view<ranges::subrange<iterator>> __uv({__first, __last});
>>+ if (!__uv.empty())
>>+ {
>>+ auto __beg = __uv.begin();
>>+ char32_t __c = *__beg++;
>>+ if (__is_scalar_value(__c))
>>+ if (auto __next = __beg.base(); __next != __last)
>>+ if (_Align __align = _S_align(*__next))
>>+ {
>>+ _M_fill = __c;
>>+ _M_align = __align;
>>+ return ++__next;
>>+ }
>>+ }
>>+ }
>>+ else if (__last - __first >= 2)
>>+ if (_Align __align = _S_align(__first[1]))
>>+ {
>>+ _M_fill = *__first;
>>+ _M_align = __align;
>>+ return __first + 2;
>>+ }
>>
>>- if (_Align __align = _S_align(__first[0]))
>>- {
>>- _M_fill = ' ';
>>- _M_align = __align;
>>- return __first + 1;
>>- }
>>+ if (_Align __align = _S_align(__first[0]))
>>+ {
>>+ _M_fill = ' ';
>>+ _M_align = __align;
>>+ return __first + 1;
>> }
>> return __first;
>> }
>>@@ -934,11 +945,27 @@ namespace __format
>>
>> static consteval
>> _Str_view _S_all()
>>- { return _GLIBCXX_WIDEN("{}"); }
>>+ { return _GLIBCXX_WIDEN("[]{}(), : "); }
>>
>> static consteval
>>- _Str_view _S_braces()
>>+ _Str_view _S_squares()
>> { return _S_all().substr(0, 2); }
>>+
>>+ static consteval
>>+ _Str_view _S_braces()
>>+ { return _S_all().substr(2, 2); }
>>+
>>+ static consteval
>>+ _Str_view _S_parens()
>>+ { return _S_all().substr(4, 2); }
>>+
>>+ static consteval
>>+ _Str_view _S_comma()
>>+ { return _S_all().substr(6, 2); }
>>+
>>+ static consteval
>>+ _Str_view _S_colon()
>>+ { return _S_all().substr(8, 2); }
>> };
>>
>> template<typename _CharT>
>>@@ -1231,6 +1258,13 @@ namespace __format
>> template<__char _CharT>
>> struct __formatter_str
>> {
>>+ __formatter_str() = default;
>>+
>>+ constexpr
>>+ __formatter_str(_Spec<_CharT> __spec) noexcept
>>+ : _M_spec(__spec)
>>+ { }
>>+
>> constexpr typename basic_format_parse_context<_CharT>::iterator
>> parse(basic_format_parse_context<_CharT>& __pc)
>> {
>>@@ -1329,6 +1363,43 @@ namespace __format
>> }
>>
>>#if __glibcxx_format_ranges // C++ >= 23 && HOSTED
>>+ template<ranges::input_range _Rg, class _Out>
>
>typename _Out rather than class _Out
>
>>+ requires same_as<remove_cvref_t<ranges::range_reference_t<_Rg>>, _CharT>
>>+ typename basic_format_context<_Out, _CharT>::iterator
>>+ _M_format_range(_Rg&& __rg, basic_format_context<_Out, _CharT>& __fc) const
>>+ {
>>+ using _String = basic_string<_CharT>;
>>+ using _String_view = basic_string_view<_CharT>;
>>+ if constexpr (ranges::forward_range<_Rg> || ranges::sized_range<_Rg>)
>>+ {
>>+ const size_t __n(ranges::distance(__rg));
>>+ if constexpr (ranges::contiguous_range<_Rg>)
>>+ return format(_String_view(ranges::data(__rg), __n), __fc);
>
>This should be std::format to prevent ADL (and similarly below).
>
>>+ else if (__n <= __format::__Stackbuf_size<_CharT>)
>>+ {
>>+ _CharT __buf[__format::__Stackbuf_size<_CharT>];
>>+ ranges::copy(__rg, __buf);
>>+ return format(_String_view(__buf, __n), __fc);
>>+ }
>>+ else if constexpr (ranges::sized_range<_Rg>)
>>+ return format(_String(from_range, __rg), __fc);
>>+ else if constexpr (ranges::random_access_range<_Rg>)
>>+ {
>>+ ranges::iterator_t<_Rg> __first = ranges::begin(__rg);
>>+ ranges::subrange __sub(__first, __first + __n);
>>+ return format(_String(from_range, __sub), __fc);
>>+ }
>>+ else
>>+ {
>>+ // N.B. preserve the computed size
>>+ ranges::subrange __sub(__rg, __n);
>>+ return format(_String(from_range, __sub), __fc);
>>+ }
>>+ }
>>+ else
>>+ return format(_String(from_range, __rg), __fc);
>>+ }
>>+
>> constexpr void
>> set_debug_format() noexcept
>> { _M_spec._M_type = _Pres_esc; }
>>@@ -2931,7 +3002,7 @@ namespace __format
>> };
>> /// @}
>>
>>-#if defined _GLIBCXX_USE_WCHAR_T && __cpp_lib_format_ranges
>>+#if defined _GLIBCXX_USE_WCHAR_T && __glibcxx_format_ranges
>> // _GLIBCXX_RESOLVE_LIB_DEFECTS
>> // 3944. Formatters converting sequences of char to sequences of wchar_t
>>
>>@@ -2991,19 +3062,21 @@ namespace __format
>> concept __formattable_impl
>> = __parsable_with<_Tp, _Context> && __formattable_with<_Tp, _Context>;
>>
>>+ template<typename _Formatter>
>>+ concept __has_debug_format = requires(_Formatter __f)
>>+ {
>>+ __f.set_debug_format();
>>+ };
>>+
>>} // namespace __format
>>/// @endcond
>>
>>-// Concept std::formattable was introduced by P2286R8 "Formatting Ranges",
>>-// but we can't guard it with __cpp_lib_format_ranges until we define that!
>>-#if __cplusplus > 202002L
>>+#if __glibcxx_format_ranges // C++ >= 23 && HOSTED
>> // [format.formattable], concept formattable
>> template<typename _Tp, typename _CharT>
>> concept formattable
>> = __format::__formattable_impl<remove_reference_t<_Tp>, _CharT>;
>>-#endif
>>
>>-#if __cpp_lib_format_ranges
>> /// @cond undocumented
>>namespace __format
>>{
>>@@ -3246,7 +3319,7 @@ namespace __format
>> class _Buf_sink : public _Sink<_CharT>
>> {
>> protected:
>>- _CharT _M_buf[32 * sizeof(void*) / sizeof(_CharT)];
>>+ _CharT _M_buf[__Stackbuf_size<_CharT>];
>>
>> [[__gnu__::__always_inline__]]
>> constexpr
>>@@ -5088,7 +5161,7 @@ namespace __format
>> }
>>#endif
>>
>>-#if __cpp_lib_format_ranges
>>+#if __glibcxx_format_ranges // C++ >= 23 && HOSTED
>> // [format.range], formatting of ranges
>> // [format.range.fmtkind], variable template format_kind
>> enum class range_format {
>>@@ -5133,28 +5206,352 @@ namespace __format
>> template<ranges::input_range _Rg> requires same_as<_Rg, remove_cvref_t<_Rg>>
>> constexpr range_format format_kind<_Rg> = __fmt_kind<_Rg>();
>>
>>- // [format.range.formatter], class template range_formatter
>>- template<typename _Tp, typename _CharT = char>
>>- requires same_as<remove_cvref_t<_Tp>, _Tp> && formattable<_Tp, _CharT>
>>- class range_formatter; // TODO
>>-
>>/// @cond undocumented
>>namespace __format
>>{
>>- // [format.range.fmtdef], class template range-default-formatter
>>- template<range_format _Kind, ranges::input_range _Rg, typename _CharT>
>>- struct __range_default_formatter; // TODO
>>+ template<typename _Tp>
>>+ concept __is_map_formattable
>>+ = __is_pair<_Tp> || (__is_tuple_v<_Tp> && tuple_size_v<_Tp> == 2);
>>+
>>} // namespace __format
>>/// @endcond
>>
>>+ // [format.range.formatter], class template range_formatter
>>+ template<class _Tp, __format::__char _CharT = char>
>
>typename _Tp
>
>>+ requires same_as<remove_cvref_t<_Tp>, _Tp> && formattable<_Tp, _CharT>
>>+ class range_formatter
>>+ {
>>+ using _String_view = basic_string_view<_CharT>;
>>+ using _Seps = __format::_Separators<_CharT>;
>>+
>>+ public:
>>+ constexpr void
>>+ set_separator(basic_string_view<_CharT> __sep) noexcept
>>+ { _M_sep = __sep; }
>>+
>>+ constexpr void
>>+ set_brackets(basic_string_view<_CharT> __open,
>>+ basic_string_view<_CharT> __close) noexcept
>>+ {
>>+ _M_open = __open;
>>+ _M_close = __close;
>>+ }
>>+
>>+ constexpr formatter<_Tp, _CharT>&
>>+ underlying() noexcept
>>+ { return _M_fval; }
>>+
>>+ constexpr const formatter<_Tp, _CharT>&
>>+ underlying() const noexcept
>>+ { return _M_fval; }
>>+
>>+ // We deviate from standard, that declares this as template accepting
>>+ // unconstrained ParseContext type, which seems unimplementable.
>
>Thanks for noting this with the comment.
>
>>+ constexpr typename basic_format_parse_context<_CharT>::iterator
>>+ parse(basic_format_parse_context<_CharT>& __pc)
>>+ {
>>+ auto __first = __pc.begin();
>>+ const auto __last = __pc.end();
>>+ __format::_Spec<_CharT> __spec{};
>>+ bool __no_brace = false;
>>+
>>+ auto __finished = [&]
>>+ { return __first == __last || *__first == '}'; };
>>+
>>+ auto __finalize = [&]
>>+ {
>>+ _M_spec = __spec;
>>+ return __first;
>>+ };
>>+
>>+ auto __parse_val = [&](_String_view __nfs = _String_view())
>>+ {
>>+ basic_format_parse_context<_CharT> __npc(__nfs);
>>+ if (_M_fval.parse(__npc) != __npc.end())
>>+ __format::__failed_to_parse_format_spec();
>>+ if constexpr (__format::__has_debug_format<formatter<_Tp, _CharT>>)
>>+ _M_fval.set_debug_format();
>>+ return __finalize();
>>+ };
>>+
>>+ if (__finished())
>>+ return __parse_val();
>>+
>>+ __first = __spec._M_parse_fill_and_align(__first, __last, "{:");
>>+ if (__finished())
>>+ return __parse_val();
>>+
>>+ __first = __spec._M_parse_width(__first, __last, __pc);
>>+ if (__finished())
>>+ return __parse_val();
>>+
>>+ if (*__first == '?')
>>+ {
>>+ ++__first;
>>+ __spec._M_type = __format::_Pres_esc;
>>+ }
>>+
>>+ if (__finished()) // only '?'
>>+ __throw_format_error("format error: '?' is allowed only in combination"
>>+ " with 's'");
>>+
>>+ if (*__first == 's')
>>+ {
>>+ ++__first;
>>+ if constexpr (same_as<_Tp, _CharT>)
>>+ {
>>+ if (__spec._M_type != __format::_Pres_esc)
>>+ __spec._M_type = __format::_Pres_str;
>>+ if (__finished())
>>+ return __finalize();
>>+ __throw_format_error("format error: element format specifier "
>>+ "cannot be provided when 's' specifier is used");
>>+ }
>>+ else
>>+ __throw_format_error("format error: 's' specifier requires "
>>+ " range of character types");
>
>This has two space characters before "range".
>
>>+ }
>>+
>>+ if (__spec._M_type == __format::_Pres_esc) // only '?'
>>+ __throw_format_error("format error: '?' is allowed only in combination"
>>+ " with 's'");
>
>These string literals that are split over two lines are inconsistent
>about whether a space appears at the end of the first line or the
>start of the second. Not a big deal, but it would be easier to check
>they're correct if they all consistently used "foo " "bar" with the
>space at the end of the first line.
>
>>+
>>+ if (__finished())
>>+ return __parse_val();
>>+
>>+ if (*__first == 'n')
>>+ {
>>+ ++__first;
>>+ _M_open = _M_close = _String_view();
>>+ __no_brace = true;
>>+ }
>>+
>>+ if (__finished())
>>+ return __parse_val();
>>+
>>+ if (*__first == 'm')
>>+ {
>>+ _String_view __m(__first, 1);
>>+ ++__first;
>>+ if constexpr (__format::__is_map_formattable<_Tp>)
>>+ {
>>+ _M_sep = _Seps::_S_comma();
>>+ if (!__no_brace)
>>+ {
>>+ _M_open = _Seps::_S_braces().substr(0, 1);
>>+ _M_close = _Seps::_S_braces().substr(1, 1);
>>+ }
>>+ if (__finished())
>>+ return __parse_val(__m);
>>+ __throw_format_error("format error: element format specifier "
>>+ "cannot be provided when 'm' specifier is used");
>>+
>>+ }
>>+ else
>>+ __throw_format_error("format error: 'm' specifier requires "
>>+ " range of pairs or tuples of two elements");
>
>Two spaces before "range" again.
>
>>+ }
>>+
>>+ if (__finished())
>>+ return __parse_val();
>>+
>>+ if (*__first == ':')
>>+ {
>>+ __pc.advance_to(++__first);
>>+ __first = _M_fval.parse(__pc);
>>+ }
>>+
>>+ if (__finished())
>>+ return __finalize();
>>+
>>+ __format::__failed_to_parse_format_spec();
>>+ }
>>+
>>+ // We deviate from standard, that declares this as template accepting
>>+ // unconstrained FormatContext type, which seems unimplementable.
>>+ template<ranges::input_range _Rg, class _Out>
>
>typename _Out
>
>>+ requires formattable<ranges::range_reference_t<_Rg>, _CharT> &&
>>+ same_as<remove_cvref_t<ranges::range_reference_t<_Rg>>, _Tp>
>>+ typename basic_format_context<_Out, _CharT>::iterator
>>+ format(_Rg&& __rg, basic_format_context<_Out, _CharT>& __fc) const
>>+ {
>>+ // This is required to implement formatting with padding,
>>+ // as we need to format to temporary buffer, using the same itertor.
>
>"itertor" should be "iterator"
>
>>+ static_assert(is_same_v<_Out, __format::_Sink_iter<_CharT>>);
>>+ if constexpr (same_as<_Tp, _CharT>)
>>+ if (_M_spec._M_type == __format::_Pres_str
>>+ || _M_spec._M_type == __format::_Pres_esc)
>>+ {
>>+ __format::__formatter_str __fstr(_M_spec);
>>+ return __fstr._M_format_range(__rg, __fc);
>>+ }
>>+ if (_M_spec._M_get_width(__fc) > 0)
>>+ return _M_format_with_padding(__rg, __fc);
>>+ return _M_format_no_padding(__rg, __fc);
>>+ }
>>+
>>+ private:
>>+ template<ranges::input_range _Rg, class _Out>
>
>typename _Out
>
>>+ typename basic_format_context<_Out, _CharT>::iterator
>>+ _M_format_no_padding(_Rg& __rg,
>>+ basic_format_context<_Out, _CharT>& __fc) const
>>+ {
>>+ auto __out = __format::__write(__fc.out(), _M_open);
>>+
>>+ auto __first = ranges::begin(__rg);
>>+ auto const __last = ranges::end(__rg);
>>+ if (__first == __last)
>>+ return __format::__write(__out, _M_close);
>>+
>>+ __fc.advance_to(__out);
>>+ __out = _M_fval.format(*__first, __fc);
>>+ for (++__first; __first != __last; ++__first)
>>+ {
>>+ __out = __format::__write(__out, _M_sep);
>>+ __fc.advance_to(__out);
>>+ __out = _M_fval.format(*__first, __fc);
>>+ }
>>+
>>+ return __format::__write(__out, _M_close);
>>+ }
>>+
>>+ template<ranges::input_range _Rg, class _Out>
>
>typename _Out
>
>>+ typename basic_format_context<_Out, _CharT>::iterator
>>+ _M_format_with_padding(_Rg& __rg,
>>+ basic_format_context<_Out, _CharT>& __fc) const
>>+ {
>>+ struct _Restore_out
>>+ {
>>+ _Restore_out(basic_format_context<_Out, _CharT>& __fc)
>>+ : _M_ctx(addressof(__fc)), _M_out(__fc.out())
>>+ { }
>>+
>>+ void trigger()
>>+ {
>>+ if (_M_ctx)
>>+ _M_ctx->advance_to(_M_out);
>>+ _M_ctx = nullptr;
>>+ }
>>+
>>+ ~_Restore_out()
>>+ { trigger(); }
>>+
>>+ private:
>>+ basic_format_context<_Out, _CharT>* _M_ctx;
>>+ __format::_Sink_iter<_CharT> _M_out;
>>+ };
>>+
>>+ _Restore_out __restore{__fc};
>>+ // TODO Consider double sinking, first buffer of width
>>+ // size and then original sink, if first buffer is overun
>
>"overun" should be "overrun"
>
>>+ // we do not need to align
>>+ __format::_Str_sink<_CharT> __buf;
>>+ __fc.advance_to(__format::_Sink_iter<_CharT>(__buf));
>>+ _M_format_no_padding(__rg, __fc);
>>+ __restore.trigger();
>>+
>>+ _String_view __s(__buf.view());
>>+ size_t __width;
>>+ if constexpr (__unicode::__literal_encoding_is_unicode<_CharT>())
>>+ __width = __unicode::__field_width(__s);
>>+ else
>>+ __width = __s.size();
>>+ return __format::__write_padded_as_spec(__s, __width, __fc, _M_spec);
>>+ }
>>+
>>+ __format::_Spec<_CharT> _M_spec{};
>>+ _String_view _M_open = _Seps::_S_squares().substr(0, 1);
>>+ _String_view _M_close = _Seps::_S_squares().substr(1, 1);
>>+ _String_view _M_sep = _Seps::_S_comma();
>>+ formatter<_Tp, _CharT> _M_fval;
>>+ };
>>+
>>+ // In standard this is shown as inherting from specialization of
>
>"inherting" should be "inheriting"
>
>>+ // exposition only specialization for range-default-formatter for
>>+ // each range_format. We opt for simpler implementation.
>> // [format.range.fmtmap], [format.range.fmtset], [format.range.fmtstr],
>> // specializations for maps, sets, and strings
>>- template<ranges::input_range _Rg, typename _CharT>
>>+ template<ranges::input_range _Rg, __format::__char _CharT>
>> requires (format_kind<_Rg> != range_format::disabled)
>> && formattable<ranges::range_reference_t<_Rg>, _CharT>
>> struct formatter<_Rg, _CharT>
>>- : __format::__range_default_formatter<format_kind<_Rg>, _Rg, _CharT>
>>- { };
>>+ {
>>+ private:
>>+ static const bool _S_range_format_is_string =
>>+ (format_kind<_Rg> == range_format::string)
>>+ || (format_kind<_Rg> == range_format::debug_string);
>>+ using _Vt = remove_cvref_t<
>>+ ranges::range_reference_t<
>>+ __format::__maybe_const_range<_Rg, _CharT>>>;
>>+
>>+ static consteval bool _S_is_correct()
>>+ {
>>+ if constexpr (_S_range_format_is_string)
>>+ static_assert(same_as<_Vt, _CharT>);
>>+ return true;
>>+ }
>>+
>>+ static_assert(_S_is_correct());
>>+
>>+ public:
>>+ constexpr formatter() noexcept
>>+ {
>>+ using _Seps = __format::_Separators<_CharT>;
>>+ if constexpr (format_kind<_Rg> == range_format::map)
>>+ {
>>+ static_assert(__format::__is_map_formattable<_Vt>);
>>+ _M_under.set_brackets(_Seps::_S_braces().substr(0, 1),
>>+ _Seps::_S_braces().substr(1, 1));
>>+ _M_under.underlying().set_brackets({}, {});
>>+ _M_under.underlying().set_separator(_Seps::_S_colon());
>>+ }
>>+ else if constexpr (format_kind<_Rg> == range_format::set)
>>+ _M_under.set_brackets(_Seps::_S_braces().substr(0, 1),
>>+ _Seps::_S_braces().substr(1, 1));
>>+ }
>>+
>>+ constexpr void
>>+ set_separator(basic_string_view<_CharT> __sep) noexcept
>>+ requires (!_S_range_format_is_string)
>>+ { _M_under.set_separator(__sep); }
>>+
>>+ constexpr void
>>+ set_brackets(basic_string_view<_CharT> __open,
>>+ basic_string_view<_CharT> __close) noexcept
>>+ requires (!_S_range_format_is_string)
>>+ { _M_under.set_brackets(__open, __close); }
>
>Looks like a stray tab here.
>
>>+
>>+ // We deviate from standard, that declares this as template accepting
>>+ // unconstrained ParseContext type, which seems unimplementable.
>>+ constexpr typename basic_format_parse_context<_CharT>::iterator
>>+ parse(basic_format_parse_context<_CharT>& __pc)
>>+ {
>>+ auto __res = _M_under.parse(__pc);
>>+ if constexpr (format_kind<_Rg> == range_format::debug_string)
>>+ _M_under.set_debug_format();
>>+ return __res;
>>+ }
>>+
>>+ // We deviate from standard, that declares this as template accepting
>>+ // unconstrained FormatContext type, which seems unimplementable.
>>+ template<typename _Out>
>>+ typename basic_format_context<_Out, _CharT>::iterator
>>+ format(__format::__maybe_const_range<_Rg, _CharT>& __rg,
>>+ basic_format_context<_Out, _CharT>& __fc) const
>>+ {
>>+ if constexpr (_S_range_format_is_string)
>>+ return _M_under._M_format_range(__rg, __fc);
>>+ else
>>+ return _M_under.format(__rg, __fc);
>>+ }
>
>And another stray tab here.
>
>>+
>>+ private:
>>+ using _Formatter_under
>>+ = __conditional_t<_S_range_format_is_string,
>>+ __format::__formatter_str<_CharT>,
>>+ range_formatter<_Vt, _CharT>>;
>>+ _Formatter_under _M_under;
>>+ };
>>#endif // C++23 formatting ranges
>>#undef _GLIBCXX_WIDEN
>>
>>diff --git a/libstdc++-v3/src/c++23/std.cc.in b/libstdc++-v3/src/c++23/std.cc.in
>>index 12253b95c5a..5e18ad73908 100644
>>--- a/libstdc++-v3/src/c++23/std.cc.in
>>+++ b/libstdc++-v3/src/c++23/std.cc.in
>>@@ -1332,6 +1332,12 @@ export namespace std
>> using std::wformat_context;
>> using std::wformat_parse_context;
>> using std::wformat_string;
>>+// FIXME __cpp_lib_format_ranges
>>+#ifdef __glibcxx_format_ranges
>>+ using std::format_kind;
>>+ using std::range_format;
>>+ using std::range_formatter;
>>+#endif
>>}
>>
>>// <forward_list>
>>diff --git a/libstdc++-v3/testsuite/23_containers/vector/bool/format.cc b/libstdc++-v3/testsuite/23_containers/vector/bool/format.cc
>>index 2586225dd05..eb24b66d82e 100644
>>--- a/libstdc++-v3/testsuite/23_containers/vector/bool/format.cc
>>+++ b/libstdc++-v3/testsuite/23_containers/vector/bool/format.cc
>>@@ -56,6 +56,12 @@ test_output()
>>
>> res = std::format(WIDEN("{:=^#7X}"), v[1]);
>> VERIFY( res == WIDEN("==0X0==") );
>>+
>>+ res = std::format(WIDEN("{}"), v);
>>+ VERIFY( res == WIDEN("[true, false]") );
>>+
>>+ res = std::format(WIDEN("{::d}"), v);
>>+ VERIFY( res == WIDEN("[1, 0]") );
>>}
>>
>>int main()
>>diff --git a/libstdc++-v3/testsuite/std/format/formatter/lwg3944.cc b/libstdc++-v3/testsuite/std/format/formatter/lwg3944.cc
>>index ff5f075bcc8..1f3edc9cb03 100644
>>--- a/libstdc++-v3/testsuite/std/format/formatter/lwg3944.cc
>>+++ b/libstdc++-v3/testsuite/std/format/formatter/lwg3944.cc
>>@@ -4,6 +4,7 @@
>>// LWG 3944. Formatters converting sequences of char to sequences of wchar_t
>>
>>#include <format>
>>+#include <vector>
>>
>>void test_lwg3944()
>>{
>>@@ -14,11 +15,10 @@ void test_lwg3944()
>> std::format(L"{}",cstr); // { dg-error "here" }
>>
>> // Ill-formed in C++20
>>- // In C++23 they give L"['h', 'e', 'l', 'l', 'o']"
>> std::format(L"{}", "hello"); // { dg-error "here" }
>> std::format(L"{}", std::string_view("hello")); // { dg-error "here" }
>> std::format(L"{}", std::string("hello")); // { dg-error "here" }
>>-#ifdef __cpp_lib_format_ranges
>>+#ifdef __glibcxx_format_ranges
>> // LWG 3944 does not change this, it's still valid.
>> std::format(L"{}", std::vector{'h', 'e', 'l', 'l', 'o'});
>>#endif
>>diff --git a/libstdc++-v3/testsuite/std/format/formatter/requirements.cc b/libstdc++-v3/testsuite/std/format/formatter/requirements.cc
>>index 416b9a8ede5..51f04f538ba 100644
>>--- a/libstdc++-v3/testsuite/std/format/formatter/requirements.cc
>>+++ b/libstdc++-v3/testsuite/std/format/formatter/requirements.cc
>>@@ -70,12 +70,14 @@ test_specializations() // [format.formatter.spec]
>>
>> // LWG 3833. Remove specialization
>> // template<size_t N> struct formatter<const charT[N], charT>
>>- using Farr = std::format_context::formatter_type<const char[1]>;
>>- static_assert( ! std::is_default_constructible_v<Farr> );
>>- static_assert( ! std::is_copy_constructible_v<Farr> );
>>- static_assert( ! std::is_move_constructible_v<Farr> );
>>- static_assert( ! std::is_copy_assignable_v<Farr> );
>>- static_assert( ! std::is_move_assignable_v<Farr> );
>>+ // Formatter is only expected to be instantiated with only cv-unqal types
>
>"unqual"
>
>>+ // and attempting to instantiate this specialization is ill-formed
>>+ // using Farr = std::format_context::formatter_type<const char[1]>;
>>+ // static_assert( ! std::is_default_constructible_v<Farr> );
>>+ // static_assert( ! std::is_copy_constructible_v<Farr> );
>>+ // static_assert( ! std::is_move_constructible_v<Farr> );
>>+ // static_assert( ! std::is_copy_assignable_v<Farr> );
>>+ // static_assert( ! std::is_move_assignable_v<Farr> );
>>}
>>
>>int main()
>>diff --git a/libstdc++-v3/testsuite/std/format/ranges/format_kind.cc b/libstdc++-v3/testsuite/std/format/ranges/format_kind.cc
>>new file mode 100644
>>index 00000000000..14b9ff20c21
>>--- /dev/null
>>+++ b/libstdc++-v3/testsuite/std/format/ranges/format_kind.cc
>>@@ -0,0 +1,94 @@
>>+// { dg-do run { target c++23 } }
>>+
>>+#include <deque>
>>+#include <flat_map>
>>+#include <flat_set>
>>+#include <format>
>>+#include <list>
>>+#include <map>
>>+#include <set>
>>+#include <testsuite_hooks.h>
>>+#include <unordered_map>
>>+#include <unordered_set>
>>+#include <vector>
>>+
>>+static_assert( std::format_kind<std::vector<int>> == std::range_format::sequence );
>>+static_assert( std::format_kind<std::deque<int>> == std::range_format::sequence );
>>+static_assert( std::format_kind<std::list<int>> == std::range_format::sequence );
>>+
>>+static_assert( std::format_kind<std::set<int>> == std::range_format::set );
>>+static_assert( std::format_kind<std::multiset<int>> == std::range_format::set );
>>+static_assert( std::format_kind<std::unordered_set<int>> == std::range_format::set );
>>+static_assert( std::format_kind<std::unordered_multiset<int>> == std::range_format::set );
>>+static_assert( std::format_kind<std::flat_set<int>> == std::range_format::set );
>>+static_assert( std::format_kind<std::flat_multiset<int>> == std::range_format::set );
>>+
>>+static_assert( std::format_kind<std::map<int, int>> == std::range_format::map );
>>+static_assert( std::format_kind<std::multimap<int, int>> == std::range_format::map );
>>+static_assert( std::format_kind<std::unordered_map<int, int>> == std::range_format::map );
>>+static_assert( std::format_kind<std::unordered_multimap<int, int>> == std::range_format::map );
>>+static_assert( std::format_kind<std::flat_map<int, int>> == std::range_format::map );
>>+static_assert( std::format_kind<std::flat_multimap<int, int>> == std::range_format::map );
>>+
>>+template<typename T>
>>+struct MyVec : std::vector<T>
>>+{};
>>+
>>+static_assert( std::format_kind<MyVec<int>> == std::range_format::sequence );
>>+
>>+template<typename T>
>>+struct MySet : std::vector<T>
>>+{
>>+ using key_type = T;
>>+};
>>+
>>+static_assert( std::format_kind<MySet<int>> == std::range_format::set );
>>+
>>+template<typename T>
>>+struct MyMap : std::vector<T>
>>+{
>>+ using key_type = T;
>>+ using mapped_type = int;
>>+};
>>+
>>+static_assert( std::format_kind<MyMap<std::pair<int, int>>> == std::range_format::map );
>>+static_assert( std::format_kind<MyMap<std::tuple<int, int>>> == std::range_format::map );
>>+static_assert( std::format_kind<MyMap<int>> == std::range_format::set );
>>+
>>+template<typename T, std::range_format rf>
>>+struct CustFormat : std::vector<T>
>>+{
>>+ using std::vector<T>::vector;
>>+};
>>+
>>+template<typename T, std::range_format rf>
>>+constexpr auto std::format_kind<CustFormat<T, rf>> = rf;
>>+
>>+void test_override()
>>+{
>>+ CustFormat<int, std::range_format::disabled> disabledf;
>>+ static_assert( !std::formattable<decltype(disabledf), char> );
>>+
>>+ CustFormat<int, std::range_format::sequence> seqf{1, 2, 3};
>>+ VERIFY( std::format("{}", seqf) == "[1, 2, 3]" );
>>+
>>+ CustFormat<int, std::range_format::set> setf{1, 2, 3};
>>+ VERIFY( std::format("{}", setf) == "{1, 2, 3}" );
>>+
>>+ // TODO test map once formatter for pair is implenented
>>+
>>+ CustFormat<char, std::range_format::string> stringf{'a', 'b', 'c', 'd'};
>>+ VERIFY( std::format("{}", stringf) == "abcd" );
>>+ // Support precision as string do
>>+ VERIFY( std::format("{:.2}", stringf) == "ab" );
>>+
>>+ CustFormat<char, std::range_format::debug_string> debugf{'a', 'b', 'c', 'd'};
>>+ VERIFY( std::format("{}", debugf) == R"("abcd")" );
>>+ // Support precision as string do
>>+ VERIFY( std::format("{:.3}", debugf) == R"("ab)" );
>>+}
>>+
>>+int main()
>>+{
>>+ test_override();
>>+}
>>diff --git a/libstdc++-v3/testsuite/std/format/ranges/formatter.cc b/libstdc++-v3/testsuite/std/format/ranges/formatter.cc
>>new file mode 100644
>>index 00000000000..2045b51547a
>>--- /dev/null
>>+++ b/libstdc++-v3/testsuite/std/format/ranges/formatter.cc
>>@@ -0,0 +1,145 @@
>>+// { dg-do run { target c++23 } }
>>+
>>+#include <format>
>>+#include <testsuite_hooks.h>
>>+#include <vector>
>>+
>>+#define WIDEN_(C, S) ::std::__format::_Widen<C>(S, L##S)
>>+#define WIDEN(S) WIDEN_(_CharT, S)
>>+
>>+template<typename T,
>>+ template<typename, typename> class Formatter = std::range_formatter>
>>+struct MyVector : std::vector<T>
>>+{
>>+ using std::vector<T>::vector;
>>+};
>>+
>>+template<typename T,
>>+ template<typename, typename> class Formatter,
>>+ typename CharT>
>>+struct std::formatter<MyVector<T, Formatter>, CharT>
>>+{
>>+ constexpr formatter() noexcept
>>+ {
>>+ using _CharT = CharT;
>>+ _formatter.set_brackets(WIDEN("<"), WIDEN(">"));
>>+ _formatter.set_separator(WIDEN("; "));
>>+ }
>>+
>>+ constexpr std::basic_format_parse_context<CharT>::iterator
>>+ parse(std::basic_format_parse_context<CharT>& pc)
>>+ { return _formatter.parse(pc); }
>>+
>>+ template<typename Out>
>>+ typename std::basic_format_context<Out, CharT>::iterator
>>+ format(const MyVector<T, Formatter>& mv,
>>+ std::basic_format_context<Out, CharT>& fc) const
>>+ { return _formatter.format(mv, fc); }
>>+
>>+private:
>>+ Formatter<T, CharT> _formatter;
>>+};
>>+
>>+template<typename _CharT, template<typename, typename> class Formatter>
>>+void
>>+test_default()
>>+{
>>+ MyVector<int, Formatter> vec{1, 2, 3};
>>+ std::basic_string<_CharT> res;
>>+
>>+ res = std::format(WIDEN("{}"), vec);
>>+ VERIFY( res == WIDEN("<1; 2; 3>") );
>>+ res = std::format(WIDEN("{:}"), vec);
>>+ VERIFY( res == WIDEN("<1; 2; 3>") );
>>+ res = std::format(WIDEN("{:n}"), vec);
>>+ VERIFY( res == WIDEN("1; 2; 3") );
>>+
>>+ res = std::format(WIDEN("{:3}"), vec);
>>+ VERIFY( res == WIDEN("<1; 2; 3>") );
>>+
>>+ res = std::format(WIDEN("{:10}"), vec);
>>+ VERIFY( res == WIDEN("<1; 2; 3> ") );
>>+
>>+ res = std::format(WIDEN("{:{}}"), vec, 10);
>>+ VERIFY( res == WIDEN("<1; 2; 3> ") );
>>+
>>+ res = std::format(WIDEN("{1:{0}}"), 10, vec);
>>+ VERIFY( res == WIDEN("<1; 2; 3> ") );
>>+
>>+ res = std::format(WIDEN("{:10n}"), vec);
>>+ VERIFY( res == WIDEN("1; 2; 3 ") );
>>+
>>+ res = std::format(WIDEN("{:*<11}"), vec);
>>+ VERIFY( res == WIDEN("<1; 2; 3>**") );
>>+
>>+ res = std::format(WIDEN("{:->12}"), vec);
>>+ VERIFY( res == WIDEN("---<1; 2; 3>") );
>>+
>>+ res = std::format(WIDEN("{:=^13}"), vec);
>>+ VERIFY( res == WIDEN("==<1; 2; 3>==") );
>>+
>>+ res = std::format(WIDEN("{:=^13n}"), vec);
>>+ VERIFY( res == WIDEN("===1; 2; 3===") );
>>+
>>+ res = std::format(WIDEN("{::#x}"), vec);
>>+ VERIFY( res == WIDEN("<0x1; 0x2; 0x3>") );
>>+
>>+ res = std::format(WIDEN("{:|^25n:#05x}"), vec);
>>+ VERIFY( res == WIDEN("|||0x001; 0x002; 0x003|||") );
>>+
>>+ // ':' is start of the format string for element
>>+ res = std::format(WIDEN("{::^+4}"), vec);
>>+ VERIFY( res == WIDEN("< +1 ; +2 ; +3 >") );
>>+}
>>+
>>+template<typename _CharT, template<typename, typename> class Formatter>
>>+void
>>+test_override()
>>+{
>>+ MyVector<_CharT, Formatter> vc{'a', 'b', 'c', 'd'};
>>+ std::basic_string<_CharT> res;
>>+
>>+ res = std::format(WIDEN("{:s}"), vc);
>>+ VERIFY( res == WIDEN("abcd") );
>>+ res = std::format(WIDEN("{:?s}"), vc);
>>+ VERIFY( res == WIDEN("\"abcd\"") );
>>+ res = std::format(WIDEN("{:+^6s}"), vc);
>>+ VERIFY( res == WIDEN("+abcd+") );
>>+
>>+ // TODO test map
>>+}
>>+
>>+template<template<typename, typename> class Formatter>
>>+void test_outputs()
>>+{
>>+ test_default<char, Formatter>();
>>+ test_default<wchar_t, Formatter>();
>>+ test_override<char, Formatter>();
>>+ test_override<wchar_t, Formatter>();
>>+}
>>+
>>+void
>>+test_nested()
>>+{
>>+ MyVector<MyVector<int>> v
>>+ {
>>+ {1, 2},
>>+ {11, 12}
>>+ };
>>+
>>+ std::string res = std::format("{}", v);
>>+ VERIFY( res == "<<1; 2>; <11; 12>>" );
>>+
>>+ res = std::format("{:+^18:n:02}", v);
>>+ VERIFY( res == "+<01; 02; 11; 12>+" );
>>+}
>>+
>>+template<typename T, typename CharT>
>>+using VectorFormatter = std::formatter<std::vector<T>, CharT>;
>>+
>>+int main()
>>+{
>>+ test_outputs<std::range_formatter>();
>>+ test_outputs<VectorFormatter>();
>>+ test_nested();
>>+}
>>diff --git a/libstdc++-v3/testsuite/std/format/ranges/sequence.cc b/libstdc++-v3/testsuite/std/format/ranges/sequence.cc
>>new file mode 100644
>>index 00000000000..06574379ed5
>>--- /dev/null
>>+++ b/libstdc++-v3/testsuite/std/format/ranges/sequence.cc
>>@@ -0,0 +1,190 @@
>>+// { dg-do run { target c++23 } }
>>+
>>+#include <format>
>>+#include <list>
>>+#include <span>
>>+#include <testsuite_hooks.h>
>>+#include <testsuite_iterators.h>
>>+#include <vector>
>>+
>>+struct NotFormattable
>>+{};
>>+
>>+static_assert(!std::formattable<std::vector<NotFormattable>, char>);
>>+static_assert(!std::formattable<std::span<NotFormattable>, wchar_t>);
>>+
>>+template<typename... Args>
>>+bool
>>+is_format_string_for(const char* str, Args&&... args)
>>+{
>>+ try {
>>+ (void) std::vformat(str, std::make_format_args(args...));
>>+ return true;
>>+ } catch (const std::format_error&) {
>>+ return false;
>>+ }
>>+}
>>+
>>+template<typename... Args>
>>+bool
>>+is_format_string_for(const wchar_t* str, Args&&... args)
>>+{
>>+ try {
>>+ (void) std::vformat(str, std::make_wformat_args(args...));
>>+ return true;
>>+ } catch (const std::format_error&) {
>>+ return false;
>>+ }
>>+}
>>+
>>+template<typename Rg, typename CharT>
>>+bool is_range_formatter_spec_for(CharT const* spec, Rg&& rg)
>>+{
>>+ using V = std::remove_cvref_t<std::ranges::range_reference_t<Rg>>;
>>+ std::range_formatter<V, CharT> fmt;
>>+ std::basic_format_parse_context<CharT> pc(spec);
>>+ try {
>>+ (void)fmt.parse(pc);
>>+ return true;
>>+ } catch (const std::format_error&) {
>>+ return false;
>>+ }
>>+}
>>+
>>+void
>>+test_format_string()
>>+{
>>+ // invalid format spec 'p'
>>+ VERIFY( !is_range_formatter_spec_for("p", std::vector<int>()) );
>>+ VERIFY( !is_format_string_for("{:p}", std::vector<int>()) );
>>+ VERIFY( !is_range_formatter_spec_for("np", std::vector<int>()) );
>>+ VERIFY( !is_format_string_for("{:np}", std::vector<int>()) );
>>+
>>+ // width needs to be integer type
>>+ VERIFY( !is_format_string_for("{:{}}", std::vector<int>(), 1.0f) );
>>+
>>+ // element format needs to be valid
>>+ VERIFY( !is_range_formatter_spec_for(":p", std::vector<int>()) );
>>+ VERIFY( !is_format_string_for("{::p}", std::vector<int>()) );
>>+ VERIFY( !is_range_formatter_spec_for("n:p", std::vector<int>()) );
>>+ VERIFY( !is_format_string_for("{:n:p}", std::vector<int>()) );
>>+}
>>+
>>+#define WIDEN_(C, S) ::std::__format::_Widen<C>(S, L##S)
>>+#define WIDEN(S) WIDEN_(_CharT, S)
>>+
>>+template<typename _CharT, typename Range>
>>+void test_output()
>>+{
>>+ using Sv = std::basic_string_view<_CharT>;
>>+ using T = std::ranges::range_value_t<Range>;
>>+ auto makeRange = [](std::span<T> s) {
>>+ return Range(s.data(), s.data() + s.size());
>>+ };
>>+
>>+ std::basic_string<_CharT> res;
>>+ size_t size = 0;
>>+
>>+ T v1[]{1, 2, 3};
>>+ res = std::format(WIDEN("{}"), makeRange(v1));
>>+ VERIFY( res == WIDEN("[1, 2, 3]") );
>>+ res = std::format(WIDEN("{:}"), makeRange(v1));
>>+ VERIFY( res == WIDEN("[1, 2, 3]") );
>>+ res = std::format(WIDEN("{:n}"), makeRange(v1));
>>+ VERIFY( res == WIDEN("1, 2, 3") );
>>+
>>+ res = std::format(WIDEN("{:3}"), makeRange(v1));
>>+ VERIFY( res == WIDEN("[1, 2, 3]") );
>>+
>>+ res = std::format(WIDEN("{:10}"), makeRange(v1));
>>+ VERIFY( res == WIDEN("[1, 2, 3] ") );
>>+
>>+ res = std::format(WIDEN("{:{}}"), makeRange(v1), 10);
>>+ VERIFY( res == WIDEN("[1, 2, 3] ") );
>>+
>>+ res = std::format(WIDEN("{1:{0}}"), 10, makeRange(v1));
>>+ VERIFY( res == WIDEN("[1, 2, 3] ") );
>>+
>>+ res = std::format(WIDEN("{:10n}"), makeRange(v1));
>>+ VERIFY( res == WIDEN("1, 2, 3 ") );
>>+
>>+ res = std::format(WIDEN("{:*<11}"), makeRange(v1));
>>+ VERIFY( res == WIDEN("[1, 2, 3]**") );
>>+
>>+ res = std::format(WIDEN("{:->12}"), makeRange(v1));
>>+ VERIFY( res == WIDEN("---[1, 2, 3]") );
>>+
>>+ res = std::format(WIDEN("{:=^13}"), makeRange(v1));
>>+ VERIFY( res == WIDEN("==[1, 2, 3]==") );
>>+
>>+ res = std::format(WIDEN("{:=^13n}"), makeRange(v1));
>>+ VERIFY( res == WIDEN("===1, 2, 3===") );
>>+
>>+ res = std::format(WIDEN("{::#x}"), makeRange(v1));
>>+ VERIFY( res == WIDEN("[0x1, 0x2, 0x3]") );
>>+
>>+ res = std::format(WIDEN("{:|^25n:#05x}"), makeRange(v1));
>>+ VERIFY( res == WIDEN("|||0x001, 0x002, 0x003|||") );
>>+
>>+ // ':' is start of the format string for element
>>+ res = std::format(WIDEN("{::^+04}"), makeRange(v1));
>>+ VERIFY( res == WIDEN("[ +1 , +2 , +3 ]") );
>>+
>>+ size = std::formatted_size(WIDEN("{:}"), makeRange(v1));
>>+ VERIFY( size == Sv(WIDEN("[1, 2, 3]")).size() );
>>+
>>+ size = std::formatted_size(WIDEN("{:3}"), makeRange(v1));
>>+ VERIFY( size == Sv(WIDEN("[1, 2, 3]")).size() );
>>+
>>+ size = std::formatted_size(WIDEN("{:10}"), makeRange(v1));
>>+ VERIFY( size == 10 );
>>+
>>+ size = std::formatted_size(WIDEN("{:|^25n:#05x}"), makeRange(v1));
>>+ VERIFY( size == 25 );
>>+}
>>+
>>+template<typename Range>
>>+void test_output_c()
>>+{
>>+ test_output<char, Range>();
>>+ test_output<wchar_t, Range>();
>>+}
>>+
>>+void
>>+test_outputs()
>>+{
>>+ using namespace __gnu_test;
>>+ test_output_c<std::vector<int>>();
>>+ test_output_c<std::list<int>>();
>>+ test_output_c<std::span<int>>();
>>+
>>+ test_output_c<test_forward_range<int>>();
>>+ test_output_c<test_input_range<int>>();
>>+ test_output_c<test_range_nocopy<int, input_iterator_wrapper_nocopy>>();
>>+
>>+ test_output_c<std::span<const int>>();
>>+ test_output_c<test_forward_range<const int>>();
>>+}
>>+
>>+void
>>+test_nested()
>>+{
>>+ std::vector<std::vector<int>> v
>>+ {
>>+ {1, 2},
>>+ {11, 12}
>>+ };
>>+
>>+ std::string res = std::format("{}", v);
>>+ VERIFY( res == "[[1, 2], [11, 12]]" );
>>+
>>+ res = std::format("{:+^18:n:02}", v);
>>+ VERIFY( res == "+[01, 02, 11, 12]+" );
>>+}
>>+
>>+int main()
>>+{
>>+ test_format_string();
>>+ test_outputs();
>>+ test_nested();
>>+}
>>diff --git a/libstdc++-v3/testsuite/std/format/ranges/string.cc b/libstdc++-v3/testsuite/std/format/ranges/string.cc
>>new file mode 100644
>>index 00000000000..7f59f59dda3
>>--- /dev/null
>>+++ b/libstdc++-v3/testsuite/std/format/ranges/string.cc
>>@@ -0,0 +1,226 @@
>>+// { dg-do run { target c++23 } }
>>+
>>+#include <format>
>>+#include <span>
>>+#include <testsuite_hooks.h>
>>+#include <testsuite_iterators.h>
>>+#include <vector>
>>+
>>+template<typename... Args>
>>+bool
>>+is_format_string_for(const char* str, Args&&... args)
>>+{
>>+ try {
>>+ (void) std::vformat(str, std::make_format_args(args...));
>>+ return true;
>>+ } catch (const std::format_error&) {
>>+ return false;
>>+ }
>>+}
>>+
>>+template<typename... Args>
>>+bool
>>+is_format_string_for(const wchar_t* str, Args&&... args)
>>+{
>>+ try {
>>+ (void) std::vformat(str, std::make_wformat_args(args...));
>>+ return true;
>>+ } catch (const std::format_error&) {
>>+ return false;
>>+ }
>>+}
>>+
>>+template<typename Rg, typename CharT>
>>+bool is_range_formatter_spec_for(CharT const* spec, Rg&& rg)
>>+{
>>+ using V = std::remove_cvref_t<std::ranges::range_reference_t<Rg>>;
>>+ std::range_formatter<V, CharT> fmt;
>>+ std::basic_format_parse_context<CharT> pc(spec);
>>+ try {
>>+ (void)fmt.parse(pc);
>>+ return true;
>>+ } catch (const std::format_error&) {
>>+ return false;
>>+ }
>>+}
>>+
>>+#define WIDEN_(C, S) ::std::__format::_Widen<C>(S, L##S)
>>+#define WIDEN(S) WIDEN_(_CharT, S)
>>+
>>+void
>>+test_format_string()
>>+{
>>+ // only CharT value types are supported
>>+ VERIFY( !is_range_formatter_spec_for(L"s", std::vector<char>()) );
>>+ VERIFY( !is_format_string_for(L"{:s}", std::vector<char>()) );
>>+ VERIFY( !is_range_formatter_spec_for(L"s", std::vector<char>()) );
>>+ VERIFY( !is_format_string_for(L"{:s}", std::vector<char>()) );
>>+ VERIFY( !is_range_formatter_spec_for("s", std::vector<int>()) );
>>+ VERIFY( !is_format_string_for("{:s}", std::vector<int>()) );
>>+
>>+ // invalid format stringss
>>+ VERIFY( !is_range_formatter_spec_for("?", std::vector<char>()) );
>>+ VERIFY( !is_format_string_for("{:?}", std::vector<char>()) );
>>+ VERIFY( !is_range_formatter_spec_for("ns", std::vector<char>()) );
>>+ VERIFY( !is_format_string_for("{:ns}", std::vector<char>()) );
>>+ VERIFY( !is_range_formatter_spec_for("s:", std::vector<char>()) );
>>+ VERIFY( !is_format_string_for("{:s:}", std::vector<char>()) );
>>+
>>+ // precision is not supported, even for s
>>+ VERIFY( !is_range_formatter_spec_for(".10s", std::vector<char>()) );
>>+ VERIFY( !is_format_string_for("{:.10s}", std::vector<char>()) );
>>+ VERIFY( !is_format_string_for("{:.{}s}", std::vector<char>(), 10) );
>>+
>>+ // width needs to be integer type
>>+ VERIFY( !is_format_string_for("{:{}s}", std::vector<char>(), 1.0f) );
>>+}
>>+
>>+template<typename Range>
>>+void test_output()
>>+{
>>+ using _CharT = std::ranges::range_value_t<Range>;
>>+ auto makeRange = [](std::basic_string<_CharT>& s) {
>>+ return Range(s.data(), s.data() + s.size());
>>+ };
>>+ std::basic_string<_CharT> res;
>>+ size_t size = 0;
>>+
>>+ std::basic_string<_CharT> s1 = WIDEN("abcd");
>>+ res = std::format(WIDEN("{}"), makeRange(s1));
>>+ VERIFY( res == WIDEN("['a', 'b', 'c', 'd']") );
>>+
>>+ res = std::format(WIDEN("{::}"), makeRange(s1));
>>+ VERIFY( res == WIDEN("[a, b, c, d]") );
>>+
>>+ res = std::format(WIDEN("{:s}"), makeRange(s1));
>>+ VERIFY( res == WIDEN("abcd") );
>>+
>>+ res = std::format(WIDEN("{:?s}"), makeRange(s1));
>>+ VERIFY( res == WIDEN(R"("abcd")") );
>>+
>>+ res = std::format(WIDEN("{:3s}"), makeRange(s1));
>>+ VERIFY( res == WIDEN("abcd") );
>>+
>>+ res = std::format(WIDEN("{:7s}"), makeRange(s1));
>>+ VERIFY( res == WIDEN("abcd ") );
>>+
>>+ res = std::format(WIDEN("{:{}s}"), makeRange(s1), 7);
>>+ VERIFY( res == WIDEN("abcd ") );
>>+
>>+ res = std::format(WIDEN("{1:{0}s}"), 7, makeRange(s1));
>>+ VERIFY( res == WIDEN("abcd ") );
>>+
>>+ res = std::format(WIDEN("{:*>6s}"), makeRange(s1));
>>+ VERIFY( res == WIDEN("**abcd") );
>>+
>>+ res = std::format(WIDEN("{:-<5s}"), makeRange(s1));
>>+ VERIFY( res == WIDEN("abcd-") );
>>+
>>+ res = std::format(WIDEN("{:=^8s}"), makeRange(s1));
>>+ VERIFY( res == WIDEN("==abcd==") );
>>+
>>+ std::basic_string<_CharT> s2(512, static_cast<_CharT>('a'));
>>+ res = std::format(WIDEN("{:=^8s}"), makeRange(s2));
>>+ VERIFY( res == s2 );
>>+
>>+ size = std::formatted_size(WIDEN("{:s}"), makeRange(s1));
>>+ VERIFY( size == 4 );
>>+
>>+ size = std::formatted_size(WIDEN("{:3s}"), makeRange(s1));
>>+ VERIFY( size == 4 );
>>+
>>+ size = std::formatted_size(WIDEN("{:7s}"), makeRange(s1));
>>+ VERIFY( size == 7 );
>>+
>>+ size = std::formatted_size(WIDEN("{:s}"), makeRange(s2));
>>+ VERIFY( size == 512 );
>>+}
>>+
>>+template<typename CharT>
>>+struct cstr_view
>>+{
>>+ cstr_view() = default;
>>+ explicit cstr_view(CharT* f, CharT* l)
>>+ : ptr(f)
>>+ { VERIFY(!*l); }
>>+
>>+ struct sentinel
>>+ {
>>+ friend constexpr
>>+ bool operator==(CharT const* ptr, sentinel) noexcept
>>+ { return !*ptr; }
>>+ };
>>+
>>+ constexpr
>>+ CharT* begin() const noexcept
>>+ { return ptr; };
>>+ static constexpr
>>+ sentinel end() noexcept
>>+ { return {}; }
>>+
>>+private:
>>+ CharT* ptr = "";
>>+};
>>+
>>+template<typename CharT>
>>+void
>>+test_outputs()
>>+{
>>+ using namespace __gnu_test;
>>+ test_output<std::vector<CharT>>();
>>+ test_output<std::span<CharT>>();
>>+ test_output<cstr_view<CharT>>();
>>+
>>+ test_output<test_forward_range<CharT>>();
>>+ test_output<test_forward_sized_range<CharT>>();
>>+
>>+ test_output<test_input_range<CharT>>();
>>+ test_output<test_input_sized_range<CharT>>();
>>+
>>+ test_output<test_range_nocopy<CharT, input_iterator_wrapper_nocopy>>();
>>+ test_output<test_sized_range<CharT, input_iterator_wrapper_nocopy>>();
>>+
>>+ test_output<std::span<const CharT>>();
>>+ test_output<cstr_view<const CharT>>();
>>+ test_output<test_forward_range<const CharT>>();
>>+
>>+ static_assert(!std::formattable<std::span<volatile CharT>, CharT>);
>>+ static_assert(!std::formattable<std::span<const volatile CharT>, CharT>);
>>+}
>>+
>>+void
>>+test_nested()
>>+{
>>+ std::string_view s1 = "str1";
>>+ std::string_view s2 = "str2";
>>+
>>+ std::vector<std::string> vs;
>>+ vs.emplace_back(s1);
>>+ vs.emplace_back(s2);
>>+
>>+ VERIFY( std::format("{}", vs) == R"(["str1", "str2"])" );
>>+ VERIFY( std::format("{:}", vs) == R"(["str1", "str2"])" );
>>+ VERIFY( std::format("{::?}", vs) == R"(["str1", "str2"])" );
>>+ VERIFY( std::format("{::}", vs) == R"([str1, str2])" );
>>+
>>+ std::vector<std::vector<char>> vv;
>>+ vv.emplace_back(s1.begin(), s1.end());
>>+ vv.emplace_back(s2.begin(), s2.end());
>>+ std::string_view escaped = R"([['s', 't', 'r', '1'], ['s', 't', 'r', '2']])";
>>+
>>+ VERIFY( std::format("{}", vv) == escaped );
>>+ VERIFY( std::format("{:}", vv) == escaped );
>>+ VERIFY( std::format("{::}", vv) == escaped );
>>+ VERIFY( std::format("{:::?}", vv) == escaped );
>>+ VERIFY( std::format("{:::}", vv) == R"([[s, t, r, 1], [s, t, r, 2]])" );
>>+ VERIFY( std::format("{::s}", vv) == R"([str1, str2])" );
>>+ VERIFY( std::format("{::?s}", vv) == R"(["str1", "str2"])" );
>>+}
>>+
>>+int main()
>>+{
>>+ test_format_string();
>>+ test_outputs<char>();
>>+ test_outputs<wchar_t>();
>>+ test_nested();
>>+}
>>--
>>2.49.0
>>
>>
More information about the Libstdc++
mailing list