https://gcc.gnu.org/g:473dde525248a694c0f4e62b31a7fc24b238c5b0

commit r15-9517-g473dde525248a694c0f4e62b31a7fc24b238c5b0
Author: Tomasz Kamiński <tkami...@redhat.com>
Date:   Mon Apr 14 16:00:57 2025 +0200

    libstdc++: Implement formatters for pair and tuple [PR109162]
    
    This patch implements formatter specializations for pair and tuple form
    P2286R8. In addition using 'm` and range_format::map (from P2585R1) for
    ranges are now supported.
    
    The formatters for pairs and tuples whose corresponding elements are the 
same
    (after applying remove_cvref_t) derive from the same __tuple_formatter 
class.
    This reduce the code duplication, as most of the parsing and formatting is 
the
    same in such cases. We use a custom reduced implementation of the tuple
    (__formatters_storage) to store the elements formatters.
    
    Handling of the padding (width and fill) options, is extracted to
    __format::__format_padded function, that is used both by __tuple_formatter 
and
    range_formatter. To reduce number of instantations range_formatter::format
    triggers, we cast incoming range to __format::__maybe_const_range<_Rg, 
_CharT>&,
    before formatting it.
    
    As in the case of previous commits, the signatures of the user-facing parse
    and format methods of the provided formatters deviate from the standard by
    constraining types of parameters:
    * _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.
    
    Finally, test for tuple-like std::array and std::ranges::subrange,
    that illustrate that they remain formatted as ranges.
    
            PR libstdc++/109162
    
    libstdc++-v3/ChangeLog:
    
            * include/std/format (__formatter_int::_M_format_character_escaped)
            (__formatter_str::format): Use __sink.out() to produce _Sink_iter.
            (__format::__const_formattable_range): Moved closer to 
range_formatter.
            (__format::__maybe_const_range): Use `__conditional_t` and moved 
closer
            to range_formatter.
            (__format::__format_padded, __format::maybe_const)
            (__format::__indexed_formatter_storage, __format::__tuple_formatter)
            (std::formatter<pair<_Fp, _Sp>, _CharT>>)
            (std::formatter<tuple<_Tps...>, _CharT): Define.
            (std::formatter<_Rg, _CharT>::format): Cast incoming range to
            __format::__maybe_const_range<_Rg, _CharT>&.
            (std::formatter<_Rg, _CharT>::_M_format): Extracted from format,
            and use __format_padded.
            (std::formatter<_Rg, _CharT>::_M_format_no_padding): Rename...
            (std::formatter<_Rg, _CharT>::_M_format_elems): ...to this.
            (std::formatter<_Rg, _CharT>::_M_format_with_padding): Extracted as
            __format_padded.
            * testsuite/util/testsuite_iterators.h (test_input_range_nocopy):
            Define.
            * testsuite/std/format/ranges/formatter.cc: Tests for `m` specifier.
            * testsuite/std/format/ranges/sequence.cc: Tests for array and 
subrange.
            * testsuite/std/format/ranges/map.cc: New test.
            * testsuite/std/format/tuple.cc: New test.
    
    Reviewed-by: Jonathan Wakely <jwak...@redhat.com>
    Signed-off-by: Tomasz Kamiński <tkami...@redhat.com>

Diff:
---
 libstdc++-v3/include/std/format                    | 377 +++++++++++++++++----
 .../testsuite/std/format/ranges/formatter.cc       |   6 +-
 libstdc++-v3/testsuite/std/format/ranges/map.cc    | 209 ++++++++++++
 .../testsuite/std/format/ranges/sequence.cc        |  52 ++-
 libstdc++-v3/testsuite/std/format/tuple.cc         | 259 ++++++++++++++
 libstdc++-v3/testsuite/util/testsuite_iterators.h  |   3 +
 6 files changed, 813 insertions(+), 93 deletions(-)

diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format
index 096dda4f989c..b1455977c65f 100644
--- a/libstdc++-v3/include/std/format
+++ b/libstdc++-v3/include/std/format
@@ -1350,8 +1350,7 @@ namespace __format
                                                    __fc, _M_spec);
 
          __format::_Str_sink<_CharT> __sink;
-         __format::_Sink_iter<_CharT> __out(__sink);
-         __format::__write_escaped(__out, __s, __term);
+         __format::__write_escaped(__sink.out(), __s, __term);
          basic_string_view<_CharT> __escaped(__sink.view().data(),
                                              __sink.view().size());
          const size_t __escaped_width = _S_trunc(__escaped, __prec);
@@ -1387,13 +1386,13 @@ namespace __format
                {
                  ranges::iterator_t<_Rg> __first = ranges::begin(__rg);
                  ranges::subrange __sub(__first, __first + __n);
-                 return format(_String(from_range, __sub), __fc);
+                 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);
+                 return format(_String(from_range, __sub), __fc);
                }
            }
          else
