There are few whitespace changes that are caused by me mass removing spaces
that are followed by tabs.
They seem to affect only the code that was recently added by me in debug
string and range support, so I think it is ok to keep them.

On Wed, Apr 16, 2025 at 9:16 AM Tomasz Kamiński <tkami...@redhat.com> wrote:

> 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++/PR109162
>
> libstdc++-v3/ChangeLog:
>
>         * include/std/format (__formatter_int::_M_format_character_escaped)
>         (__formatter_str::format): Use __sink.out() to produce _Sink_iter.
>         (__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.
> ---
> Testing on x86_64-linux, tests matched by `*format*` passes.
> OK for trunk? Should I wait for 16?
>
>  libstdc++-v3/include/std/format               | 357 +++++++++++++++---
>  .../testsuite/std/format/ranges/formatter.cc  |   6 +-
>  .../testsuite/std/format/ranges/map.cc        | 209 ++++++++++
>  .../testsuite/std/format/ranges/sequence.cc   |  52 ++-
>  libstdc++-v3/testsuite/std/format/tuple.cc    | 259 +++++++++++++
>  .../testsuite/util/testsuite_iterators.h      |   3 +
>  6 files changed, 806 insertions(+), 80 deletions(-)
>  create mode 100644 libstdc++-v3/testsuite/std/format/ranges/map.cc
>  create mode 100644 libstdc++-v3/testsuite/std/format/tuple.cc
>
> diff --git a/libstdc++-v3/include/std/format
> b/libstdc++-v3/include/std/format
> index 096dda4f989..5b93eb8bc2d 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;
> @@ -5209,6 +5207,237 @@ 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(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<_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.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 _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 : formatter<_Tp, _CharT>
> +    {
> +      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 of 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 +5445,48 @@ 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 +5640,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(static_cast<__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 +5657,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 +5688,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 +5772,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 2045b51547a..a4f5d9210dd 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 00000000000..34c5ed554b8
> --- /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 06574379ed5..61fc68ea252 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 00000000000..62f9d293aab
> --- /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 20539ecaca6..74a87395cd7 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>;
> --
> 2.49.0
>
>

Reply via email to