Just to clarify, we still will be missing the formatter for adaptors
(stack, queue, piority_queue).

On Wed, Apr 16, 2025 at 10:04 AM Jonathan Wakely <jwak...@redhat.com> wrote:

> On Wed, 16 Apr 2025 at 08:15, 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?
>
> I'd like to get this in trunk before we branch for gcc-15, if possible.
>
> >
> >  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())
>
> std::addressof
>
> > +       { }
> > +
> > +       void trigger()
>
> This is not a reserved name.
>
> I'll continue the review ASAP ...
>
> > +       {
> > +         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