@@ -1698,7 +1697,7 @@ namespace __format
       template<typename _Out>
        typename basic_format_context<_Out, _CharT>::iterator
        _M_format_character_escaped(_CharT __c,
-                                  basic_format_context<_Out, _CharT>& __fc) 
const
+                                   basic_format_context<_Out, _CharT>& __fc) 
const
        {
          using _Esc = _Escapes<_CharT>;
          constexpr auto __term = __format::_Term_char::_Tc_apos;
@@ -1708,8 +1707,7 @@ namespace __format
 
          _CharT __buf[12];
          __format::_Fixedbuf_sink<_CharT> __sink(__buf);
-         __format::_Sink_iter<_CharT> __out(__sink);
-         __format::__write_escaped(__out, __in, __term);
+         __format::__write_escaped(__sink.out(), __in, __term);
 
          const basic_string_view<_CharT> __escaped = __sink.view();
          size_t __estimated_width;
@@ -3077,19 +3075,6 @@ namespace __format
     concept formattable
       = __format::__formattable_impl<remove_reference_t<_Tp>, _CharT>;
 
-  /// @cond undocumented
-namespace __format
-{
-  template<typename _Rg, typename _CharT>
-    concept __const_formattable_range
-      = ranges::input_range<const _Rg>
-         && formattable<ranges::range_reference_t<const _Rg>, _CharT>;
-
-  template<typename _Rg, typename _CharT>
-    using __maybe_const_range
-      = conditional_t<__const_formattable_range<_Rg, _CharT>, const _Rg, _Rg>;
-} // namespace __format
-  /// @endcond
 #endif // format_ranges
 
   /// An iterator after the last character written, and the number of
@@ -5209,6 +5194,246 @@ namespace __format
 /// @cond undocumented
 namespace __format
 {
+  template<typename _CharT, typename _Out, typename _Callback>
+    typename basic_format_context<_Out, _CharT>::iterator
+    __format_padded(basic_format_context<_Out, _CharT>& __fc,
+                   const _Spec<_CharT>& __spec,
+                   _Callback&& __call)
+    {
+      // This is required to implement formatting with padding,
+      // as we need to format to temporary buffer, using the same iterator.
+      static_assert(is_same_v<_Out, __format::_Sink_iter<_CharT>>);
+
+      if (__spec._M_get_width(__fc) == 0)
+       return __call(__fc);
+
+      struct _Restore_out
+      {
+       _Restore_out(basic_format_context<_Sink_iter<_CharT>, _CharT>& __fc)
+       : _M_ctx(std::addressof(__fc)), _M_out(__fc.out())
+       { }
+
+       void _M_trigger()
+       {
+         if (_M_ctx)
+           _M_ctx->advance_to(_M_out);
+         _M_ctx = nullptr;
+       }
+
+       ~_Restore_out()
+       { _M_trigger(); }
+
+      private:
+       basic_format_context<_Sink_iter<_CharT>, _CharT>* _M_ctx;
+       _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
+      // we do not need to align
+      _Str_sink<_CharT> __buf;
+      __fc.advance_to(__buf.out());
+      __call(__fc);
+      __restore._M_trigger();
+
+      basic_string_view<_CharT> __str(__buf.view());
+      size_t __width;
+      if constexpr (__unicode::__literal_encoding_is_unicode<_CharT>())
+       __width = __unicode::__field_width(__str);
+      else
+       __width = __str.size();
+
+      return __format::__write_padded_as_spec(__str, __width, __fc, __spec);
+    }
+
+  template<typename _Rg, typename _CharT>
+    concept __const_formattable_range
+      = ranges::input_range<const _Rg>
+         && formattable<ranges::range_reference_t<const _Rg>, _CharT>;
+
+  template<typename _Rg, typename _CharT>
+    using __maybe_const_range
+      = __conditional_t<__const_formattable_range<_Rg, _CharT>, const _Rg, 
_Rg>;
+
+  template<typename _Tp, typename _CharT>
+    using __maybe_const
+      = __conditional_t<formattable<const _Tp, _CharT>, const _Tp, _Tp>;
+
+  template<size_t _Pos, typename _Tp, typename _CharT>
+    struct __indexed_formatter_storage
+    {
+      constexpr void
+      _M_parse()
+      {
+       basic_format_parse_context<_CharT> __pc({});
+       if (_M_formatter.parse(__pc) != __pc.end())
+         __format::__failed_to_parse_format_spec();
+      }
+
+      template<typename _Out>
+       void
+       _M_format(__maybe_const<_Tp, _CharT>& __elem,
+                 basic_format_context<_Out, _CharT>& __fc,
+                 basic_string_view<_CharT> __sep) const
+       {
+         if constexpr (_Pos != 0)
+           __fc.advance_to(__format::__write(__fc.out(), __sep));
+         __fc.advance_to(_M_formatter.format(__elem, __fc));
+       }
+
+      [[__gnu__::__always_inline__]]
+      constexpr void
+      set_debug_format()
+      {
+       if constexpr (__has_debug_format<formatter<_Tp, _CharT>>)
+         _M_formatter.set_debug_format();
+      }
+
+    private:
+      formatter<_Tp, _CharT> _M_formatter;
+    };
+
+  template<typename _CharT, typename... _Tps>
+    class __tuple_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;
+      }
+
+      // 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 __first = __pc.begin();
+       const auto __last = __pc.end();
+       __format::_Spec<_CharT> __spec{};
+
+       auto __finished = [&]
+         {
+           if (__first != __last && *__first != '}')
+             return false;
+
+           _M_spec = __spec;
+           _M_felems._M_parse();
+           _M_felems.set_debug_format();
+           return true;
+         };
+
+       if (__finished())
+         return __first;
+
+       __first = __spec._M_parse_fill_and_align(__first, __last, "{:");
+       if (__finished())
+         return __first;
+
+       __first = __spec._M_parse_width(__first, __last, __pc);
+       if (__finished())
+         return __first;
+
+       if (*__first == 'n')
+         {
+           ++__first;
+           _M_open = _M_close = _String_view();
+         }
+       else if (*__first == 'm')
+         {
+           ++__first;
+           if constexpr (sizeof...(_Tps) == 2)
+             {
+               _M_sep = _Seps::_S_colon();
+               _M_open = _M_close = _String_view();
+             }
+           else
+             __throw_format_error("format error: 'm' specifier requires range"
+                                  " of pair or tuple of two elements");
+         }
+
+       if (__finished())
+         return __first;
+
+       __format::__failed_to_parse_format_spec();
+      }
+
+    protected:
+      template<typename _Tuple, typename _Out, size_t... _Ids>
+       typename basic_format_context<_Out, _CharT>::iterator
+       _M_format(_Tuple& __tuple, index_sequence<_Ids...>,
+                 basic_format_context<_Out, _CharT>& __fc) const
+       { return _M_format_elems(std::get<_Ids>(__tuple)..., __fc); }
+
+      template<typename _Out>
+       typename basic_format_context<_Out, _CharT>::iterator
+       _M_format_elems(__maybe_const<_Tps, _CharT>&... __elems,
+                       basic_format_context<_Out, _CharT>& __fc) const
+       {
+         return __format::__format_padded(
+                  __fc, _M_spec,
+                  [this, &__elems...](basic_format_context<_Out, _CharT>& 
__nfc)
+                    {
+                      __nfc.advance_to(__format::__write(__nfc.out(), 
_M_open));
+                      _M_felems._M_format(__elems..., __nfc, _M_sep);
+                      return __format::__write(__nfc.out(), _M_close);
+                    });
+       }
+
+    private:
+      template<size_t... _Ids>
+       struct __formatters_storage
+         : __indexed_formatter_storage<_Ids, _Tps, _CharT>...
+       {
+         template<size_t _Id, typename _Up>
+           using _Base = __indexed_formatter_storage<_Id, _Up, _CharT>;
+
+         constexpr void
+         _M_parse()
+         {
+           (_Base<_Ids, _Tps>::_M_parse(), ...);
+         }
+
+         template<typename _Out>
+           void
+           _M_format(__maybe_const<_Tps, _CharT>&... __elems,
+                     basic_format_context<_Out, _CharT>& __fc,
+                     _String_view __sep) const
+           {
+             (_Base<_Ids, _Tps>::_M_format(__elems, __fc, __sep), ...);
+           }
+
+         constexpr void
+         set_debug_format()
+         {
+           (_Base<_Ids, _Tps>::set_debug_format(), ...);
+         }
+       };
+
+      template<size_t... _Ids>
+       static auto
+       _S_create_storage(index_sequence<_Ids...>)
+         -> __formatters_storage<_Ids...>;
+      using _Formatters
+       = decltype(_S_create_storage(index_sequence_for<_Tps...>()));
+
+      _Spec<_CharT> _M_spec{};
+      _String_view _M_open = _Seps::_S_parens().substr(0, 1);
+      _String_view _M_close = _Seps::_S_parens().substr(1, 1);
+      _String_view _M_sep = _Seps::_S_comma();
+      _Formatters _M_felems;
+    };
+
   template<typename _Tp>
     concept __is_map_formattable
       = __is_pair<_Tp> || (__is_tuple_v<_Tp> && tuple_size_v<_Tp> == 2);
