[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