@@ -5216,6 +5441,46 @@ namespace __format
 } // namespace __format
 /// @endcond
 
+  // [format.tuple] Tuple formatter
+  template<__format::__char _CharT, formattable<_CharT> _Fp,
+          formattable<_CharT> _Sp>
+    struct formatter<pair<_Fp, _Sp>, _CharT>
+      : __format::__tuple_formatter<_CharT, remove_cvref_t<_Fp>,
+                                   remove_cvref_t<_Sp>>
+    {
+    private:
+      using __maybe_const_pair
+       = __conditional_t<formattable<const _Fp, _CharT>
+                         && formattable<const _Sp, _CharT>,
+                         const pair<_Fp, _Sp>, pair<_Fp, _Sp>>;
+    public:
+      // 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(__maybe_const_pair& __p,
+              basic_format_context<_Out, _CharT>& __fc) const
+       { return this->_M_format_elems(__p.first, __p.second, __fc); }
+    };
+
+  template<__format::__char _CharT, formattable<_CharT>... _Tps>
+    struct formatter<tuple<_Tps...>, _CharT>
+      : __format::__tuple_formatter<_CharT, remove_cvref_t<_Tps>...>
+    {
+    private:
+      using __maybe_const_tuple
+       = __conditional_t<(formattable<const _Tps, _CharT> && ...),
+                         const tuple<_Tps...>, tuple<_Tps...>>;
+    public:
+      // 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(__maybe_const_tuple& __t,
+              basic_format_context<_Out, _CharT>& __fc) const
+       { return this->_M_format(__t, index_sequence_for<_Tps...>(), __fc); }
+    };
+
   // [format.range.formatter], class template range_formatter
   template<typename _Tp, __format::__char _CharT = char>
     requires same_as<remove_cvref_t<_Tp>, _Tp> && formattable<_Tp, _CharT>
@@ -5369,9 +5634,16 @@ namespace __format
        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 iterator.
-         static_assert(is_same_v<_Out, __format::_Sink_iter<_CharT>>);
+         using __maybe_const_range
+           = __format::__maybe_const_range<_Rg, _CharT>;
+         return _M_format<__maybe_const_range>(__rg, __fc);
+       }
+
+    private:
+      template<ranges::input_range _Rg, typename _Out>
+       typename basic_format_context<_Out, _CharT>::iterator
+       _M_format(_Rg& __rg, basic_format_context<_Out, _CharT>& __fc) const
+       {
          if constexpr (same_as<_Tp, _CharT>)
            if (_M_spec._M_type == __format::_Pres_str
                  || _M_spec._M_type == __format::_Pres_esc)
@@ -5379,16 +5651,17 @@ namespace __format
                __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);
+         return __format::__format_padded(
+                  __fc, _M_spec,
+                  [this, &__rg](basic_format_context<_Out, _CharT>& __nfc)
+                    { return _M_format_elems(__rg, __nfc); });
        }
 
-    private:
+
       template<ranges::input_range _Rg, typename _Out>
        typename basic_format_context<_Out, _CharT>::iterator
-       _M_format_no_padding(_Rg& __rg,
-                            basic_format_context<_Out, _CharT>& __fc) const
+       _M_format_elems(_Rg& __rg,
+                       basic_format_context<_Out, _CharT>& __fc) const
        {
          auto __out = __format::__write(__fc.out(), _M_open);
 
@@ -5409,50 +5682,6 @@ namespace __format
          return __format::__write(__out, _M_close);
        }
 
-      template<ranges::input_range _Rg, 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 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);
@@ -5537,7 +5766,7 @@ namespace __format
            return _M_under._M_format_range(__rg, __fc);
          else
            return _M_under.format(__rg, __fc);
-               }
+       }
 
     private:
       using _Formatter_under
diff --git a/libstdc++-v3/testsuite/std/format/ranges/formatter.cc 
b/libstdc++-v3/testsuite/std/format/ranges/formatter.cc
index 2045b51547ad..a4f5d9210ddf 100644
--- a/libstdc++-v3/testsuite/std/format/ranges/formatter.cc
+++ b/libstdc++-v3/testsuite/std/format/ranges/formatter.cc
@@ -97,6 +97,7 @@ void
 test_override()
 {
   MyVector<_CharT, Formatter> vc{'a', 'b', 'c', 'd'};
+  MyVector<std::pair<int, int>, Formatter> vp{{1, 11}, {2, 21}};
   std::basic_string<_CharT> res;
 
   res = std::format(WIDEN("{:s}"), vc);
@@ -106,7 +107,10 @@ test_override()
   res = std::format(WIDEN("{:+^6s}"), vc);
   VERIFY( res == WIDEN("+abcd+") );
 
-  // TODO test map
+  res = std::format(WIDEN("{:m}"), vp);
+  VERIFY( res == WIDEN("{1: 11, 2: 21}") );
+  res = std::format(WIDEN("{:=^20m}"), vp);
+  VERIFY( res == WIDEN("==={1: 11, 2: 21}===") );
 }
 
 template<template<typename, typename> class Formatter>
diff --git a/libstdc++-v3/testsuite/std/format/ranges/map.cc 
b/libstdc++-v3/testsuite/std/format/ranges/map.cc
new file mode 100644
index 000000000000..34c5ed554b8b
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/format/ranges/map.cc
@@ -0,0 +1,209 @@
+// { dg-do run { target c++23 } }
+
+#include <flat_map>
+#include <format>
+#include <list>
+#include <map>
+#include <span>
+#include <testsuite_hooks.h>
+#include <testsuite_iterators.h>
+#include <vector>
+
+struct NotFormattable
+{
+  friend auto operator<=>(NotFormattable, NotFormattable) = default;
+};
+
+static_assert( !std::formattable<std::map<int, NotFormattable>, char> );
+static_assert( !std::formattable<std::map<NotFormattable, int>, 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;
+  }
+}
+
+#define WIDEN_(C, S) ::std::__format::_Widen<C>(S, L##S)
+#define WIDEN(S) WIDEN_(_CharT, S)
+
+void
+test_format_string()
+{
+  // only pair<T, U> amd tuple<T, U> value types are supported
+  VERIFY( !is_range_formatter_spec_for("m", std::vector<int>()) );
+  VERIFY( !is_format_string_for("{:m}", std::vector<int>()) );
+  VERIFY( !is_range_formatter_spec_for("m", std::vector<std::tuple<int, int, 
int>>()) );
+  VERIFY( !is_format_string_for("{:m}", std::vector<std::tuple<int, int, 
int>>()) );
+
+  // invalid format stringss
+  VERIFY( !is_range_formatter_spec_for("?m", std::vector<std::pair<int, 
int>>()) );
+  VERIFY( !is_format_string_for("{:?m}", std::vector<std::pair<int, int>>()) );
+  VERIFY( !is_range_formatter_spec_for("m:", std::vector<std::pair<int, 
int>>()) );
+  VERIFY( !is_format_string_for("{:m:}", std::vector<std::pair<int, int>>()) );
+
+  // precision is not supported
+  VERIFY( !is_range_formatter_spec_for(".10m", std::vector<std::pair<int, 
int>>()) );
+  VERIFY( !is_format_string_for("{:.10m}", std::vector<std::pair<int, int>>()) 
);
+  VERIFY( !is_format_string_for("{:.{}m}", std::vector<std::pair<int, int>>(), 
10) );
+
+  // width needs to be integer type
+  VERIFY( !is_format_string_for("{:{}m}", std::vector<std::pair<int, int>>(), 
1.0f) );
+}
+
+template<typename _CharT, typename Range>
+void test_output(bool mapIsDefault)
+{
+  using Sv = std::basic_string_view<_CharT>;
+  using Pt = std::ranges::range_value_t<Range>;
+  using Ft = std::remove_cvref_t<std::tuple_element_t<0, Pt>>;
+  using St = std::remove_cvref_t<std::tuple_element_t<1, Pt>>;
+  auto makeRange = [](std::span<Pt> s) {
+    return Range(s.data(), s.data() + s.size());
+  };
+
+  std::basic_string<_CharT> res;
+  size_t size = 0;
+
+  Ft f1[]{1, 2, 3};
+  St s1[]{11, 22, 33};
+  Pt v1[]{{f1[0], s1[0]}, {f1[1], s1[1]}, {f1[2], s1[2]}};
+
+  res = std::format(WIDEN("{}"), makeRange(v1));
+  if (mapIsDefault)
+    VERIFY( res == WIDEN("{1: 11, 2: 22, 3: 33}") );
+  else
+    VERIFY( res == WIDEN("[(1, 11), (2, 22), (3, 33)]") );
+
+  res = std::format(WIDEN("{:m}"), makeRange(v1));
+  VERIFY( res == WIDEN("{1: 11, 2: 22, 3: 33}") );
+  res = std::format(WIDEN("{:nm}"), makeRange(v1));
+  VERIFY( res == WIDEN("1: 11, 2: 22, 3: 33") );
+
+  res = std::format(WIDEN("{:3m}"), makeRange(v1));
+  VERIFY( res == WIDEN("{1: 11, 2: 22, 3: 33}") );
+
+  res = std::format(WIDEN("{:25m}"), makeRange(v1));
+  VERIFY( res == WIDEN("{1: 11, 2: 22, 3: 33}    ") );
+
+  res = std::format(WIDEN("{:{}m}"), makeRange(v1), 25);
+  VERIFY( res == WIDEN("{1: 11, 2: 22, 3: 33}    ") );
+
+  res = std::format(WIDEN("{1:{0}m}"), 25, makeRange(v1));
+  VERIFY( res == WIDEN("{1: 11, 2: 22, 3: 33}    ") );
+
+  res = std::format(WIDEN("{:25nm}"), makeRange(v1));
+  VERIFY( res == WIDEN("1: 11, 2: 22, 3: 33      ") );
+
+  res = std::format(WIDEN("{:*<23m}"), makeRange(v1));
+  VERIFY( res == WIDEN("{1: 11, 2: 22, 3: 33}**") );
+
+  res = std::format(WIDEN("{:->24m}"), makeRange(v1));
+  VERIFY( res == WIDEN("---{1: 11, 2: 22, 3: 33}") );
+
+  res = std::format(WIDEN("{:=^25m}"), makeRange(v1));
+  VERIFY( res == WIDEN("=={1: 11, 2: 22, 3: 33}==") );
+
+  res = std::format(WIDEN("{:=^25nm}"), makeRange(v1));
+  VERIFY( res == WIDEN("===1: 11, 2: 22, 3: 33===") );
+
+  size = std::formatted_size(WIDEN("{:m}"), makeRange(v1));
+  VERIFY( size == Sv(WIDEN("{1: 11, 2: 22, 3: 33}")).size() );
+
+  size = std::formatted_size(WIDEN("{:3m}"), makeRange(v1));
+  VERIFY( size == Sv(WIDEN("{1: 11, 2: 22, 3: 33}")).size() );
+
+  size = std::formatted_size(WIDEN("{:25m}"), makeRange(v1));
+  VERIFY( size == 25 );
+}
+
+template<class Range>
+void test_output_c(bool mapIsDefault = false)
+{
+  test_output<char, Range>(mapIsDefault);
+  test_output<wchar_t, Range>(mapIsDefault);
+}
+
+template<template<typename> class RangeT>
+void test_output_pc()
+{
+  test_output_c<RangeT<std::pair<int, int>>>();
+  test_output_c<RangeT<std::pair<const int, int>>>();
+  test_output_c<RangeT<std::tuple<const int&, int&>>>();
+}
+
+void
+test_outputs()
+{
+  using namespace __gnu_test;
+  test_output_c<std::map<int, int>>(true);
+  test_output_c<std::flat_map<int, int>>(true);
+
+  test_output_pc<std::vector>();
+  test_output_pc<std::list>();
+  test_output_pc<std::span>();
+
+  test_output_pc<test_forward_range>();
+  test_output_pc<test_input_range>();
+  test_output_pc<test_input_range_nocopy>();
+}
+
+void
+test_nested()
+{
+  std::vector<std::map<int, std::string>> vm{
+    {{1, "one"}, {2, "two"}},
+    {{1, "jeden"}, {2, "dwa"}},
+  };
+  std::string res;
+
+  res = std::format("{}", vm);
+  VERIFY( res == R"([{1: "one", 2: "two"}, {1: "jeden", 2: "dwa"}])" );
+  res = std::format("{:n:n}", vm);
+  VERIFY( res == R"(1: "one", 2: "two", 1: "jeden", 2: "dwa")" );
+
+  std::map<std::string, std::vector<std::string>> mv{
+    {"english", {"zero", "one", "two"}},
+    {"polish", {"zero", "jeden", "dwa"}},
+  };
+  res = std::format("{}", mv);
+  VERIFY( res == R"({"english": ["zero", "one", "two"], "polish": ["zero", 
"jeden", "dwa"]})" );
+}
+
+int main()
+{
+  test_format_string();
+  test_outputs();
+  test_nested();
+}
diff --git a/libstdc++-v3/testsuite/std/format/ranges/sequence.cc 
b/libstdc++-v3/testsuite/std/format/ranges/sequence.cc
index 06574379ed50..61fc68ea252d 100644
--- a/libstdc++-v3/testsuite/std/format/ranges/sequence.cc
+++ b/libstdc++-v3/testsuite/std/format/ranges/sequence.cc
@@ -1,7 +1,9 @@
 // { dg-do run { target c++23 } }
 
+#include <array>
 #include <format>
 #include <list>
+#include <ranges>
 #include <span>
 #include <testsuite_hooks.h>
 #include <testsuite_iterators.h>
@@ -73,19 +75,23 @@ test_format_string()
 #define WIDEN_(C, S) ::std::__format::_Widen<C>(S, L##S)
 #define WIDEN(S) WIDEN_(_CharT, S)
 
-template<typename _CharT, typename Range>
+template<typename _CharT, typename Range, typename Storage>
 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());
+  auto makeRange = [](Storage& s) -> Range {
+    if constexpr (std::is_same_v<std::remove_cvref_t<Range>, Storage>)
+      return s;
+    else
+      return Range(std::ranges::data(s),
+                  std::ranges::data(s) + std::ranges::size(s));
   };
 
   std::basic_string<_CharT> res;
   size_t size = 0;
 
-  T v1[]{1, 2, 3};
+  Storage v1{1, 2, 3};
   res = std::format(WIDEN("{}"), makeRange(v1));
   VERIFY( res == WIDEN("[1, 2, 3]") );
   res = std::format(WIDEN("{:}"), makeRange(v1));
@@ -143,27 +149,37 @@ void test_output()
   VERIFY( size == 25 );
 }
 
-template<typename Range>
-void test_output_c()
+template<typename Cont>
+void test_output_cont()
 {
-  test_output<char, Range>();
-  test_output<wchar_t, Range>();
+  test_output<char, Cont&, Cont>();
+  test_output<wchar_t, Cont const&, Cont>();
+}
+
+template<typename View>
+void test_output_view()
+{
+  test_output<char, View, int[3]>();
+  test_output<wchar_t, View, int[3]>();
 }
 
 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>>();
+  test_output_cont<std::vector<int>>();
+  test_output_cont<std::list<int>>();
+  test_output_cont<std::array<int, 3>>();
+
+  test_output_view<std::span<int>>();
+  test_output_view<std::ranges::subrange<int*>>();
+  test_output_view<test_forward_range<int>>();
+  test_output_view<test_input_range<int>>();
+  test_output_view<test_input_range_nocopy<int>>();
+
+  test_output_view<std::span<const int>>();
+  test_output_view<std::ranges::subrange<const int*>>();
+  test_output_view<test_forward_range<const int>>();
 }
 
 void
diff --git a/libstdc++-v3/testsuite/std/format/tuple.cc 
b/libstdc++-v3/testsuite/std/format/tuple.cc
new file mode 100644
index 000000000000..62f9d293aab5
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/format/tuple.cc
@@ -0,0 +1,259 @@
+// { dg-do run { target c++23 } }
+
+#include <format>
+#include <string>
+#include <testsuite_hooks.h>
+#include <tuple>
+#include <utility>
+
+struct NotFormattable
+{};
+
+static_assert( !std::formattable<std::pair<int, NotFormattable>, char> );
+static_assert( !std::formattable<std::tuple<int, NotFormattable, int>, 
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;
+  }
+}
+
+#define WIDEN_(C, S) ::std::__format::_Widen<C>(S, L##S)
+#define WIDEN(S) WIDEN_(_CharT, S)
+
+void
+test_format_string()
+{
+  // invalid format stringss
+  VERIFY( !is_format_string_for("{:p}", std::tuple<>()) );
+  VERIFY( !is_format_string_for("{:nm}", std::tuple<>()) );
+
+  // 'm' is only valid for 2 elemenst
+  VERIFY( !is_format_string_for("{:m}", std::tuple<>()) );
+  VERIFY( !is_format_string_for("{:m}", std::tuple<int, int, int>()) );
+
+  // element specifier is not supported
+  VERIFY( !is_format_string_for("{::}", std::tuple<>()) );
+
+  // precision is not supported
+  VERIFY( !is_format_string_for("{:.10}", std::tuple<>()) );
+
+  // width needs to be integer type
+  VERIFY( !is_format_string_for("{:{}}", std::tuple<>(), 1.0f) );
+}
+
+template<typename _CharT>
+void test_multi()
+{
+  using Sv = std::basic_string_view<_CharT>;
+  using Str = std::basic_string<_CharT>;
+
+  std::basic_string<_CharT> res;
+  std::size_t size = 0;
+  std::tuple<int, Str, float> t1(1, WIDEN("test"), 2.1);
+
+  res = std::format(WIDEN("{}"), t1);
+  VERIFY( res == WIDEN(R"((1, "test", 2.1))") );
+  res = std::format(WIDEN("{:}"), t1);
+  VERIFY( res == WIDEN(R"((1, "test", 2.1))") );
+  res = std::format(WIDEN("{:n}"), t1);
+  VERIFY( res == WIDEN(R"(1, "test", 2.1)") );
+
+  res = std::format(WIDEN("{:3}"), t1);
+  VERIFY( res == WIDEN(R"((1, "test", 2.1))") );
+
+  res = std::format(WIDEN("{:20}"), t1);
+  VERIFY( res == WIDEN(R"((1, "test", 2.1)    )") );
+
+  res = std::format(WIDEN("{:{}}"), t1, 20);
+  VERIFY( res == WIDEN(R"((1, "test", 2.1)    )") );
+
+  res = std::format(WIDEN("{1:{0}}"), 20, t1);
+  VERIFY( res == WIDEN(R"((1, "test", 2.1)    )") );
+
+  res = std::format(WIDEN("{:^>17}"), t1);
+  VERIFY( res == WIDEN(R"(^(1, "test", 2.1))") );
+
+  res = std::format(WIDEN("{:$<18}"), t1);
+  VERIFY( res == WIDEN(R"((1, "test", 2.1)$$)") );
+
+  res = std::format(WIDEN("{:+^19}"), t1);
+  VERIFY( res == WIDEN(R"(+(1, "test", 2.1)++)") );
+
+  res = std::format(WIDEN("{:|^19n}"), t1);
+  VERIFY( res == WIDEN(R"(||1, "test", 2.1|||)") );
+
+  size = std::formatted_size(WIDEN("{}"), t1);
+  VERIFY( size == Sv(WIDEN(R"((1, "test", 2.1))")).size() );
+
+  size = std::formatted_size(WIDEN("{:3}"), t1);
+  VERIFY( size == Sv(WIDEN(R"((1, "test", 2.1))")).size() );
+
+  size = std::formatted_size(WIDEN("{:20}"), t1);
+  VERIFY( size == 20 );
+
+  std::tuple<int&, Str&, float&> t2 = t1;
+  res = std::format(WIDEN("{}"), t2);
+  VERIFY( res == WIDEN(R"((1, "test", 2.1))") );
+
+  std::tuple<int, int, int, int> t3(1, 2, 3, 4);
+  res = std::format(WIDEN("{}"), t3);
+  VERIFY( res == WIDEN(R"((1, 2, 3, 4))") );
+
+}
+
+template<typename _CharT, typename Tuple>
+void test_empty()
+{
+  std::basic_string<_CharT> res;
+
+  Tuple e1;
+  res = std::format(WIDEN("{}"), e1);
+  VERIFY( res == WIDEN(R"(())") );
+
+  res = std::format(WIDEN("{:}"), e1);
+  VERIFY( res == WIDEN(R"(())") );
+
+  res = std::format(WIDEN("{:n}"), e1);
+  VERIFY( res == WIDEN(R"()") );
+
+  res = std::format(WIDEN("{:^>6}"), e1);
+  VERIFY( res == WIDEN(R"(^^^^())") );
+}
+
+template<typename _CharT, typename Pair>
+void test_pair()
+{
+  using Ft = std::remove_cvref_t<std::tuple_element_t<0, Pair>>;
+  using St = std::remove_cvref_t<std::tuple_element_t<1, Pair>>;
+
+  std::basic_string<_CharT> res;
+
+  Ft f1 = 1;
+  St s1 = WIDEN("abc");
+  Pair p1(f1, s1);
+
+  res = std::format(WIDEN("{}"), p1);
+  VERIFY( res == WIDEN(R"((1, "abc"))") );
+
+  res = std::format(WIDEN("{:}"), p1);
+  VERIFY( res == WIDEN(R"((1, "abc"))") );
+
+  res = std::format(WIDEN("{:m}"), p1);
+  VERIFY( res == WIDEN(R"(1: "abc")") );
+
+  res = std::format(WIDEN("{:|^12m}"), p1);
+  VERIFY( res == WIDEN(R"(||1: "abc"||)") );
+}
+
+template<typename CharT, template<typename, typename> class PairT>
+void test_pair_e()
+{
+  test_pair<CharT, PairT<int, std::basic_string<CharT>>>();
+  test_pair<CharT, PairT<int, const CharT*>>();
+  test_pair<CharT, PairT<const int, std::basic_string<CharT>>>();
+  test_pair<CharT, PairT<int&, std::basic_string<CharT>&>>();
+  test_pair<CharT, PairT<const int&, const std::basic_string<CharT>&>>();
+}
+
+template<typename Pair>
+struct MyPair : Pair
+{
+  using Pair::Pair;
+};
+
+template<typename Pair, typename CharT>
+struct std::formatter<MyPair<Pair>, 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 MyPair<Pair>& mp,
+        std::basic_format_context<Out, CharT>& fc) const
+  { return _formatter.format(mp, fc); }
+
+private:
+  std::formatter<Pair, CharT> _formatter;
+};
+
+template<typename _CharT, template<typename, typename> class PairT>
+void test_custom()
+{
+  std::basic_string<_CharT> res;
+  MyPair<PairT<int, const _CharT*>> c1(1, WIDEN("abc"));
+
+  res = std::format(WIDEN("{}"), c1);
+  VERIFY( res == WIDEN(R"(<1; "abc">)") );
+
+  res = std::format(WIDEN("{:}"), c1);
+  VERIFY( res == WIDEN(R"(<1; "abc">)") );
+
+  res = std::format(WIDEN("{:n}"), c1);
+  VERIFY( res == WIDEN(R"(1; "abc")") );
+
+  res = std::format(WIDEN("{:m}"), c1);
+  VERIFY( res == WIDEN(R"(1: "abc")") );
+
+  res = std::format(WIDEN("{:|^14}"), c1);
+  VERIFY( res == WIDEN(R"(||<1; "abc">||)") );
+}
+
+template<typename CharT>
+void test_outputs()
+{
+  test_multi<CharT>();
+  test_empty<CharT, std::tuple<>>();
+  test_pair_e<CharT, std::pair>();
+  test_pair_e<CharT, std::tuple>();
+  test_custom<CharT, std::pair>();
+  test_custom<CharT, std::tuple>();
+}
+
+void test_nested()
+{
+  std::string res;
+  std::tuple<std::tuple<>, std::pair<int, std::string>> tt{{}, {1, "abc"}};
+
+  res = std::format("{}", tt);
+  VERIFY( res == R"(((), (1, "abc")))" );
+  res = std::format("{:n}", tt);
+  VERIFY( res == R"((), (1, "abc"))" );
+  res = std::format("{:m}", tt);
+  VERIFY( res == R"((): (1, "abc"))" );
+}
+
+int main()
+{
+  test_format_string();
+  test_outputs<char>();
+  test_outputs<wchar_t>();
+  test_nested();
+}
diff --git a/libstdc++-v3/testsuite/util/testsuite_iterators.h 
b/libstdc++-v3/testsuite/util/testsuite_iterators.h
index 20539ecaca61..74a87395cd7d 100644
--- a/libstdc++-v3/testsuite/util/testsuite_iterators.h
+++ b/libstdc++-v3/testsuite/util/testsuite_iterators.h
@@ -891,6 +891,9 @@ namespace __gnu_test
   template<typename T>
     using test_input_range
       = test_range<T, input_iterator_wrapper>;
+  template<typename T>
+    using test_input_range_nocopy
+      = test_range_nocopy<T, input_iterator_wrapper_nocopy>;
   template<typename T>
     using test_output_range
       = test_range<T, output_iterator_wrapper>;

Reply via email to