libstdc++: Implement C++20 <format> [PR104166]
authorJonathan Wakely <jwakely@redhat.com>
Tue, 18 Oct 2022 20:20:06 +0000 (21:20 +0100)
committerJonathan Wakely <jwakely@redhat.com>
Sun, 13 Nov 2022 01:10:44 +0000 (01:10 +0000)
This doesn't add the newer C++23 features like formatting ranges
and escaped string prsentation types.

However, C++23 extended floating-point types are supported, as are
128-bit integers.

It could do with more tests.

libstdc++-v3/ChangeLog:

PR libstdc++/104166
* include/Makefile.am (std_headers): Add <format>.
* include/Makefile.in: Regenerate.
* include/precompiled/stdc++.h: Add <format>.
* include/std/format: New file.
* python/libstdcxx/v6/printers.py (StdFormatArgsPrinter): New
printer for std::format_args.
* testsuite/std/format/arguments/args.cc: New test.
* testsuite/std/format/error.cc: New test.
* testsuite/std/format/formatter.cc: New test.
* testsuite/std/format/functions/format.cc: New test.
* testsuite/std/format/functions/format_to_n.cc: New test.
* testsuite/std/format/functions/size.cc: New test.
* testsuite/std/format/functions/vformat_to.cc: New test.
* testsuite/std/format/parse_ctx.cc: New test.
* testsuite/std/format/string.cc: New test.
* testsuite/std/format/string_neg.cc: New test.

15 files changed:
libstdc++-v3/include/Makefile.am
libstdc++-v3/include/Makefile.in
libstdc++-v3/include/precompiled/stdc++.h
libstdc++-v3/include/std/format [new file with mode: 0644]
libstdc++-v3/python/libstdcxx/v6/printers.py
libstdc++-v3/testsuite/std/format/arguments/args.cc [new file with mode: 0644]
libstdc++-v3/testsuite/std/format/error.cc [new file with mode: 0644]
libstdc++-v3/testsuite/std/format/formatter.cc [new file with mode: 0644]
libstdc++-v3/testsuite/std/format/functions/format.cc [new file with mode: 0644]
libstdc++-v3/testsuite/std/format/functions/format_to_n.cc [new file with mode: 0644]
libstdc++-v3/testsuite/std/format/functions/size.cc [new file with mode: 0644]
libstdc++-v3/testsuite/std/format/functions/vformat_to.cc [new file with mode: 0644]
libstdc++-v3/testsuite/std/format/parse_ctx.cc [new file with mode: 0644]
libstdc++-v3/testsuite/std/format/string.cc [new file with mode: 0644]
libstdc++-v3/testsuite/std/format/string_neg.cc [new file with mode: 0644]

index 96137a6621ae2714bac42c980ab789fbe5ac07ae..27dfa2be2f39d4e1e8897e0bbf02c59dc941133f 100644 (file)
@@ -68,6 +68,7 @@ std_headers = \
        ${std_srcdir}/deque \
        ${std_srcdir}/execution \
        ${std_srcdir}/filesystem \
+       ${std_srcdir}/format \
        ${std_srcdir}/forward_list \
        ${std_srcdir}/fstream \
        ${std_srcdir}/future \
index be3e12a6f1b147a12e0f74a991186f4a2249be02..64621922f779904dd63513ea41b40fd3711cb557 100644 (file)
@@ -424,6 +424,7 @@ std_freestanding = \
 @GLIBCXX_HOSTED_TRUE@  ${std_srcdir}/deque \
 @GLIBCXX_HOSTED_TRUE@  ${std_srcdir}/execution \
 @GLIBCXX_HOSTED_TRUE@  ${std_srcdir}/filesystem \
+@GLIBCXX_HOSTED_TRUE@  ${std_srcdir}/format \
 @GLIBCXX_HOSTED_TRUE@  ${std_srcdir}/forward_list \
 @GLIBCXX_HOSTED_TRUE@  ${std_srcdir}/fstream \
 @GLIBCXX_HOSTED_TRUE@  ${std_srcdir}/future \
index 815768994731fe33ee40db9d7d2732ed132c8865..b447feb844f91bde5c365c4007cb50a32dc1c767 100644 (file)
 #include <bit>
 #include <compare>
 #include <concepts>
+#include <format>
 #include <latch>
 #include <numbers>
 #include <ranges>
diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format
new file mode 100644 (file)
index 0000000..1796362
--- /dev/null
@@ -0,0 +1,3926 @@
+// <format> Formatting -*- C++ -*-
+
+// Copyright The GNU Toolchain Authors.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// Under Section 7 of GPL version 3, you are granted additional
+// permissions described in the GCC Runtime Library Exception, version
+// 3.1, as published by the Free Software Foundation.
+
+// You should have received a copy of the GNU General Public License and
+// a copy of the GCC Runtime Library Exception along with this program;
+// see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
+// <http://www.gnu.org/licenses/>.
+
+/** @file include/format
+ *  This is a Standard C++ Library header.
+ */
+
+#ifndef _GLIBCXX_FORMAT
+#define _GLIBCXX_FORMAT 1
+
+#pragma GCC system_header
+
+#include <bits/requires_hosted.h> // for std::string
+
+#if __cplusplus >= 202002L
+
+#include <array>
+#include <charconv>
+#include <concepts>
+#include <limits>
+#include <locale>
+#include <optional>
+#include <span>
+#include <string_view>
+#include <string>
+#include <variant>            // monostate (TODO: move to bits/utility.h?)
+#include <bits/ranges_base.h>  // input_range, range_reference_t
+#include <bits/ranges_algobase.h> // ranges::copy
+#include <bits/stl_iterator.h> // back_insert_iterator
+#include <bits/stl_pair.h>     // __is_pair
+#include <bits/utility.h>      // tuple_size_v
+#include <ext/numeric_traits.h> // __int_traits
+
+namespace std _GLIBCXX_VISIBILITY(default)
+{
+_GLIBCXX_BEGIN_NAMESPACE_VERSION
+
+// 201907 Text Formatting, Integration of chrono, printf corner cases.
+// 202106 std::format improvements.
+// 202110 Fixing locale handling in chrono formatters, generator-like types.
+// 202207 Encodings in localized formatting of chrono, basic-format-string.
+#define __cpp_lib_format 202106L
+
+#if __cplusplus > 202002L
+// 202207 P2286R8 Formatting Ranges
+// 202207 P2585R1 Improving default container formatting
+// TODO: #define __cpp_lib_format_ranges 202207L
+#endif
+
+  // [format.context], class template basic_format_context
+  template<typename _Out, typename _CharT> class basic_format_context;
+
+/// @cond undocumented
+namespace __format
+{
+  // Type-erased character sink.
+  template<typename _CharT> struct _Sink;
+  // Output iterator that writes to a type-erase character sink.
+  template<typename _CharT>
+    class _Sink_iter;
+} // namespace __format
+/// @endcond
+
+  using format_context
+    = basic_format_context<__format::_Sink_iter<char>, char>;
+  using wformat_context
+    = basic_format_context<__format::_Sink_iter<wchar_t>, wchar_t>;
+
+  // [format.args], class template basic_format_args
+  template<typename _Context> class basic_format_args;
+  using format_args = basic_format_args<format_context>;
+  using wformat_args = basic_format_args<wformat_context>;
+
+  // [format.arguments], arguments
+  // [format.arg], class template basic_format_arg
+  template<typename _Context>
+    class basic_format_arg;
+
+  // [format.fmt.string], class template basic_format_string
+
+  /** A compile-time checked format string for the specified argument types.
+   *
+   * @since C++23 but available as an extension in C++20.
+   */
+  template<typename _CharT, typename... _Args>
+    struct basic_format_string
+    {
+      template<convertible_to<basic_string_view<_CharT>> _Tp>
+       consteval
+       basic_format_string(const _Tp& __s);
+
+      [[__gnu__::__always_inline__]]
+      constexpr basic_string_view<_CharT>
+      get() const noexcept
+      { return _M_str; }
+
+    private:
+      basic_string_view<_CharT> _M_str;
+    };
+
+  template<typename... _Args>
+    using format_string = basic_format_string<char, type_identity_t<_Args>...>;
+
+  template<typename... _Args>
+    using wformat_string
+      = basic_format_string<wchar_t, type_identity_t<_Args>...>;
+
+  // [format.formatter], formatter
+
+  /// The primary template of std::formatter is disabled.
+  template<typename _Tp, typename _CharT = char>
+    struct formatter
+    {
+      formatter() = delete;
+      formatter(const formatter&) = delete;
+      formatter& operator=(const formatter&) = delete;
+    };
+
+  // [format.error], class format_error
+  class format_error : public runtime_error
+  {
+  public:
+    explicit format_error(const string& __what) : runtime_error(__what) { }
+    explicit format_error(const char* __what) : runtime_error(__what) { }
+  };
+
+  /// @cond undocumented
+  [[noreturn]]
+  inline void
+  __throw_format_error(const char* __what)
+  { _GLIBCXX_THROW_OR_ABORT(format_error(__what)); }
+
+namespace __format
+{
+  // XXX use named functions for each constexpr error?
+
+  [[noreturn]]
+  inline void
+  __unmatched_left_brace_in_format_string()
+  { __throw_format_error("format error: unmatched '{' in format string"); }
+
+  [[noreturn]]
+  inline void
+  __unmatched_right_brace_in_format_string()
+  { __throw_format_error("format error: unmatched '}' in format string"); }
+
+  [[noreturn]]
+  inline void
+  __conflicting_indexing_in_format_string()
+  { __throw_format_error("format error: conflicting indexing style in format string"); }
+
+  [[noreturn]]
+  inline void
+  __invalid_arg_id_in_format_string()
+  { __throw_format_error("format error: invalid arg-id in format string"); }
+
+  [[noreturn]]
+  inline void
+  __failed_to_parse_format_spec()
+  { __throw_format_error("format error: failed to parse format-spec"); }
+} // namespace __format
+/// @endcond
+
+  // [format.parse.ctx], class template basic_format_parse_context
+  template<typename _CharT> class basic_format_parse_context;
+  using format_parse_context = basic_format_parse_context<char>;
+  using wformat_parse_context = basic_format_parse_context<wchar_t>;
+
+  template<typename _CharT>
+    class basic_format_parse_context
+    {
+    public:
+      using char_type = _CharT;
+      using const_iterator = typename basic_string_view<_CharT>::const_iterator;
+      using iterator = const_iterator;
+
+      constexpr explicit
+      basic_format_parse_context(basic_string_view<_CharT> __fmt,
+                                size_t __num_args = 0) noexcept
+      : _M_begin(__fmt.begin()), _M_end(__fmt.end()), _M_num_args(__num_args)
+      { }
+
+      basic_format_parse_context(const basic_format_parse_context&) = delete;
+      void operator=(const basic_format_parse_context&) = delete;
+
+      constexpr const_iterator begin() const noexcept { return _M_begin; }
+      constexpr const_iterator end() const noexcept { return _M_end; }
+
+      constexpr void
+      advance_to(const_iterator __it) noexcept
+      { _M_begin = __it; }
+
+      constexpr size_t
+      next_arg_id()
+      {
+       if (_M_indexing == _Manual)
+         __format::__conflicting_indexing_in_format_string();
+       _M_indexing = _Auto;
+       // if (std::is_constant_evaluated()) // XXX skip runtime check?
+         if (_M_next_arg_id == _M_num_args)
+           __format::__invalid_arg_id_in_format_string();
+       return _M_next_arg_id++;
+      }
+
+      constexpr void
+      check_arg_id(size_t __id)
+      {
+       if (_M_indexing == _Auto)
+         __format::__conflicting_indexing_in_format_string();
+       _M_indexing = _Manual;
+
+       // if (std::is_constant_evaluated()) // XXX skip runtime check?
+         if (__id >= _M_num_args)
+           __format::__invalid_arg_id_in_format_string();
+      }
+
+    private:
+      iterator _M_begin;
+      iterator _M_end;
+      enum _Indexing { _Unknown, _Manual, _Auto };
+      _Indexing _M_indexing = _Unknown;
+      size_t _M_next_arg_id = 0;
+      size_t _M_num_args;
+    };
+
+/// @cond undocumented
+  template<typename _Tp, template<typename...> class _Class>
+    static constexpr bool __is_specialization_of = false;
+  template<template<typename...> class _Class, typename... _Args>
+    static constexpr bool __is_specialization_of<_Class<_Args...>, _Class>
+      = true;
+
+namespace __format
+{
+  // pre: first != last
+  template<typename _CharT>
+    constexpr pair<unsigned short, const _CharT*>
+    __parse_integer(const _CharT* __first, const _CharT* __last)
+    {
+      if (__first == __last)
+       __builtin_unreachable();
+
+      // TODO: use this loop unconditionally?
+      // Most integers used for arg-id, width or precision will be small.
+      if (is_constant_evaluated())
+       {
+         auto __next = __first;
+         unsigned short __val = 0;
+         while (__next != __last && '0' <= *__next && *__next <= '9')
+           {
+             __val = (__val * 10) + (*__next - '0'); // TODO check overflow?
+             ++__next;
+           }
+         if (__next == __first)
+           return {0, nullptr};
+         return {__val, __next};
+       }
+
+      unsigned short __val = 0;
+      if constexpr (is_same_v<_CharT, char>)
+       {
+         auto [ptr, ec] = std::from_chars(__first, __last, __val);
+         if (ec == errc{})
+           return {__val, ptr};
+         return {0, nullptr};
+       }
+      else
+       {
+         constexpr size_t __n = 32;
+         char __buf[__n]{};
+         for (int __i = 0; __i < __n && __first != __last; ++__i)
+           __buf[__i] = __first[__i];
+         auto [__v, __ptr] = __format::__parse_integer(__buf, __buf + __n);
+         return {__v, __first + (__ptr - __buf)};
+       }
+    }
+
+  template<typename _CharT>
+    constexpr pair<unsigned short, const _CharT*>
+    __parse_arg_id(const _CharT* __first, const _CharT* __last)
+    {
+      if (__first == __last)
+       __builtin_unreachable();
+
+      if (*__first == '0')
+       return {0, __first + 1}; // No leading zeros allowed, so '0...' == 0
+
+      if ('1' <= *__first && *__first <= '9')
+       {
+         const unsigned short __id = *__first - '0';
+         const auto __next = __first + 1;
+         // Optimize for most likely case of single digit arg-id.
+         if (__next == __last || !('0' <= *__next && *__next <= '9'))
+           return {__id, __next};
+         else
+           return __format::__parse_integer(__first, __last);
+       }
+      return {0, nullptr};
+    }
+
+  enum _Pres_type {
+    _Pres_none = 0, // Default type (not valid for integer presentation types).
+    // Presentation types for integral types (including bool and charT).
+    _Pres_d = 1, _Pres_b, _Pres_B, _Pres_o, _Pres_x, _Pres_X, _Pres_c,
+    // Presentation types for floating-point types.
+    _Pres_a = 1, _Pres_A, _Pres_e, _Pres_E, _Pres_f, _Pres_g, _Pres_G,
+    _Pres_p = 0, _Pres_P,   // For pointers.
+    _Pres_s = 0,            // For strings and bool.
+    _Pres_esc = 0xf,        // For strings and charT.
+  };
+
+  enum _Align {
+    _Align_default,
+    _Align_left,
+    _Align_right,
+    _Align_centre,
+  };
+
+  enum _Sign {
+    _Sign_default,
+    _Sign_plus,
+    _Sign_minus,  // XXX does this need to be distinct from _Sign_default?
+    _Sign_space,
+  };
+
+  enum _WidthPrec {
+    _WP_none,    // No width/prec specified.
+    _WP_value,   // Fixed width/prec specified.
+    _WP_from_arg // Use a formatting argument for width/prec.
+  };
+
+  template<typename _Context>
+    size_t
+    __int_from_arg(const basic_format_arg<_Context>& __arg);
+
+  template<typename _CharT>
+    struct _Spec
+    {
+      _Align     _M_align : 2;
+      _Sign      _M_sign : 2;
+      unsigned   _M_alt : 1;
+      unsigned   _M_localized : 1;
+      unsigned   _M_zero_fill : 1;
+      _WidthPrec _M_width_kind : 2;
+      _WidthPrec _M_prec_kind : 2;
+      _Pres_type _M_type : 4;
+      unsigned short _M_width;
+      unsigned short _M_prec;
+      _CharT _M_fill = ' ';
+
+      using iterator = typename basic_string_view<_CharT>::iterator;
+
+      static constexpr _Align
+      _S_align(_CharT __c) noexcept
+      {
+       switch (__c)
+       {
+         case '<': return _Align_left;
+         case '>': return _Align_right;
+         case '^': return _Align_centre;
+         default: return _Align_default;
+       }
+      }
+
+      // pre: __first != __last
+      constexpr iterator
+      _M_parse_fill_and_align(iterator __first, iterator __last) noexcept
+      {
+       if (*__first != '{')
+         {
+           // TODO: accept any UCS scalar value as fill character.
+           // If narrow source encoding is UTF-8 then accept multibyte char.
+           if (__last - __first >= 2)
+             {
+               if (_Align __align = _S_align(__first[1]))
+                 {
+                   _M_fill = *__first;
+                   _M_align = __align;
+                   return __first + 2;
+                 }
+             }
+
+           if (_Align __align = _S_align(__first[0]))
+             {
+               _M_fill = ' ';
+               _M_align = __align;
+               return __first + 1;
+             }
+         }
+       return __first;
+      }
+
+      static constexpr _Sign
+      _S_sign(_CharT __c) noexcept
+      {
+       switch (__c)
+       {
+         case '+': return _Sign_plus;
+         case '-': return _Sign_minus;
+         case ' ': return _Sign_space;
+         default:  return _Sign_default;
+       }
+      }
+
+      // pre: __first != __last
+      constexpr iterator
+      _M_parse_sign(iterator __first, iterator) noexcept
+      {
+       if (_Sign __sign = _S_sign(*__first))
+         {
+           _M_sign = __sign;
+           return __first + 1;
+         }
+       return __first;
+      }
+
+      // pre: *__first is valid
+      constexpr iterator
+      _M_parse_alternate_form(iterator __first, iterator) noexcept
+      {
+       if (*__first == '#')
+         {
+           _M_alt = true;
+           ++__first;
+         }
+       return __first;
+      }
+
+      // pre: __first != __last
+      constexpr iterator
+      _M_parse_zero_fill(iterator __first, iterator /* __last */) noexcept
+      {
+       if (*__first == '0')
+         {
+           _M_zero_fill = true;
+           ++__first;
+         }
+       return __first;
+      }
+
+      // pre: __first != __last
+      static constexpr iterator
+      _S_parse_width_or_precision(iterator __first, iterator __last,
+                                 unsigned short& __val, bool& __arg_id,
+                                 basic_format_parse_context<_CharT>& __pc)
+      {
+       if (std::isdigit(*__first))
+         {
+           auto [__v, __ptr] = __format::__parse_integer(__first, __last);
+           if (!__ptr)
+             __throw_format_error("format error: invalid width or precision "
+                                  "in format-spec");
+           __first = __ptr;
+           __val = __v;
+         }
+       else if (*__first == '{')
+         {
+           __arg_id = true;
+           ++__first;
+           if (__first == __last)
+             __format::__unmatched_left_brace_in_format_string();
+           if (*__first == '}')
+             __val = __pc.next_arg_id();
+           else
+             {
+               auto [__v, __ptr] = __format::__parse_arg_id(__first, __last);
+               if (__ptr == nullptr || __ptr == __last || *__ptr != '}')
+                 __format::__invalid_arg_id_in_format_string();
+               __first = __ptr;
+               __pc.check_arg_id(__v);
+               __val = __v;
+             }
+           ++__first; // past the '}'
+         }
+       return __first;
+      }
+
+      // pre: __first != __last
+      constexpr iterator
+      _M_parse_width(iterator __first, iterator __last,
+                    basic_format_parse_context<_CharT>& __pc)
+      {
+       bool __arg_id = false;
+       if (*__first == '0')
+         __throw_format_error("format error: width must be non-zero in "
+                              "format string");
+       auto __next = _S_parse_width_or_precision(__first, __last, _M_width,
+                                                 __arg_id, __pc);
+       if (__next != __first)
+         _M_width_kind = __arg_id ? _WP_from_arg : _WP_value;
+       return __next;
+      }
+
+      // pre: __first != __last
+      constexpr iterator
+      _M_parse_precision(iterator __first, iterator __last,
+                        basic_format_parse_context<_CharT>& __pc)
+      {
+       if (__first[0] != '.')
+         return __first;
+
+       ++__first;
+       bool __arg_id = false;
+       auto __next = _S_parse_width_or_precision(__first, __last, _M_prec,
+                                                 __arg_id, __pc);
+       if (__next == __first)
+         __throw_format_error("format error: missing precision after '.' in "
+                              "format string");
+       _M_prec_kind = __arg_id ? _WP_from_arg : _WP_value;
+       return __next;
+      }
+
+      // pre: __first != __last
+      constexpr iterator
+      _M_parse_locale(iterator __first, iterator /* __last */) noexcept
+      {
+       if (*__first == 'L')
+         {
+           _M_localized = true;
+           ++__first;
+         }
+       return __first;
+      }
+
+      template<typename _Context>
+       size_t
+       _M_get_width(_Context& __ctx) const
+       {
+         size_t __width = 0;
+         if (_M_width_kind == _WP_value)
+           __width = _M_width;
+         else if (_M_width_kind == _WP_from_arg)
+           __width = __format::__int_from_arg(__ctx.arg(_M_width));
+         return __width;
+       }
+
+      template<typename _Context>
+       size_t
+       _M_get_precision(_Context& __ctx) const
+       {
+         size_t __prec = -1;
+         if (_M_prec_kind == _WP_value)
+           __prec = _M_prec;
+         else if (_M_prec_kind == _WP_from_arg)
+           __prec = __format::__int_from_arg(__ctx.arg(_M_prec));
+         return __prec;
+       }
+    };
+
+  template<typename _Int>
+    inline char*
+    __put_sign(_Int __i, _Sign __sign, char* __dest) noexcept
+    {
+      if (__i < 0)
+       *__dest = '-';
+      else if (__sign == _Sign_plus)
+       *__dest = '+';
+      else if (__sign == _Sign_space)
+       *__dest = ' ';
+      else
+       ++__dest;
+      return __dest;
+    }
+
+  template<typename _Out, typename _CharT>
+    requires output_iterator<_Out, const _CharT&>
+    inline _Out
+    __write(_Out __out, basic_string_view<_CharT> __str)
+    {
+      if constexpr (is_same_v<_Out, _Sink_iter<_CharT>>)
+       {
+         if (__str.size())
+           __out = __str;
+       }
+      else
+       for (_CharT __c : __str)
+         *__out++ = __c;
+      return std::move(__out);
+    }
+
+  // Write STR to OUT with NFILL copies of FILL_CHAR specified by ALIGN.
+  // pre: __align != _Align_default
+  template<typename _Out, typename _CharT>
+    _Out
+    __write_padded(_Out __out, basic_string_view<_CharT> __str,
+                  _Align __align, size_t __nfill, _CharT __fill_char)
+    {
+      const size_t __buflen = 0x20;
+      _CharT __padding_chars[__buflen];
+      basic_string_view<_CharT> __padding{__padding_chars, __buflen};
+
+      auto __pad = [&__padding] (size_t __n, _Out& __o) {
+       if (__n == 0)
+         return;
+       while (__n > __padding.size())
+         {
+           __o = __format::__write(std::move(__o), __padding);
+           __n -= __padding.size();
+         }
+       if (__n != 0)
+         __o = __format::__write(std::move(__o), __padding.substr(0, __n));
+      };
+
+      size_t __l, __r, __max;
+      if (__align == _Align_centre)
+       {
+         __l = __nfill / 2;
+         __r = __l + (__nfill & 1);
+         __max = __r;
+       }
+      else if (__align == _Align_right)
+       {
+         __l = __nfill;
+         __r = 0;
+         __max = __l;
+       }
+      else
+       {
+         __l = 0;
+         __r = __nfill;
+         __max = __r;
+       }
+      if (__max < __buflen)
+       __padding.remove_suffix(__buflen - __max);
+      else
+       __max = __buflen;
+      char_traits<_CharT>::assign(__padding_chars, __max, __fill_char);
+
+      __pad(__l, __out);
+      __out = __format::__write(std::move(__out), __str);
+      __pad(__r, __out);
+
+      return std::move(__out);
+    }
+
+  // A lightweight optional<locale>.
+  struct _Optional_locale
+  {
+    [[__gnu__::__always_inline__]]
+    _Optional_locale() : _M_dummy(), _M_hasval(false) { }
+
+    _Optional_locale(const locale& __loc) noexcept
+    : _M_loc(__loc), _M_hasval(true)
+    { }
+
+    _Optional_locale(const _Optional_locale& __l) noexcept
+    : _M_dummy(), _M_hasval(__l._M_hasval)
+    {
+      if (_M_hasval)
+       std::construct_at(&_M_loc, __l._M_loc);
+    }
+
+    _Optional_locale&
+    operator=(const _Optional_locale& __l) noexcept
+    {
+      if (_M_hasval)
+       {
+         if (__l._M_hasval)
+           _M_loc = __l._M_loc;
+         else
+           {
+             _M_loc.~locale();
+             _M_hasval = false;
+           }
+       }
+      else if (__l._M_hasval)
+       {
+         std::construct_at(&_M_loc, __l._M_loc);
+         _M_hasval = true;
+       }
+      return *this;
+    }
+
+    ~_Optional_locale() { if (_M_hasval) _M_loc.~locale(); }
+
+    _Optional_locale&
+    operator=(locale&& __loc) noexcept
+    {
+      if (_M_hasval)
+       _M_loc = std::move(__loc);
+      else
+       {
+         std::construct_at(&_M_loc, std::move(__loc));
+         _M_hasval = true;
+       }
+      return *this;
+    }
+
+    const locale&
+    value() noexcept
+    {
+      if (!_M_hasval)
+       {
+         std::construct_at(&_M_loc);
+         _M_hasval = true;
+       }
+      return _M_loc;
+    }
+
+    bool has_value() const noexcept { return _M_hasval; }
+
+    union {
+      char _M_dummy = '\0';
+      std::locale _M_loc;
+    };
+    bool _M_hasval = false;
+  };
+
+  template<typename _CharT>
+    concept __char = same_as<_CharT, char> || same_as<_CharT, wchar_t>;
+
+  template<__char _CharT>
+    struct __formatter_str
+    {
+      constexpr typename basic_format_parse_context<_CharT>::iterator
+      parse(basic_format_parse_context<_CharT>& __pc)
+      {
+       auto __first = __pc.begin();
+       const auto __last = __pc.end();
+       _Spec<_CharT> __spec{};
+
+       auto __finalize = [this, &__spec] {
+         _M_spec = __spec;
+       };
+
+       auto __finished = [&] {
+         if (__first == __last || *__first == '}')
+           {
+             __finalize();
+             return true;
+           }
+         return false;
+       };
+
+       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;
+
+       __first = __spec._M_parse_precision(__first, __last, __pc);
+       if (__finished())
+         return __first;
+
+       if (*__first == 's')
+         ++__first;
+#if __cpp_lib_format_ranges
+       else if (*__first == '?')
+         {
+           __spec._M_type = _Pres_esc;
+           ++__first;
+         }
+#endif
+
+       if (__finished())
+         return __first;
+
+       __format::__failed_to_parse_format_spec();
+      }
+
+      template<typename _Out>
+       typename basic_format_context<_Out, _CharT>::iterator
+       format(basic_string_view<_CharT> __s,
+              basic_format_context<_Out, _CharT>& __fc) const
+       {
+         if (_M_spec._M_type == _Pres_esc)
+           {
+             // TODO: C++20 escaped string presentation
+           }
+
+         if (_M_spec._M_width_kind == _WP_none
+               && _M_spec._M_prec_kind == _WP_none)
+           return __format::__write(__fc.out(), __s);
+
+         size_t __estimated_width = __s.size(); // TODO: Unicode-aware estim.
+
+         if (_M_spec._M_prec_kind != _WP_none)
+           {
+             size_t __prec = _M_spec._M_get_precision(__fc);
+             if (__estimated_width > __prec)
+               {
+                 __s = __s.substr(0, __prec); // TODO: do not split code points
+                 __estimated_width = __prec;
+               }
+           }
+
+         size_t __width = _M_spec._M_get_width(__fc);
+
+         if (__width <= __estimated_width)
+           return __format::__write(__fc.out(), __s);
+
+         const size_t __nfill = __width - __estimated_width;
+         _Align __align = _M_spec._M_align ? _M_spec._M_align : _Align_left;
+
+         return __format::__write_padded(__fc.out(), __s,
+                                         __align, __nfill, _M_spec._M_fill);
+       }
+
+#if __cpp_lib_format_ranges
+      constexpr void
+      set_debug_format() noexcept
+      { _M_spec._M_type = _Pres_esc; }
+#endif
+
+    private:
+      _Spec<_CharT> _M_spec{};
+    };
+
+  template<__char _CharT>
+    struct __formatter_int
+    {
+      // If no presentation type is specified, meaning of "none" depends
+      // whether we are formatting an integer or a char or a bool.
+      static constexpr _Pres_type _AsInteger = _Pres_d;
+      static constexpr _Pres_type _AsBool = _Pres_s;
+      static constexpr _Pres_type _AsChar = _Pres_c;
+
+      constexpr typename basic_format_parse_context<_CharT>::iterator
+      _M_do_parse(basic_format_parse_context<_CharT>& __pc, _Pres_type __type)
+      {
+       _Spec<_CharT> __spec{};
+       __spec._M_type = __type;
+
+       const auto __last = __pc.end();
+       auto __first = __pc.begin();
+
+       auto __finalize = [this, &__spec] {
+         _M_spec = __spec;
+       };
+
+       auto __finished = [&] {
+         if (__first == __last || *__first == '}')
+           {
+             __finalize();
+             return true;
+           }
+         return false;
+       };
+
+       if (__finished())
+         return __first;
+
+       __first = __spec._M_parse_fill_and_align(__first, __last);
+       if (__finished())
+         return __first;
+
+       __first = __spec._M_parse_sign(__first, __last);
+       if (__finished())
+         return __first;
+
+       __first = __spec._M_parse_alternate_form(__first, __last);
+       if (__finished())
+         return __first;
+
+       __first = __spec._M_parse_zero_fill(__first, __last);
+       if (__finished())
+         return __first;
+
+       __first = __spec._M_parse_width(__first, __last, __pc);
+       if (__finished())
+         return __first;
+
+       __first = __spec._M_parse_locale(__first, __last);
+       if (__finished())
+         return __first;
+
+       switch (*__first)
+       {
+         case 'b':
+           __spec._M_type = _Pres_b;
+           ++__first;
+           break;
+         case 'B':
+           __spec._M_type = _Pres_B;
+           ++__first;
+           break;
+         case 'c':
+           // _GLIBCXX_RESOLVE_LIB_DEFECTS
+           // 3586. format should not print bool with 'c'
+           if (__type != _AsBool)
+             {
+               __spec._M_type = _Pres_c;
+               ++__first;
+             }
+           break;
+         case 'd':
+           __spec._M_type = _Pres_d;
+           ++__first;
+           break;
+         case 'o':
+           __spec._M_type = _Pres_o;
+           ++__first;
+           break;
+         case 'x':
+           __spec._M_type = _Pres_x;
+           ++__first;
+           break;
+         case 'X':
+           __spec._M_type = _Pres_X;
+           ++__first;
+           break;
+         case 's':
+           if (__type == _AsBool)
+             {
+               __spec._M_type = _Pres_s; // same value (and meaning) as "none"
+               ++__first;
+             }
+           break;
+#if __cpp_lib_format_ranges
+         case '?':
+           if (__type == _AsChar)
+             {
+               __spec._M_type = _Pres_esc;
+               ++__first;
+             }
+#endif
+           break;
+         }
+
+       if (__finished())
+         return __first;
+
+       __format::__failed_to_parse_format_spec();
+      }
+
+      template<typename _Tp>
+       constexpr typename basic_format_parse_context<_CharT>::iterator
+       _M_parse(basic_format_parse_context<_CharT>& __pc)
+       {
+         if constexpr (is_same_v<_Tp, bool>)
+           {
+             auto __end = _M_do_parse(__pc, _AsBool);
+             if (_M_spec._M_type == _Pres_s)
+               if (_M_spec._M_sign || _M_spec._M_alt || _M_spec._M_zero_fill)
+                 __throw_format_error("format error: format-spec contains "
+                                      "invalid formatting options for "
+                                      "'bool'");
+             return __end;
+           }
+         else if constexpr (__char<_Tp>)
+           {
+             auto __end = _M_do_parse(__pc, _AsChar);
+             if (_M_spec._M_type == _Pres_c || _M_spec._M_type == _Pres_esc)
+               if (_M_spec._M_sign || _M_spec._M_alt || _M_spec._M_zero_fill
+                     /* XXX should be invalid? || _M_spec._M_localized */)
+                 __throw_format_error("format error: format-spec contains "
+                                      "invalid formatting options for "
+                                      "'charT'");
+             return __end;
+           }
+         else
+           return _M_do_parse(__pc, _AsInteger);
+       }
+
+      template<typename _Int, typename _Out>
+       typename basic_format_context<_Out, _CharT>::iterator
+       format(_Int __i, basic_format_context<_Out, _CharT>& __fc) const
+       {
+         if (_M_spec._M_type == _Pres_c)
+           return _M_format_character(_S_to_character(__i), __fc);
+
+         char __buf[sizeof(_Int) * __CHAR_BIT__ + 3];
+         to_chars_result __res{};
+
+         string_view __base_prefix;
+         make_unsigned_t<_Int> __u;
+         if (__i < 0)
+           __u = -static_cast<make_unsigned_t<_Int>>(__i);
+         else
+           __u = __i;
+
+         char* __start = __buf + 3;
+         char* const __end = __buf + sizeof(__buf);
+         char* const __start_digits = __start;
+
+         switch (_M_spec._M_type)
+         {
+           case _Pres_b:
+           case _Pres_B:
+             __base_prefix = _M_spec._M_type == _Pres_b ? "0b" : "0B";
+             __res = to_chars(__start, __end, __u, 2);
+             break;
+#if 0
+           case _Pres_c:
+             return _M_format_character(_S_to_character(__i), __fc);
+#endif
+           case _Pres_none:
+             // Should not reach here with _Pres_none for bool or charT, so:
+             [[fallthrough]];
+           case _Pres_d:
+             __res = to_chars(__start, __end, __u, 10);
+             break;
+           case _Pres_o:
+             if (__i != 0)
+               __base_prefix = "0";
+             __res = to_chars(__start, __end, __u, 8);
+             break;
+           case _Pres_x:
+           case _Pres_X:
+             __base_prefix = _M_spec._M_type == _Pres_x ? "0x" : "0X";
+             __res = to_chars(__start, __end, __u, 16);
+             if (_M_spec._M_type == _Pres_X)
+               for (auto __p = __start; __p != __res.ptr; ++__p)
+                 *__p = __builtin_toupper(*__p);
+             break;
+           default:
+             __builtin_unreachable();
+         }
+
+         if (_M_spec._M_alt && __base_prefix.size())
+           {
+             __start -= __base_prefix.size();
+             __builtin_memcpy(__start, __base_prefix.data(),
+                              __base_prefix.size());
+           }
+         __start = __format::__put_sign(__i, _M_spec._M_sign, __start - 1);
+
+         return _M_format_int(string_view(__start, __res.ptr - __start),
+                              __start_digits - __start, __fc);
+       }
+
+      template<typename _Out>
+       typename basic_format_context<_Out, _CharT>::iterator
+       format(bool __i, basic_format_context<_Out, _CharT>& __fc) const
+       {
+         if (_M_spec._M_type == _Pres_c)
+           return _M_format_character(static_cast<unsigned char>(__i), __fc);
+         if (_M_spec._M_type != _Pres_s)
+           return format(static_cast<unsigned char>(__i), __fc);
+
+         basic_string<_CharT> __s;
+         size_t __est_width;
+         if (_M_spec._M_localized) [[unlikely]]
+           {
+             auto& __np = std::use_facet<numpunct<_CharT>>(__fc.locale());
+             __s = __i ? __np.truename() : __np.falsename();
+             __est_width = __s.size(); // TODO Unicode-aware estimate
+           }
+         else
+           {
+             if constexpr (is_same_v<char, _CharT>)
+               __s = __i ? "true" : "false";
+             else
+               __s = __i ? L"true" : L"false";
+             __est_width = __s.size();
+           }
+
+         return _M_format_str(__s, __est_width, __fc);
+       }
+
+      template<typename _Out>
+       typename basic_format_context<_Out, _CharT>::iterator
+       _M_format_character(_CharT __c,
+                     basic_format_context<_Out, _CharT>& __fc) const
+       { return _M_format_str({&__c, 1u}, 1, __fc); }
+
+      template<typename _Out>
+       typename basic_format_context<_Out, _CharT>::iterator
+       _M_format_str(basic_string_view<_CharT> __str, size_t __est_width,
+                     basic_format_context<_Out, _CharT>& __fc) const
+       {
+         // TODO: this is identical to last part of __formatter_str::format
+         // so refactor to reuse the same code.
+
+         size_t __width = _M_spec._M_get_width(__fc);
+
+         if (__width <= __est_width)
+           return __format::__write(__fc.out(), __str);
+
+         size_t __nfill = __width - __est_width;
+         _Align __align = _M_spec._M_align ? _M_spec._M_align : _Align_left;
+         return __format::__write_padded(__fc.out(), __str,
+                                         __align, __nfill, _M_spec._M_fill);
+       }
+
+      template<typename _Int>
+       static _CharT
+       _S_to_character(_Int __i)
+       {
+         using _Traits = __gnu_cxx::__int_traits<_CharT>;
+         if constexpr (is_signed_v<_Int> == is_signed_v<_CharT>)
+           {
+             if (_Traits::__min <= __i && __i <= _Traits::__max)
+               return static_cast<_CharT>(__i);
+           }
+         else if constexpr (is_signed_v<_Int>)
+           {
+             if (__i >= 0 && make_unsigned_t<_Int>(__i) <= _Traits::__max)
+               return static_cast<_CharT>(__i);
+           }
+         else if (__i <= make_unsigned_t<_CharT>(_Traits::__max))
+           return static_cast<_CharT>(__i);
+         __throw_format_error("format error: integer not representable as "
+                              "character");
+       }
+
+      template<typename _Out>
+       typename basic_format_context<_Out, _CharT>::iterator
+       _M_format_int(string_view __narrow_str, size_t __prefix_len,
+                     basic_format_context<_Out, _CharT>& __fc) const
+       {
+         size_t __width = _M_spec._M_get_width(__fc);
+
+         _Optional_locale __loc;
+
+         basic_string_view<_CharT> __str;
+         if constexpr (is_same_v<char, _CharT>)
+           __str = __narrow_str;
+         else
+           {
+             __loc = __fc.locale();
+             auto& __ct = use_facet<ctype<_CharT>>(__loc.value());
+             size_t __n = __narrow_str.size();
+             auto __p = (_CharT*)__builtin_alloca(__n * sizeof(_CharT));
+             __ct.widen(__narrow_str.data(), __narrow_str.data() + __n, __p);
+             __str = {__p, __n};
+           }
+
+         if (_M_spec._M_localized)
+           {
+             if constexpr (is_same_v<char, _CharT>)
+               __loc = __fc.locale();
+             const auto& __l = __loc.value();
+             if (__l.name() != "C")
+               {
+                 auto& __np = use_facet<numpunct<_CharT>>(__l);
+                 string __grp = __np.grouping();
+                 if (!__grp.empty())
+                   {
+                     size_t __n = __str.size();
+                     auto __p = (_CharT*)__builtin_alloca(2 * __n
+                                                            * sizeof(_CharT));
+                     auto __end = std::__add_grouping(__p,
+                                                      __np.thousands_sep(),
+                                                      __grp.data(),
+                                                      __grp.size(),
+                                                      __str.data(),
+                                                      __str.data() + __n);
+                     __str = {__p, size_t(__end - __p)};
+                   }
+               }
+           }
+
+         if (__width <= __str.size())
+           return __format::__write(__fc.out(), __str);
+
+         _CharT __fill_char = _M_spec._M_fill;
+         _Align __align = _M_spec._M_align;
+
+         size_t __nfill = __width - __str.size();
+         auto __out = __fc.out();
+         if (__align == _Align_default)
+           {
+             __align = _Align_right;
+             if (_M_spec._M_zero_fill)
+               {
+                 __fill_char = _CharT('0');
+                 // Write sign and base prefix before zero filling.
+                 if (__prefix_len != 0)
+                   {
+                     __out = __format::__write(std::move(__out),
+                                               __str.substr(0, __prefix_len));
+                     __str.remove_prefix(__prefix_len);
+                   }
+               }
+             else
+               __fill_char = _CharT(' ');
+           }
+         return __format::__write_padded(std::move(__out), __str,
+                                         __align, __nfill, __fill_char);
+       }
+
+#if defined __SIZEOF_INT128__ && defined __STRICT_ANSI__
+      template<typename _Tp>
+       using make_unsigned_t
+         = typename __conditional_t<(sizeof(_Tp) <= sizeof(long long)),
+                                    std::make_unsigned<_Tp>,
+                                    type_identity<unsigned __int128>>::type;
+
+      // std::to_chars is not overloaded for int128 in strict mode.
+      template<typename _Int>
+       static to_chars_result
+       to_chars(char* __first, char* __last, _Int __value, int __base)
+       { return std::__to_chars_i<_Int>(__first, __last, __value, __base); }
+#endif
+
+      _Spec<_CharT> _M_spec{};
+    };
+
+#ifdef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT
+# define _GLIBCXX_FORMAT_F128 1
+  using __float128_t = __ieee128;
+#elif defined _GLIBCXX_LDOUBLE_IS_IEEE_BINARY128
+# define _GLIBCXX_FORMAT_F128 1
+  using __float128_t = long double;
+#elif __FLT128_DIG__
+# define _GLIBCXX_FORMAT_F128 2
+  using __float128_t = _Float128;
+#else
+# undef _GLIBCXX_FORMAT_F128
+#endif
+
+#ifdef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT
+  template<typename _Tp>
+    concept __extended_floating_point = __is_same(_Tp, _Float128)
+                                         || __is_same(_Tp, __ibm128)
+                                         || __is_same(_Tp, __ieee128);
+#elif _GLIBCXX_FORMAT_F128
+  template<typename _Tp>
+    concept __extended_floating_point = __is_same(_Tp, __float128_t);
+#else
+  template<typename _Tp>
+    concept __extended_floating_point = false;
+#endif
+
+  template<typename _Tp>
+    concept __floating_point = std::floating_point<_Tp>
+                                || __extended_floating_point<_Tp>;
+
+  using std::to_chars;
+
+#if _GLIBCXX_FORMAT_F128 == 2 \
+  && (__cplusplus == 202002L || !defined(_GLIBCXX_HAVE_FLOAT128_MATH))
+  // These overloads exist in the library, but are not declared for C++20.
+  // Make them available as std::__format::to_chars.
+  to_chars_result
+  to_chars(char*, char*, _Float128) noexcept
+    __asm("_ZSt8to_charsPcS_DF128_");
+
+  to_chars_result
+  to_chars(char*, char*, _Float128, chars_format) noexcept
+    __asm("_ZSt8to_charsPcS_DF128_St12chars_format");
+
+  to_chars_result
+  to_chars(char*, char*, _Float128, chars_format, int) noexcept
+    __asm("_ZSt8to_charsPcS_DF128_St12chars_formati");
+#endif
+
+  template<__char _CharT>
+    struct __formatter_fp
+    {
+      constexpr typename basic_format_parse_context<_CharT>::iterator
+      parse(basic_format_parse_context<_CharT>& __pc)
+      {
+       _Spec<_CharT> __spec{};
+       const auto __last = __pc.end();
+       auto __first = __pc.begin();
+
+       auto __finalize = [this, &__spec] {
+         _M_spec = __spec;
+       };
+
+       auto __finished = [&] {
+         if (__first == __last || *__first == '}')
+           {
+             __finalize();
+             return true;
+           }
+         return false;
+       };
+
+       if (__finished())
+         return __first;
+
+       __first = __spec._M_parse_fill_and_align(__first, __last);
+       if (__finished())
+         return __first;
+
+       __first = __spec._M_parse_sign(__first, __last);
+       if (__finished())
+         return __first;
+
+       __first = __spec._M_parse_alternate_form(__first, __last);
+       if (__finished())
+         return __first;
+
+       __first = __spec._M_parse_zero_fill(__first, __last);
+       if (__finished())
+         return __first;
+
+       if (__first[0] != '.')
+         {
+           __first = __spec._M_parse_width(__first, __last, __pc);
+           if (__finished())
+             return __first;
+         }
+
+       __first = __spec._M_parse_precision(__first, __last, __pc);
+       if (__finished())
+         return __first;
+
+       __first = __spec._M_parse_locale(__first, __last);
+       if (__finished())
+         return __first;
+
+       switch (*__first)
+       {
+         case 'a':
+           __spec._M_type = _Pres_a;
+           ++__first;
+           break;
+         case 'A':
+           __spec._M_type = _Pres_A;
+           ++__first;
+           break;
+         case 'e':
+           __spec._M_type = _Pres_e;
+           ++__first;
+           break;
+         case 'E':
+           __spec._M_type = _Pres_E;
+           ++__first;
+           break;
+         case 'f':
+         case 'F':
+           __spec._M_type = _Pres_f;
+           ++__first;
+           break;
+         case 'g':
+           __spec._M_type = _Pres_g;
+           ++__first;
+           break;
+         case 'G':
+           __spec._M_type = _Pres_G;
+           ++__first;
+           break;
+         }
+
+       if (__finished())
+         return __first;
+
+       __format::__failed_to_parse_format_spec();
+      }
+
+      template<typename _Fp, typename _Out>
+       typename basic_format_context<_Out, _CharT>::iterator
+       format(_Fp __v, basic_format_context<_Out, _CharT>& __fc) const
+       {
+         std::string __dynbuf;
+         char __buf[128];
+         to_chars_result __res{};
+
+         size_t __prec = 6;
+         bool __use_prec = _M_spec._M_prec_kind != _WP_none;
+         if (__use_prec)
+           __prec = _M_spec._M_get_precision(__fc);
+
+         char* __start = __buf + 1; // reserve space for sign
+         char* __end = __buf + sizeof(__buf);
+
+         chars_format __fmt{};
+         bool __upper = false;
+         bool __trailing_zeros = false;
+         char __expc = 0;
+
+         switch (_M_spec._M_type)
+         {
+           case _Pres_A:
+             __upper = true;
+             [[fallthrough]];
+           case _Pres_a:
+             __expc = 'p';
+             __fmt = chars_format::hex;
+             break;
+           case _Pres_E:
+             __upper = true;
+             [[fallthrough]];
+           case _Pres_e:
+             __expc = 'e';
+             __use_prec = true;
+             __fmt = chars_format::scientific;
+             break;
+           case _Pres_f:
+             __use_prec = true;
+             __fmt = chars_format::fixed;
+             break;
+           case _Pres_G:
+             __upper = true;
+             [[fallthrough]];
+           case _Pres_g:
+             __trailing_zeros = true;
+             __expc = 'e';
+             __use_prec = true;
+             __fmt = chars_format::general;
+             break;
+           case _Pres_none:
+             if (__use_prec)
+               __fmt = chars_format::general;
+             break;
+         }
+
+         // Write value into buffer using std::to_chars.
+         auto __to_chars = [&](char* __b, char* __e) {
+           if (__use_prec)
+             return __format::to_chars(__b, __e, __v, __fmt, __prec);
+           else if (__fmt != chars_format{})
+             return __format::to_chars(__b, __e, __v, __fmt);
+           else
+             return __format::to_chars(__b, __e, __v);
+         };
+
+         // First try using stack buffer.
+         __res = __to_chars(__start, __end);
+
+         if (__builtin_expect(__res.ec == errc::value_too_large, 0))
+           {
+             // If the buffer is too small it's probably because of a large
+             // precision, or a very large value in fixed format.
+             size_t __guess =  __prec + sizeof(__buf);
+             if (__fmt == chars_format::fixed)
+               __guess += max((int)__builtin_log10(__builtin_abs(__v)) / 2, 1);
+             __dynbuf.reserve(__guess);
+
+             do
+               {
+                 auto __overwrite = [&__to_chars, &__res] (char* __p, size_t __n)
+                 {
+                   __res = __to_chars(__p + 1, __p + __n - 1);
+                   return __res.ec == errc{} ? __res.ptr - __p : 0;
+                 };
+
+                 _S_resize_and_overwrite(__dynbuf, __dynbuf.capacity() * 2,
+                                         __overwrite);
+                 __start = __dynbuf.data() + 1; // reserve space for sign
+                 __end = __dynbuf.data() + __dynbuf.size();
+               }
+             while (__builtin_expect(__res.ec == errc::value_too_large, 0));
+         }
+
+         // Use uppercase for 'A', 'E', and 'G' formats.
+         if (__upper)
+           {
+             for (char* __p = __start; __p != __res.ptr; ++__p)
+               *__p = std::toupper(*__p);
+             __expc = std::toupper(__expc);
+           }
+
+         // Add sign for non-negative values.
+         if (!__builtin_signbit(__v))
+           {
+             if (_M_spec._M_sign == _Sign_plus)
+               *--__start = '+';
+             else if (_M_spec._M_sign == _Sign_space)
+               *--__start = ' ';
+           }
+
+         string_view __narrow_str(__start, __res.ptr - __start);
+
+         // Use alternate form.
+         if (_M_spec._M_alt && __builtin_isfinite(__v))
+           {
+             string_view __s = __narrow_str;
+             size_t __z = 0;
+             size_t __p;
+             size_t __d = __s.find('.');
+             size_t __sigfigs;
+             if (__d != __s.npos)
+               {
+                 __p = __s.find(__expc, __d + 1);
+                 if (__p == __s.npos)
+                   __p = __s.size();
+                 __sigfigs = __p - 1;
+               }
+             else
+               {
+                 __p = __s.find(__expc);
+                 if (__p == __s.npos)
+                   __p = __s.size();
+                 __d = __p;
+                 __sigfigs = __d;
+               }
+
+             if (__trailing_zeros)
+               {
+                 if (!isxdigit(__s[0]))
+                   --__sigfigs;
+                 __z = __prec - __sigfigs;
+               }
+
+             if (size_t __extras = int(__d == __p) + __z)
+               {
+                 if (__dynbuf.empty() && __extras <= (__end - __res.ptr))
+                   {
+                     // Move exponent to make space for extra chars.
+                     __builtin_memmove(__start + __p + __extras,
+                                       __start + __p,
+                                       __s.size() - __p);
+
+                     if (__d == __p)
+                       __start[__p++] = '.';
+                     __builtin_memset(__start + __p, '0', __z);
+                     __narrow_str = {__s.data(), __s.size() + __extras};
+                   }
+                 else
+                   {
+                     __dynbuf.reserve(__s.size() + __extras);
+                     if (__dynbuf.empty())
+                       {
+                         __dynbuf = __s.substr(0, __p);
+                         if (__d == __p)
+                           __dynbuf += '.';
+                         if (__z)
+                           __dynbuf.append(__z, '0');
+                       }
+                     else
+                       {
+                         __dynbuf.insert(__p, __extras, '0');
+                         if (__d == __p)
+                           __dynbuf[__p] = '.';
+                       }
+                     __narrow_str = __dynbuf;
+                   }
+               }
+           }
+
+         // TODO move everything below to a new member function that
+         // doesn't depend on _Fp type.
+
+
+         _Optional_locale __loc;
+         basic_string_view<_CharT> __str;
+         basic_string<_CharT> __wstr;
+         if constexpr (is_same_v<_CharT, char>)
+           __str = __narrow_str;
+         else
+           {
+             __loc = __fc.locale();
+             auto& __ct = use_facet<ctype<_CharT>>(__loc.value());
+             const char* __data = __narrow_str.data();
+             auto __overwrite = [&__data, &__ct](_CharT* __p, size_t __n)
+             {
+               __ct.widen(__data, __data + __n, __p);
+               return __n;
+             };
+             _S_resize_and_overwrite(__wstr, __narrow_str.size(), __overwrite);
+             __str = __wstr;
+           }
+
+         if (_M_spec._M_localized)
+           {
+             if constexpr (is_same_v<char, _CharT>)
+               __wstr = _M_localize(__str, __expc, __fc.locale());
+             else
+               __wstr = _M_localize(__str, __expc, __loc.value());
+             __str = __wstr;
+           }
+
+         size_t __width = _M_spec._M_get_width(__fc);
+
+         if (__width <= __str.size())
+           return __format::__write(__fc.out(), __str);
+
+         _CharT __fill_char = _M_spec._M_fill;
+         _Align __align = _M_spec._M_align;
+
+         size_t __nfill = __width - __str.size();
+         auto __out = __fc.out();
+         if (__align == _Align_default)
+           {
+             __align = _Align_right;
+             if (_M_spec._M_zero_fill && __builtin_isfinite(__v))
+               {
+                 __fill_char = _CharT('0');
+                 // Write sign before zero filling.
+                 if (!isxdigit(__narrow_str[0]))
+                   {
+                     *__out++ = __str[0];
+                     __str.remove_prefix(1);
+                   }
+               }
+             else
+               __fill_char = _CharT(' ');
+           }
+         return __format::__write_padded(std::move(__out), __str,
+                                         __align, __nfill, __fill_char);
+       }
+
+      // Locale-specific format.
+      basic_string<_CharT>
+      _M_localize(basic_string_view<_CharT> __str, char __expc,
+                 const locale& __loc) const
+      {
+       basic_string<_CharT> __lstr;
+
+       if (__loc == locale::classic())
+         return __lstr; // Nothing to do.
+
+       const auto& __np = use_facet<numpunct<_CharT>>(__loc);
+       const _CharT __point = __np.decimal_point();
+       const string __grp = __np.grouping();
+
+       _CharT __dot, __exp;
+       if constexpr (is_same_v<_CharT, char>)
+         {
+           __dot = '.';
+           __exp = __expc;
+         }
+       else
+         {
+           const auto& __ct = use_facet<ctype<_CharT>>(__loc);
+           __dot = __ct.widen('.');
+           __exp = __ct.widen(__expc);
+         }
+
+       if (__grp.empty() && __point == __dot)
+         return __lstr; // Locale uses '.' and no grouping.
+
+       size_t __d = __str.find(__dot);
+       size_t __e = min(__d, __str.find(__exp));
+       if (__e == __str.npos)
+         __e = __str.size();
+       const size_t __r = __str.size() - __e;
+       auto __overwrite = [&](_CharT* __p, size_t) {
+         auto __end = std::__add_grouping(__p, __np.thousands_sep(),
+                                          __grp.data(), __grp.size(),
+                                          __str.data(), __str.data() + __e);
+         if (__r)
+           {
+             if (__d != __str.npos)
+               {
+                 *__end = __point;
+                 ++__end;
+                 ++__e;
+               }
+             if (__r > 1)
+               __end += __str.copy(__end, __str.npos, __e);
+           }
+         return (__end - __p);
+       };
+       _S_resize_and_overwrite(__lstr, __e * 2 + __r, __overwrite);
+       return __lstr;
+      }
+
+      template<typename _Ch, typename _Func>
+       static void
+       _S_resize_and_overwrite(basic_string<_Ch>& __str, size_t __n, _Func __f)
+       {
+#if __cpp_lib_string_resize_and_overwrite
+         __str.resize_and_overwrite(__n, __f);
+#else
+         __str.resize(__n);
+         __str.resize(__f(__str.data(), __n));
+#endif
+       }
+
+      _Spec<_CharT> _M_spec{};
+    };
+
+} // namespace __format
+/// @endcond
+
+  // Format a character.
+  template<__format::__char _CharT>
+    struct formatter<_CharT, _CharT>
+    {
+      formatter() = default;
+
+      constexpr typename basic_format_parse_context<_CharT>::iterator
+      parse(basic_format_parse_context<_CharT>& __pc)
+      {
+       return _M_f.template _M_parse<_CharT>(__pc);
+      }
+
+      template<typename _Out>
+       typename basic_format_context<_Out, _CharT>::iterator
+       format(_CharT __u, basic_format_context<_Out, _CharT>& __fc) const
+       {
+         if (_M_f._M_spec._M_type == __format::_Pres_none)
+           return _M_f._M_format_character(__u, __fc);
+         else if (_M_f._M_spec._M_type == __format::_Pres_esc)
+           {
+             // TODO
+             return __fc.out();
+           }
+         else
+           return _M_f.format(__u, __fc);
+       }
+
+#if __cpp_lib_format_ranges
+      constexpr void
+      set_debug_format() noexcept
+      { _M_f._M_spec._M_type = __format::_Pres_esc; }
+#endif
+
+    private:
+      __format::__formatter_int<_CharT> _M_f;
+    };
+
+  // Format a char value for wide character output.
+  template<>
+    struct formatter<char, wchar_t>
+    {
+      formatter() = default;
+
+      constexpr typename basic_format_parse_context<wchar_t>::iterator
+      parse(basic_format_parse_context<wchar_t>& __pc)
+      {
+       return _M_f._M_parse<char>(__pc);
+      }
+
+      template<typename _Out>
+       typename basic_format_context<_Out, wchar_t>::iterator
+       format(char __u, basic_format_context<_Out, wchar_t>& __fc) const
+       {
+         if (_M_f._M_spec._M_type == __format::_Pres_none)
+           return _M_f._M_format_character(__u, __fc);
+         else if (_M_f._M_spec._M_type == __format::_Pres_esc)
+           {
+             // TODO
+             return __fc.out();
+           }
+         else
+           return _M_f.format(__u, __fc);
+       }
+
+      constexpr void
+      set_debug_format() noexcept
+      { _M_f._M_spec._M_type = __format::_Pres_esc; }
+
+    private:
+      __format::__formatter_int<wchar_t> _M_f;
+    };
+
+  /** Format a string.
+   * @{
+   */
+  template<__format::__char _CharT>
+    struct formatter<_CharT*, _CharT>
+    {
+      formatter() = default;
+
+      [[__gnu__::__always_inline__]]
+      constexpr typename basic_format_parse_context<_CharT>::iterator
+      parse(basic_format_parse_context<_CharT>& __pc)
+      { return _M_f.parse(__pc); }
+
+      template<typename _Out>
+       [[__gnu__::__nonnull__]]
+       typename basic_format_context<_Out, _CharT>::iterator
+       format(_CharT* __u, basic_format_context<_Out, _CharT>& __fc) const
+       { return _M_f.format(__u, __fc); }
+
+      constexpr void set_debug_format() noexcept { _M_f.set_debug_format(); }
+
+    private:
+      __format::__formatter_str<_CharT> _M_f;
+    };
+
+  template<__format::__char _CharT>
+    struct formatter<const _CharT*, _CharT>
+    {
+      formatter() = default;
+
+      [[__gnu__::__always_inline__]]
+      constexpr typename basic_format_parse_context<_CharT>::iterator
+      parse(basic_format_parse_context<_CharT>& __pc)
+      { return _M_f.parse(__pc); }
+
+      template<typename _Out>
+       [[__gnu__::__nonnull__]]
+       typename basic_format_context<_Out, _CharT>::iterator
+       format(const _CharT* __u,
+              basic_format_context<_Out, _CharT>& __fc) const
+       { return _M_f.format(__u, __fc); }
+
+      constexpr void set_debug_format() noexcept { _M_f.set_debug_format(); }
+
+    private:
+      __format::__formatter_str<_CharT> _M_f;
+    };
+
+  template<__format::__char _CharT, size_t _Nm>
+    struct formatter<_CharT[_Nm], _CharT>
+    {
+      formatter() = default;
+
+      [[__gnu__::__always_inline__]]
+      constexpr typename basic_format_parse_context<_CharT>::iterator
+      parse(basic_format_parse_context<_CharT>& __pc)
+      { return _M_f.parse(__pc); }
+
+      template<typename _Out>
+       typename basic_format_context<_Out, _CharT>::iterator
+       format(const _CharT (&__u)[_Nm],
+              basic_format_context<_Out, _CharT>& __fc) const
+       { return _M_f.format({__u, _Nm}, __fc); }
+
+      constexpr void set_debug_format() noexcept { _M_f.set_debug_format(); }
+
+    private:
+      __format::__formatter_str<_CharT> _M_f;
+    };
+
+  template<__format::__char _CharT, size_t _Nm>
+    struct formatter<const _CharT[_Nm], _CharT>
+    {
+      formatter() = default;
+
+      [[__gnu__::__always_inline__]]
+      constexpr typename basic_format_parse_context<_CharT>::iterator
+      parse(basic_format_parse_context<_CharT>& __pc)
+      { return _M_f.parse(__pc); }
+
+      template<typename _Out>
+       typename basic_format_context<_Out, _CharT>::iterator
+       format(const _CharT (&__u)[_Nm],
+              basic_format_context<_Out, _CharT>& __fc) const
+       { return _M_f.format({__u, _Nm}, __fc); }
+
+      constexpr void set_debug_format() noexcept { _M_f.set_debug_format(); }
+
+    private:
+      __format::__formatter_str<_CharT> _M_f;
+    };
+
+  template<typename _Traits, typename _Alloc>
+    struct formatter<basic_string<char, _Traits, _Alloc>, char>
+    {
+      formatter() = default;
+
+      [[__gnu__::__always_inline__]]
+      constexpr typename basic_format_parse_context<char>::iterator
+      parse(basic_format_parse_context<char>& __pc)
+      { return _M_f.parse(__pc); }
+
+      template<typename _Out>
+       typename basic_format_context<_Out, char>::iterator
+       format(const basic_string<char, _Traits, _Alloc>& __u,
+              basic_format_context<_Out, char>& __fc) const
+       { return _M_f.format(__u, __fc); }
+
+#if __cpp_lib_format_ranges
+      constexpr void set_debug_format() noexcept { _M_f.set_debug_format(); }
+#endif
+
+    private:
+      __format::__formatter_str<char> _M_f;
+    };
+
+  template<typename _Traits, typename _Alloc>
+    struct formatter<basic_string<wchar_t, _Traits, _Alloc>, wchar_t>
+    {
+      formatter() = default;
+
+      [[__gnu__::__always_inline__]]
+      constexpr typename basic_format_parse_context<wchar_t>::iterator
+      parse(basic_format_parse_context<wchar_t>& __pc)
+      { return _M_f.parse(__pc); }
+
+      template<typename _Out>
+       typename basic_format_context<_Out, wchar_t>::iterator
+       format(const basic_string<wchar_t, _Traits, _Alloc>& __u,
+              basic_format_context<_Out, wchar_t>& __fc) const
+       { return _M_f.format(__u, __fc); }
+
+#if __cpp_lib_format_ranges
+      constexpr void set_debug_format() noexcept { _M_f.set_debug_format(); }
+#endif
+
+    private:
+      __format::__formatter_str<wchar_t> _M_f;
+    };
+
+  template<typename _Traits>
+    struct formatter<basic_string_view<char, _Traits>, char>
+    {
+      formatter() = default;
+
+      [[__gnu__::__always_inline__]]
+      constexpr typename basic_format_parse_context<char>::iterator
+      parse(basic_format_parse_context<char>& __pc)
+      { return _M_f.parse(__pc); }
+
+      template<typename _Out>
+       typename basic_format_context<_Out, char>::iterator
+       format(basic_string_view<char, _Traits> __u,
+              basic_format_context<_Out, char>& __fc) const
+       { return _M_f.format(__u, __fc); }
+
+#if __cpp_lib_format_ranges
+      constexpr void set_debug_format() noexcept { _M_f.set_debug_format(); }
+#endif
+
+    private:
+      __format::__formatter_str<char> _M_f;
+    };
+
+  template<typename _Traits>
+    struct formatter<basic_string_view<wchar_t, _Traits>, wchar_t>
+    {
+      formatter() = default;
+
+      [[__gnu__::__always_inline__]]
+      constexpr typename basic_format_parse_context<wchar_t>::iterator
+      parse(basic_format_parse_context<wchar_t>& __pc)
+      { return _M_f.parse(__pc); }
+
+      template<typename _Out>
+       typename basic_format_context<_Out, wchar_t>::iterator
+       format(basic_string_view<wchar_t, _Traits> __u,
+              basic_format_context<_Out, wchar_t>& __fc) const
+       { return _M_f.format(__u, __fc); }
+
+#if __cpp_lib_format_ranges
+      constexpr void set_debug_format() noexcept { _M_f.set_debug_format(); }
+#endif
+
+    private:
+      __format::__formatter_str<wchar_t> _M_f;
+    };
+  /// @}
+
+  /// Format an integer.
+  template<integral _Tp, __format::__char _CharT>
+    requires (!__is_one_of<_Tp, char, wchar_t, char16_t, char32_t>::value)
+    struct formatter<_Tp, _CharT>
+    {
+      formatter() = default;
+
+      [[__gnu__::__always_inline__]]
+      constexpr typename basic_format_parse_context<_CharT>::iterator
+      parse(basic_format_parse_context<_CharT>& __pc)
+      {
+       return _M_f.template _M_parse<_Tp>(__pc);
+      }
+
+      template<typename _Out>
+       typename basic_format_context<_Out, _CharT>::iterator
+       format(_Tp __u, basic_format_context<_Out, _CharT>& __fc) const
+       { return _M_f.format(__u, __fc); }
+
+    private:
+      __format::__formatter_int<_CharT> _M_f;
+    };
+
+#if defined __SIZEOF_INT128__ && defined __STRICT_ANSI__
+  template<typename _Tp, __format::__char _CharT>
+    requires (__is_one_of<_Tp, __int128, unsigned __int128>::value)
+    struct formatter<_Tp, _CharT>
+    {
+      formatter() = default;
+
+      [[__gnu__::__always_inline__]]
+      constexpr typename basic_format_parse_context<_CharT>::iterator
+      parse(basic_format_parse_context<_CharT>& __pc)
+      {
+       return _M_f.template _M_parse<_Tp>(__pc);
+      }
+
+      template<typename _Out>
+       typename basic_format_context<_Out, _CharT>::iterator
+       format(_Tp __u, basic_format_context<_Out, _CharT>& __fc) const
+       { return _M_f.format(__u, __fc); }
+
+    private:
+      __format::__formatter_int<_CharT> _M_f;
+    };
+#endif
+
+  /// Format a floating-point value.
+  template<__format::__floating_point _Tp, __format::__char _CharT>
+    struct formatter<_Tp, _CharT>
+    {
+      formatter() = default;
+
+      [[__gnu__::__always_inline__]]
+      constexpr typename basic_format_parse_context<_CharT>::iterator
+      parse(basic_format_parse_context<_CharT>& __pc)
+      { return _M_f.parse(__pc); }
+
+      template<typename _Out>
+       typename basic_format_context<_Out, _CharT>::iterator
+       format(_Tp __u, basic_format_context<_Out, _CharT>& __fc) const
+       { return _M_f.format(__u, __fc); }
+
+    private:
+      __format::__formatter_fp<_CharT> _M_f;
+    };
+
+  /** Format a pointer.
+   * @{
+   */
+  template<__format::__char _CharT>
+    struct formatter<const void*, _CharT>
+    {
+      formatter() = default;
+
+      constexpr typename basic_format_parse_context<_CharT>::iterator
+      parse(basic_format_parse_context<_CharT>& __pc)
+      {
+       __format::_Spec<_CharT> __spec{};
+       const auto __last = __pc.end();
+       auto __first = __pc.begin();
+
+       auto __finalize = [this, &__spec] {
+         _M_spec = __spec;
+       };
+
+       auto __finished = [&] {
+         if (__first == __last || *__first == '}')
+           {
+             __finalize();
+             return true;
+           }
+         return false;
+       };
+
+       if (__finished())
+         return __first;
+
+       __first = __spec._M_parse_fill_and_align(__first, __last);
+       if (__finished())
+         return __first;
+
+       // _GLIBCXX_RESOLVE_LIB_DEFECTS
+       // P2519R3 Formatting pointers
+       __first = __spec._M_parse_zero_fill(__first, __last);
+       if (__finished())
+         return __first;
+
+       __first = __spec._M_parse_width(__first, __last, __pc);
+
+       if (__first != __last && (*__first == 'p' || *__first == 'P'))
+         {
+           if (*__first == 'P')
+             __spec._M_type = __format::_Pres_P;
+           ++__first;
+         }
+
+       if (__finished())
+         return __first;
+
+       __format::__failed_to_parse_format_spec();
+      }
+
+      template<typename _Out>
+       typename basic_format_context<_Out, _CharT>::iterator
+       format(const void* __v, basic_format_context<_Out, _CharT>& __fc) const
+       {
+         auto __u = reinterpret_cast<__UINT64_TYPE__>(__v);
+         char __buf[2 + sizeof(__v) * 2];
+         auto [__ptr, __ec] = std::to_chars(__buf + 2, std::end(__buf),
+                                            __u, 16);
+         const int __n = __ptr - __buf;
+         __buf[0] = '0';
+         __buf[1] = 'x';
+
+         basic_string_view<_CharT> __str;
+         if constexpr (is_same_v<_CharT, char>)
+           __str = string_view(__buf, __n);
+         else
+           {
+             const std::locale& __loc = __fc.locale();
+             auto& __ct = use_facet<ctype<_CharT>>(__loc);
+             auto __p = (_CharT*)__builtin_alloca(__n * sizeof(_CharT));
+             __ct.widen(__buf, __buf + __n, __p);
+             __str = wstring_view(__p, __n);
+           }
+
+         size_t __width = _M_spec._M_get_width(__fc);
+
+         if (__width <= (size_t)__n)
+           return __format::__write(__fc.out(), __str);
+
+         size_t __nfill = __width - __n;
+         __format::_Align __align
+           = _M_spec._M_align ? _M_spec._M_align : __format::_Align_right;
+         return __format::__write_padded(__fc.out(), __str,
+                                         __align, __nfill, _M_spec._M_fill);
+       }
+
+    private:
+      __format::_Spec<_CharT> _M_spec{}; // XXX don't need full spec?
+    };
+
+  template<__format::__char _CharT>
+    struct formatter<void*, _CharT>
+    {
+      formatter() = default;
+
+      [[__gnu__::__always_inline__]]
+      constexpr typename basic_format_parse_context<_CharT>::iterator
+      parse(basic_format_parse_context<_CharT>& __pc)
+      { return _M_f.parse(__pc); }
+
+      template<typename _Out>
+       typename basic_format_context<_Out, _CharT>::iterator
+       format(void* __v, basic_format_context<_Out, _CharT>& __fc) const
+       { return _M_f.format(__v, __fc); }
+
+    private:
+      formatter<const void*, _CharT> _M_f;
+    };
+
+  template<__format::__char _CharT>
+    struct formatter<nullptr_t, _CharT>
+    {
+      formatter() = default;
+
+      [[__gnu__::__always_inline__]]
+      constexpr typename basic_format_parse_context<_CharT>::iterator
+      parse(basic_format_parse_context<_CharT>& __pc)
+      { return _M_f.parse(__pc); }
+
+      template<typename _Out>
+       typename basic_format_context<_Out, _CharT>::iterator
+       format(nullptr_t, basic_format_context<_Out, _CharT>& __fc) const
+       { return _M_f.format(nullptr, __fc); }
+
+    private:
+      formatter<const void*, _CharT> _M_f;
+    };
+  /// @}
+
+
+/// @cond undocumented
+namespace __format
+{
+  template<typename _Tp, typename _Context,
+          typename _Formatter
+            = typename _Context::template formatter_type<remove_const_t<_Tp>>,
+          typename _ParseContext
+            = basic_format_parse_context<typename _Context::char_type>>
+    concept __parsable_with
+      = semiregular<_Formatter>
+         && requires (_Formatter __f, _ParseContext __pc)
+    {
+      { __f.parse(__pc) } -> same_as<typename _ParseContext::iterator>;
+    };
+
+  template<typename _Tp, typename _Context,
+          typename _Formatter
+            = typename _Context::template formatter_type<remove_const_t<_Tp>>,
+          typename _ParseContext
+            = basic_format_parse_context<typename _Context::char_type>>
+    concept __formattable_with
+      = semiregular<_Formatter>
+         && requires (const _Formatter __cf, _Tp&& __t, _Context __fc)
+    {
+      { __cf.format(__t, __fc) } -> same_as<typename _Context::iterator>;
+    };
+
+  // An unspecified output iterator type used in the `formattable` concept.
+  template<typename _CharT>
+    using _Iter_for = back_insert_iterator<basic_string<_CharT>>;
+
+  template<typename _Tp, typename _CharT,
+          typename _Context = basic_format_context<_Iter_for<_CharT>, _CharT>>
+    concept __formattable_impl
+      = __parsable_with<_Tp, _Context> && __formattable_with<_Tp, _Context>;
+
+} // namespace __format
+/// @endcond
+
+  // [format.formattable], concept formattable
+  template<typename _Tp, typename _CharT>
+    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
+
+  /// An iterator after the last character written, and the number of
+  /// characters that would have been written.
+  template<typename _Out>
+    struct format_to_n_result
+    {
+      _Out out;
+      iter_difference_t<_Out> size;
+    };
+
+/// @cond undocumented
+namespace __format
+{
+  template<typename _CharT>
+    class _Sink_iter
+    {
+      _Sink<_CharT>* _M_sink = nullptr;
+
+    public:
+      using iterator_category = output_iterator_tag;
+      using value_type = void;
+      using difference_type = ptrdiff_t;
+      using pointer = void;
+      using reference = void;
+
+      _Sink_iter() = default;
+      _Sink_iter(const _Sink_iter&) = default;
+      _Sink_iter& operator=(const _Sink_iter&) = default;
+
+      [[__gnu__::__always_inline__]]
+      explicit constexpr
+      _Sink_iter(_Sink<_CharT>& __sink) : _M_sink(std::addressof(__sink)) { }
+
+      [[__gnu__::__always_inline__]]
+      constexpr _Sink_iter&
+      operator=(_CharT __c)
+      {
+       _M_sink->_M_write(__c);
+       return *this;
+      }
+
+      [[__gnu__::__always_inline__]]
+      constexpr _Sink_iter&
+      operator=(basic_string_view<_CharT> __s)
+      {
+       _M_sink->_M_write(__s);
+       return *this;
+      }
+
+      [[__gnu__::__always_inline__]]
+      constexpr _Sink_iter&
+      operator*() { return *this; }
+
+      [[__gnu__::__always_inline__]]
+      constexpr _Sink_iter&
+      operator++() { return *this; }
+
+      [[__gnu__::__always_inline__]]
+      constexpr _Sink_iter
+      operator++(int) { return *this; }
+    };
+
+  // Abstract base class for type-erased character sinks.
+  // All formatting and output is done via this type's iterator,
+  // to reduce the number of different template instantiations.
+  template<typename _CharT>
+    class _Sink
+    {
+      friend class _Sink_iter<_CharT>;
+
+      span<_CharT> _M_span;
+      typename span<_CharT>::iterator _M_next;
+
+      // Called when the span is full, to make more space available.
+      // Precondition: _M_next != _M_span.begin()
+      // Postcondition: _M_next != _M_span.end()
+      virtual void _M_overflow() = 0;
+
+    protected:
+      // Precondition: __span.size() != 0
+      [[__gnu__::__always_inline__]]
+      explicit constexpr
+      _Sink(span<_CharT> __span) noexcept
+      : _M_span(__span), _M_next(__span.begin())
+      { }
+
+      // The portion of the span that has been written to.
+      [[__gnu__::__always_inline__]]
+      span<_CharT>
+      _M_used() const noexcept
+      { return _M_span.first(_M_next - _M_span.begin()); }
+
+      // The portion of the span that has not been written to.
+      [[__gnu__::__always_inline__]]
+      constexpr span<_CharT>
+      _M_unused() const noexcept
+      { return _M_span.subspan(_M_next - _M_span.begin()); }
+
+      // Use the start of the span as the next write position.
+      [[__gnu__::__always_inline__]]
+      constexpr void
+      _M_rewind() noexcept
+      { _M_next = _M_span.begin(); }
+
+      // Replace the current output range.
+      void
+      _M_reset(span<_CharT> __s,
+              typename span<_CharT>::iterator __next) noexcept
+      {
+       _M_span = __s;
+       _M_next = __next;
+      }
+
+      // Called by the iterator for *it++ = c
+      constexpr void
+      _M_write(_CharT __c)
+      {
+       *_M_next++ = __c;
+       if (_M_next - _M_span.begin() == _M_span.size()) [[unlikely]]
+         _M_overflow();
+      }
+
+      constexpr void
+      _M_write(basic_string_view<_CharT> __s)
+      {
+       span __to = _M_unused();
+       while (__to.size() <= __s.size())
+         {
+           __s.copy(__to.data(), __to.size());
+           _M_next += __to.size();
+           __s.remove_prefix(__to.size());
+           _M_overflow();
+           __to = _M_unused();
+         }
+       if (__s.size())
+         {
+           __s.copy(__to.data(), __s.size());
+           _M_next += __s.size();
+         }
+      }
+
+    public:
+      _Sink(const _Sink&) = delete;
+      _Sink& operator=(const _Sink&) = delete;
+
+      [[__gnu__::__always_inline__]]
+      constexpr _Sink_iter<_CharT>
+      out() noexcept
+      { return _Sink_iter<_CharT>(*this); }
+    };
+
+  // A sink with an internal buffer. This is used to implement concrete sinks.
+  template<typename _CharT>
+    class _Buf_sink : public _Sink<_CharT>
+    {
+    protected:
+      _CharT _M_buf[32 * sizeof(void*) / sizeof(_CharT)];
+
+      [[__gnu__::__always_inline__]]
+      constexpr
+      _Buf_sink() noexcept
+      : _Sink<_CharT>(_M_buf)
+      { }
+    };
+
+  // A sink that fills a sequence (e.g. std::string, std::vector, std::deque).
+  // Writes to a buffer then appends that to the sequence when it fills up.
+  template<typename _Seq>
+    class _Seq_sink : public _Buf_sink<typename _Seq::value_type>
+    {
+      using _CharT = typename _Seq::value_type;
+
+      _Seq _M_seq;
+
+      // Transfer buffer contents to the sequence, so buffer can be refilled.
+      void
+      _M_overflow() override
+      {
+       auto __s = this->_M_used();
+       if constexpr (__is_specialization_of<_Seq, basic_string>)
+         _M_seq.append(__s.data(), __s.size());
+       else
+         _M_seq.insert(_M_seq.end(), __s.begin(), __s.end());
+       this->_M_rewind();
+      }
+
+    public:
+      [[__gnu__::__always_inline__]]
+      _Seq_sink() noexcept(is_nothrow_default_constructible_v<_Seq>)
+      { }
+
+      _Seq_sink(_Seq&& __s) noexcept(is_nothrow_move_constructible_v<_Seq>)
+      : _M_seq(std::move(__s))
+      { }
+
+      using _Sink<_CharT>::out;
+
+      _Seq
+      get() &&
+      {
+       _Seq_sink::_M_overflow();
+       return std::move(_M_seq);
+      }
+    };
+
+  template<typename _CharT, typename _Alloc = allocator<_CharT>>
+    using _Str_sink
+      = _Seq_sink<basic_string<_CharT, char_traits<_CharT>, _Alloc>>;
+
+  // template<typename _CharT, typename _Alloc = allocator<_CharT>>
+    // using _Vec_sink = _Seq_sink<vector<_CharT, _Alloc>>;
+
+  // A sink that writes to an output iterator.
+  // Writes to a fixed-size buffer and then flushes to the output iterator
+  // when the buffer fills up.
+  template<typename _CharT, typename _OutIter>
+    class _Iter_sink : public _Buf_sink<_CharT>
+    {
+      _OutIter _M_out;
+      iter_difference_t<_OutIter> _M_max;
+
+    protected:
+      size_t _M_count = 0;
+
+      void
+      _M_overflow() override
+      {
+       auto __used = this->_M_used();
+       if (_M_max < 0) // No maximum.
+         _M_out = ranges::copy(__used, std::move(_M_out)).out;
+       else if (_M_count < _M_max)
+         {
+           auto __max = _M_max - _M_count;
+           span<_CharT> __first;
+           if (__max < __used.size())
+             __first = __used.first(__max);
+           else
+             __first = __used;
+           _M_out = ranges::copy(__first, std::move(_M_out)).out;
+         }
+       this->_M_rewind();
+       _M_count += __used.size();
+      }
+
+    public:
+      [[__gnu__::__always_inline__]]
+      explicit
+      _Iter_sink(_OutIter __out, iter_difference_t<_OutIter> __max = -1)
+      : _M_out(std::move(__out)), _M_max(__max)
+      { }
+
+      using _Sink<_CharT>::out;
+
+      format_to_n_result<_OutIter>
+      _M_finish() &&
+      {
+       _Iter_sink::_M_overflow();
+       iter_difference_t<_OutIter> __count(_M_count);
+       return { std::move(_M_out), __count };
+      }
+    };
+
+  // Partial specialization for contiguous iterators.
+  // No buffer is used, characters are written straight to the iterator.
+  // We do not know the size of the output range, so the span size just grows
+  // as needed. The end of the span might be an invalid pointer outside the
+  // valid range, but we never actually call _M_span.end(). This class does
+  // not introduce any invalid pointer arithmetic or overflows that would not
+  // have happened anyway.
+  template<typename _CharT, contiguous_iterator _OutIter>
+    class _Iter_sink<_CharT, _OutIter> : public _Sink<_CharT>
+    {
+      using uint64_t = __UINTPTR_TYPE__;
+      _OutIter _M_first;
+      iter_difference_t<_OutIter> _M_max = -1;
+    protected:
+      size_t _M_count = 0;
+    private:
+      _CharT _M_buf[64]; // Write here after outputting _M_max characters.
+
+    protected:
+      void
+      _M_overflow()
+      {
+       auto __used = this->_M_used();
+       _M_count += __used.size();
+
+       if (_M_max >= 0)
+         {
+           // Span was already sized for the maximum character count,
+           // if it overflows then any further output must go to the
+           // internal buffer, to be discarded.
+           span<_CharT> __buf{_M_buf};
+           this->_M_reset(__buf, __buf.begin());
+         }
+       else
+         {
+           // No maximum character count. Just extend the span to allow
+           // writing more characters to it.
+           this->_M_reset({__used.data(), __used.size() + 1024}, __used.end());
+         }
+      }
+
+    private:
+      static span<_CharT>
+      _S_make_span(_CharT* __ptr, iter_difference_t<_OutIter> __n,
+                  span<_CharT> __buf) noexcept
+      {
+       if (__n == 0)
+         return __buf; // Only write to the internal buffer.
+
+       if (__n > 0)
+         {
+           if constexpr (!is_integral_v<decltype(__n)>
+                           || sizeof(__n) > sizeof(size_t))
+             {
+               // __int128 or __detail::__max_diff_type
+               auto __m = (decltype(__n))(size_t)-1;
+               if (__n > __m)
+                 __n = __m;
+             }
+           return {__ptr, (size_t)__n};
+         }
+
+#if __has_builtin(__builtin_dynamic_object_size)
+       if (size_t __bytes = __builtin_dynamic_object_size(__ptr, 2))
+         return {__ptr, __bytes / sizeof(_CharT)};
+#endif
+       // Avoid forming a pointer to a different memory page.
+       uint64_t __off = reinterpret_cast<uint64_t>(__ptr) % 1024;
+       __n = (1024 - __off) / sizeof(_CharT);
+       if (__n > 0) [[likely]]
+       return {__ptr, static_cast<size_t>(__n)};
+       else // Misaligned/packed buffer of wchar_t?
+         return {__ptr, 1};
+      }
+
+    public:
+      explicit
+      _Iter_sink(_OutIter __out, iter_difference_t<_OutIter> __n = -1) noexcept
+      : _Sink<_CharT>(_S_make_span(std::to_address(__out), __n, _M_buf)),
+       _M_first(__out), _M_max(__n)
+      { }
+
+      format_to_n_result<_OutIter>
+      _M_finish() &&
+      {
+       _Iter_sink::_M_overflow();
+       iter_difference_t<_OutIter> __count(_M_count);
+       auto __used = this->_M_used();
+       auto __last = _M_first;
+       if (__used.data() == _M_buf) // Wrote at least _M_max characters.
+         __last += _M_max;
+       else
+         __last += iter_difference_t<_OutIter>(__used.size());
+       return { __last, __count };
+      }
+    };
+
+  enum _Arg_t : unsigned char {
+    _Arg_none, _Arg_bool, _Arg_c, _Arg_i, _Arg_u, _Arg_ll, _Arg_ull,
+    _Arg_flt, _Arg_dbl, _Arg_ldbl, _Arg_str, _Arg_sv, _Arg_ptr, _Arg_handle,
+    _Arg_i128, _Arg_u128,
+    _Arg_bf16, _Arg_f16, _Arg_f32, _Arg_f64,
+#ifdef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT
+    _Arg_next_value_,
+    _Arg_f128 = _Arg_ldbl,
+    _Arg_ibm128 = _Args_next_value_,
+#else
+    _Arg_f128,
+#endif
+    _Arg_max_
+  };
+
+  template<typename _Context>
+    struct _Arg_value
+    {
+      using _CharT = typename _Context::char_type;
+
+      struct _HandleBase
+      {
+       const void* _M_ptr;
+       void (*_M_func)();
+      };
+
+      union
+      {
+       monostate _M_none;
+       bool _M_bool;
+       _CharT _M_c;
+       int _M_i;
+       unsigned _M_u;
+       long long _M_ll;
+       unsigned long long _M_ull;
+       float _M_flt;
+       double _M_dbl;
+#ifndef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT // No long double if it's ambiguous.
+       long double _M_ldbl;
+#endif
+       const _CharT* _M_str;
+       basic_string_view<_CharT> _M_sv;
+       const void* _M_ptr;
+       _HandleBase _M_handle;
+#ifdef __SIZEOF_INT128__
+       __int128 _M_i128;
+       unsigned __int128 _M_u128;
+#endif
+       // TODO _Float16 etc.
+#ifdef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT
+       __ieee128 _M_f128;
+       __ibm128  _M_ibm128;
+#elif _GLIBCXX_FORMAT_F128
+       __float128_t _M_f128;
+#endif
+      };
+
+      [[__gnu__::__always_inline__]]
+      _Arg_value() : _M_none() { }
+
+#if 0
+      template<typename _Tp>
+       _Arg_value(in_place_type_t<_Tp>, _Tp __val)
+       { _S_get<_Tp>() = __val; }
+#endif
+
+      template<typename _Tp, typename _Self>
+       [[__gnu__::__always_inline__]]
+       static auto&
+       _S_get(_Self& __u) noexcept
+       {
+         if constexpr (is_same_v<_Tp, bool>)
+           return __u._M_bool;
+         else if constexpr (is_same_v<_Tp, _CharT>)
+           return __u._M_c;
+         else if constexpr (is_same_v<_Tp, int>)
+           return __u._M_i;
+         else if constexpr (is_same_v<_Tp, unsigned>)
+           return __u._M_u;
+         else if constexpr (is_same_v<_Tp, long long>)
+           return __u._M_ll;
+         else if constexpr (is_same_v<_Tp, unsigned long long>)
+           return __u._M_ull;
+         else if constexpr (is_same_v<_Tp, float>)
+           return __u._M_flt;
+         else if constexpr (is_same_v<_Tp, double>)
+           return __u._M_dbl;
+#ifndef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT
+         else if constexpr (is_same_v<_Tp, long double>)
+           return __u._M_ldbl;
+#else
+         else if constexpr (is_same_v<_Tp, __ieee128>)
+           return __u._M_f128;
+         else if constexpr (is_same_v<_Tp, __ibm128>)
+           return __u._M_ibm128;
+#endif
+         else if constexpr (is_same_v<_Tp, const _CharT*>)
+           return __u._M_str;
+         else if constexpr (is_same_v<_Tp, basic_string_view<_CharT>>)
+           return __u._M_sv;
+         else if constexpr (is_same_v<_Tp, const void*>)
+           return __u._M_ptr;
+#ifdef __SIZEOF_INT128__
+         else if constexpr (is_same_v<_Tp, __int128>)
+           return __u._M_i128;
+         else if constexpr (is_same_v<_Tp, unsigned __int128>)
+           return __u._M_u128;
+#endif
+#if _GLIBCXX_FORMAT_F128
+         else if constexpr (is_same_v<_Tp, __float128_t>)
+           return __u._M_f128;
+#endif
+         else if constexpr (derived_from<_Tp, _HandleBase>)
+           return static_cast<_Tp&>(__u._M_handle);
+         // Otherwise, ill-formed.
+       }
+
+      template<typename _Tp>
+       [[__gnu__::__always_inline__]]
+       auto&
+       _M_get() noexcept
+       { return _S_get<_Tp>(*this); }
+
+      template<typename _Tp>
+       [[__gnu__::__always_inline__]]
+       const auto&
+       _M_get() const noexcept
+       { return _S_get<_Tp>(*this); }
+
+      template<typename _Tp>
+       [[__gnu__::__always_inline__]]
+       void
+       _M_set(_Tp __v) noexcept
+       {
+         if constexpr (derived_from<_Tp, _HandleBase>)
+           std::construct_at(&_M_handle, __v);
+         else
+           _S_get<_Tp>(*this) = __v;
+       }
+      };
+
+} // namespace __format
+/// @endcond
+
+  template<typename _Context>
+    class basic_format_arg
+    {
+      using _CharT = typename _Context::char_type;
+
+      template<typename _Tp>
+       static constexpr bool __formattable
+         = __format::__formattable_with<_Tp, _Context>;
+
+    public:
+      class handle : public __format::_Arg_value<_Context>::_HandleBase
+      {
+       using _Base = __format::_Arg_value<_Context>::_HandleBase;
+
+       // Format as const if possible, to reduce instantiations.
+       template<typename _Tp>
+         using __maybe_const_t
+           = __conditional_t<__format::__formattable_with<_Tp, _Context>,
+                             const _Tp, _Tp>;
+
+       template<typename _Tq>
+         static void
+         _S_format(basic_format_parse_context<_CharT>& __parse_ctx,
+                   _Context& __format_ctx, const void* __ptr)
+         {
+           using _Td = remove_const_t<_Tq>;
+           typename _Context::template formatter_type<_Td> __f;
+           __parse_ctx.advance_to(__f.parse(__parse_ctx));
+           _Tq& __val = *const_cast<_Tq*>(static_cast<const _Td*>(__ptr));
+           __format_ctx.advance_to(__f.format(__val, __format_ctx));
+         }
+
+       template<typename _Tp>
+         explicit
+         handle(_Tp& __val) noexcept
+         {
+           if constexpr (!__format::__formattable_with<const _Tp, _Context>)
+             static_assert(!is_const_v<_Tp>, "std::format argument must be "
+                                             "non-const for this type");
+
+           this->_M_ptr = __builtin_addressof(__val);
+           auto __func = _S_format<__maybe_const_t<_Tp>>;
+           this->_M_func = reinterpret_cast<void(*)()>(__func);
+         }
+
+       friend class basic_format_arg<_Context>;
+
+      public:
+       handle(const handle&) = default;
+       handle& operator=(const handle&) = default;
+
+       [[__gnu__::__always_inline__]]
+       void
+       format(basic_format_parse_context<_CharT>& __pc, _Context& __fc) const
+       {
+         using _Func = void(*)(basic_format_parse_context<_CharT>&,
+                               _Context&, const void*);
+         auto __f = reinterpret_cast<_Func>(this->_M_func);
+         __f(__pc, __fc, this->_M_ptr);
+       }
+      };
+
+      [[__gnu__::__always_inline__]]
+      basic_format_arg() noexcept : _M_type(__format::_Arg_none) { }
+
+      [[nodiscard,__gnu__::__always_inline__]]
+      explicit operator bool() const noexcept
+      { return _M_type != __format::_Arg_none; }
+
+    private:
+      template<typename _Ctx>
+       friend class basic_format_args;
+
+      static_assert(is_trivially_copyable_v<__format::_Arg_value<_Context>>);
+
+      __format::_Arg_value<_Context> _M_val;
+      __format::_Arg_t _M_type;
+
+      // Transform incoming argument type to the type stored in _Arg_value.
+      // e.g. short -> int, std::string -> std::string_view,
+      // char[3] -> const char*.
+      template<typename _Tp>
+       static consteval auto
+       _S_to_arg_type()
+       {
+         using _Td = remove_const_t<_Tp>;
+         if constexpr (is_same_v<_Td, bool>)
+           return type_identity<bool>();
+         else if constexpr (is_same_v<_Td, _CharT>)
+           return type_identity<_CharT>();
+         else if constexpr (is_same_v<_Td, char> && is_same_v<_CharT, wchar_t>)
+           return type_identity<_CharT>();
+#ifdef __SIZEOF_INT128__ // Check before signed/unsigned integer
+         else if constexpr (is_same_v<_Td, __int128>)
+           return type_identity<__int128>();
+         else if constexpr (is_same_v<_Td, unsigned __int128>)
+           return type_identity<unsigned __int128>();
+#endif
+         else if constexpr (__is_signed_integer<_Td>::value)
+           {
+             if constexpr (sizeof(_Td) <= sizeof(int))
+               return type_identity<int>();
+             else if constexpr (sizeof(_Td) <= sizeof(long long))
+               return type_identity<long long>();
+           }
+         else if constexpr (__is_unsigned_integer<_Td>::value)
+           {
+             if constexpr (sizeof(_Td) <= sizeof(unsigned))
+               return type_identity<unsigned>();
+             else if constexpr (sizeof(_Td) <= sizeof(unsigned long long))
+               return type_identity<unsigned long long>();
+           }
+         else if constexpr (is_same_v<_Td, float>)
+           return type_identity<float>();
+         else if constexpr (is_same_v<_Td, double>)
+           return type_identity<double>();
+#ifndef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT
+         else if constexpr (is_same_v<_Td, long double>)
+           return type_identity<long double>();
+#else
+         else if constexpr (is_same_v<_Td, __ibm128>)
+           return type_identity<__ibm128>();
+         else if constexpr (is_same_v<_Td, __ieee128>)
+           return type_identity<__ieee128>();
+#endif
+
+         // TODO bfloat16 and float16
+
+#ifdef __FLT32_DIG__
+         else if constexpr (is_same_v<_Td, _Float32>)
+# ifdef _GLIBCXX_FLOAT_IS_IEEE_BINARY32
+           return type_identity<float>();
+# else
+           return type_identity<_Float32>();
+# endif
+#endif
+#ifdef __FLT64_DIG__
+         else if constexpr (is_same_v<_Td, _Float64>)
+# ifdef _GLIBCXX_DOUBLE_IS_IEEE_BINARY64
+           return type_identity<double>();
+# else
+           return type_identity<_Float64>();
+# endif
+#endif
+#ifdef __FLT128_DIG__
+         else if constexpr (is_same_v<_Td, _Float128>)
+           return type_identity<__format::__float128_t>();
+#endif
+#if _GLIBCXX_USE_FLOAT128
+         else if constexpr (is_same_v<_Td, __float128>)
+           return type_identity<__format::__float128_t>();
+#endif
+         else if constexpr (__is_specialization_of<_Td, basic_string_view>)
+           return type_identity<basic_string_view<_CharT>>();
+         else if constexpr (__is_specialization_of<_Td, basic_string>)
+           return type_identity<basic_string_view<_CharT>>();
+         else if constexpr (is_same_v<decay_t<_Td>, const _CharT*>)
+           return type_identity<const _CharT*>();
+         else if constexpr (is_same_v<decay_t<_Td>, _CharT*>)
+           return type_identity<const _CharT*>();
+         else if constexpr (is_void_v<remove_pointer_t<_Td>>)
+           return type_identity<const void*>();
+         else if constexpr (is_same_v<_Td, nullptr_t>)
+           return type_identity<const void*>();
+         else
+           return type_identity<handle>();
+       }
+
+      // Transform a formattable type to the appropriate storage type.
+      template<typename _Tp>
+       using _Normalize = typename decltype(_S_to_arg_type<_Tp>())::type;
+
+      // Get the _Arg_t value corresponding to a normalized type.
+      template<typename _Tp>
+       static consteval __format::_Arg_t
+       _S_to_enum()
+       {
+         using namespace __format;
+         if constexpr (is_same_v<_Tp, bool>)
+           return _Arg_bool;
+         else if constexpr (is_same_v<_Tp, _CharT>)
+           return _Arg_c;
+         else if constexpr (is_same_v<_Tp, int>)
+           return _Arg_i;
+         else if constexpr (is_same_v<_Tp, unsigned>)
+           return _Arg_u;
+         else if constexpr (is_same_v<_Tp, long long>)
+           return _Arg_ll;
+         else if constexpr (is_same_v<_Tp, unsigned long long>)
+           return _Arg_ull;
+         else if constexpr (is_same_v<_Tp, float>)
+           return _Arg_flt;
+         else if constexpr (is_same_v<_Tp, double>)
+           return _Arg_dbl;
+#ifndef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT
+         else if constexpr (is_same_v<_Tp, long double>)
+           return _Arg_ldbl;
+#else
+         // Don't use _Arg_ldbl for this target, it's ambiguous.
+         else if constexpr (is_same_v<_Tp, __ibm128>)
+           return _Arg_ibm128
+         else if constexpr (is_same_v<_Tp, __ieee128>)
+           return _Arg_f128
+#endif
+         else if constexpr (is_same_v<_Tp, const _CharT*>)
+           return _Arg_str;
+         else if constexpr (is_same_v<_Tp, basic_string_view<_CharT>>)
+           return _Arg_sv;
+         else if constexpr (is_same_v<_Tp, const void*>)
+           return _Arg_ptr;
+#ifdef __SIZEOF_INT128__
+         else if constexpr (is_same_v<_Tp, __int128>)
+           return _Arg_i128;
+         else if constexpr (is_same_v<_Tp, unsigned __int128>)
+           return _Arg_u128;
+#endif
+
+         // N.B. some of these types will never actually be used here,
+         // because they get normalized to a standard floating-point type.
+#if defined __FLT32_DIG__ && ! _GLIBCXX_FLOAT_IS_IEEE_BINARY32
+         else if constexpr (is_same_v<_Tp, _Float32>)
+           return _Arg_f32;
+#endif
+#if defined __FLT64_DIG__ && ! _GLIBCXX_DOUBLE_IS_IEEE_BINARY64
+         else if constexpr (is_same_v<_Tp, _Float64>)
+           return _Arg_f64;
+#endif
+#if _GLIBCXX_FORMAT_F128
+         else if constexpr (is_same_v<_Tp, __format::__float128_t>)
+           return _Arg_f128;
+#endif
+         else if constexpr (is_same_v<_Tp, handle>)
+           return _Arg_handle;
+       }
+
+      template<typename _Tp>
+       void
+       _M_set(_Tp __v) noexcept
+       {
+         _M_type = _S_to_enum<_Tp>();
+         _M_val._M_set(__v);
+       }
+
+      template<typename _Tp>
+       requires __format::__formattable_with<_Tp, _Context>
+       explicit
+       basic_format_arg(_Tp& __v) noexcept
+       {
+         using _Td = _Normalize<_Tp>;
+         if constexpr (is_same_v<_Td, basic_string_view<_CharT>>)
+           _M_set(_Td{__v.data(), __v.size()});
+         else
+           _M_set(static_cast<_Td>(__v));
+       }
+
+      template<typename _Ctx, typename... _Argz>
+       friend auto
+       make_format_args(_Argz&&...) noexcept;
+
+      template<typename _Visitor, typename _Ctx>
+       friend decltype(auto)
+       visit_format_arg(_Visitor&& __vis, basic_format_arg<_Ctx>);
+
+      template<typename _Visitor>
+       decltype(auto)
+       _M_visit(_Visitor&& __vis, __format::_Arg_t __type)
+       {
+         using namespace __format;
+         switch (__type)
+         {
+           case _Arg_none:
+             return std::forward<_Visitor>(__vis)(_M_val._M_none);
+           case _Arg_bool:
+             return std::forward<_Visitor>(__vis)(_M_val._M_bool);
+           case _Arg_c:
+             return std::forward<_Visitor>(__vis)(_M_val._M_c);
+           case _Arg_i:
+             return std::forward<_Visitor>(__vis)(_M_val._M_i);
+           case _Arg_u:
+             return std::forward<_Visitor>(__vis)(_M_val._M_u);
+           case _Arg_ll:
+             return std::forward<_Visitor>(__vis)(_M_val._M_ll);
+           case _Arg_ull:
+             return std::forward<_Visitor>(__vis)(_M_val._M_ull);
+           case _Arg_flt:
+             return std::forward<_Visitor>(__vis)(_M_val._M_flt);
+           case _Arg_dbl:
+             return std::forward<_Visitor>(__vis)(_M_val._M_dbl);
+#ifndef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT
+           case _Arg_ldbl:
+             return std::forward<_Visitor>(__vis)(_M_val._M_ldbl);
+#else
+           case _Arg_f128:
+             return std::forward<_Visitor>(__vis)(_M_val._M_f128);
+           case _Arg_ibm128:
+             return std::forward<_Visitor>(__vis)(_M_val._M_ibm128);
+#endif
+           case _Arg_str:
+             return std::forward<_Visitor>(__vis)(_M_val._M_str);
+           case _Arg_sv:
+             return std::forward<_Visitor>(__vis)(_M_val._M_sv);
+           case _Arg_ptr:
+             return std::forward<_Visitor>(__vis)(_M_val._M_ptr);
+           case _Arg_handle:
+           {
+             auto& __h = static_cast<handle&>(_M_val._M_handle);
+             return std::forward<_Visitor>(__vis)(__h);
+           }
+#ifdef __SIZEOF_INT128__
+           case _Arg_i128:
+             return std::forward<_Visitor>(__vis)(_M_val._M_i128);
+           case _Arg_u128:
+             return std::forward<_Visitor>(__vis)(_M_val._M_u128);
+#endif
+             // TODO _Arg_f16 etc.
+
+#if _GLIBCXX_FORMAT_F128
+           case _Arg_f128:
+             return std::forward<_Visitor>(__vis)(_M_val._M_f128);
+#endif
+         }
+         __builtin_unreachable();
+       }
+    };
+
+  template<typename _Visitor, typename _Context>
+    inline decltype(auto)
+    visit_format_arg(_Visitor&& __vis, basic_format_arg<_Context> __arg)
+    {
+      return __arg._M_visit(std::forward<_Visitor>(__vis), __arg._M_type);
+    }
+
+/// @cond undocumented
+namespace __format
+{
+  struct _WidthPrecVisitor
+  {
+    template<typename _Tp>
+      size_t
+      operator()(_Tp& __arg) const
+      {
+       if constexpr (is_same_v<_Tp, monostate>)
+         __format::__invalid_arg_id_in_format_string();
+       // _GLIBCXX_RESOLVE_LIB_DEFECTS
+       // 3720. Restrict the valid types of arg-id for width and precision
+       // 3721. Allow an arg-id with a value of zero for width
+       else if constexpr (sizeof(_Tp) <= sizeof(long long))
+         {
+           if constexpr (__is_unsigned_integer<_Tp>::value)
+             return __arg;
+           else if constexpr (__is_signed_integer<_Tp>::value)
+             if (__arg >= 0)
+               return __arg;
+         }
+       __throw_format_error("format error: argument used for width or "
+                            "precision must be a non-negative integer");
+      }
+  };
+
+  template<typename _Context>
+    inline size_t
+    __int_from_arg(const basic_format_arg<_Context>& __arg)
+    { return std::visit_format_arg(_WidthPrecVisitor(), __arg); }
+
+  // Pack _Arg_t enum values into a single 60-bit integer.
+  template<int _Bits, size_t _Nm>
+    constexpr auto
+    __pack_arg_types(const array<_Arg_t, _Nm>& __types)
+    {
+      __UINT64_TYPE__ __packed = 0;
+      for (auto __i = __types.rbegin(); __i != __types.rend(); ++__i)
+       __packed = (__packed << _Bits) | *__i;
+      return __packed;
+    }
+} // namespace __format
+/// @endcond
+
+  template<typename _Context>
+    class basic_format_args
+    {
+      static constexpr int _S_packed_type_bits = 5; // _Arg_t values [0,20]
+      static constexpr int _S_packed_type_mask = 0b11111;
+      static constexpr int _S_max_packed_args = 12;
+
+      static_assert( __format::_Arg_max_ <= (1 << _S_packed_type_bits) );
+
+      // [format.arg.store], class template format-arg-store
+      // XXX: Should this be defined outside the class, so basic_format_args
+      // can use CTAD with a _Store argument?
+      template<typename... _Args>
+       class _Store;
+
+      using uint64_t = __UINT64_TYPE__;
+      using _Format_arg = basic_format_arg<_Context>;
+      using _Format_arg_val = __format::_Arg_value<_Context>;
+
+      // If args are packed then the number of args is in _M_packed_size and
+      // the packed types are in _M_unpacked_size, accessed via _M_type(i).
+      // If args are not packed then the number of args is in _M_unpacked_size
+      // and _M_packed_size is zero.
+      uint64_t _M_packed_size : 4;
+      uint64_t _M_unpacked_size : 60;
+
+      union {
+       const _Format_arg_val* _M_values; // Active when _M_packed_size != 0
+       const _Format_arg* _M_args;       // Active when _M_packed_size == 0
+      };
+
+      size_t
+      _M_size() const noexcept
+      { return _M_packed_size ? _M_packed_size : _M_unpacked_size; }
+
+      typename __format::_Arg_t
+      _M_type(size_t __i) const noexcept
+      {
+       uint64_t __t = _M_unpacked_size >> (__i * _S_packed_type_bits);
+       return static_cast<__format::_Arg_t>(__t & _S_packed_type_mask);
+      }
+
+      template<typename _Ctx, typename... _Args>
+       friend auto
+       make_format_args(_Args&&...) noexcept;
+
+      // An array of _Arg_t enums corresponding to _Args...
+      template<typename... _Args>
+       static consteval array<__format::_Arg_t, sizeof...(_Args)>
+       _S_types_to_pack()
+       { return {_Format_arg::template _S_to_enum<_Args>()...}; }
+
+    public:
+      basic_format_args() noexcept = default;
+
+      template<typename... _Args>
+       basic_format_args(const _Store<_Args...>& __store) noexcept;
+
+      [[nodiscard,__gnu__::__always_inline__]]
+      basic_format_arg<_Context>
+      get(size_t __i) const noexcept
+      {
+       basic_format_arg<_Context> __arg;
+       if (__i < _M_packed_size)
+         {
+           __arg._M_type = _M_type(__i);
+           __arg._M_val = _M_values[__i];
+         }
+       else if (_M_packed_size == 0 && __i < _M_unpacked_size)
+         __arg = _M_args[__i];
+       return __arg;
+      }
+    };
+
+  // An array of type-erased formatting arguments.
+  template<typename _Context>
+    template<typename... _Args>
+      class basic_format_args<_Context>::_Store
+      {
+       friend class basic_format_args;
+
+       template<typename _Ctx, typename... _Argz>
+         friend auto
+         make_format_args(_Argz&&...) noexcept;
+
+       // For a sufficiently small number of arguments we only store values.
+       // basic_format_args can get the types from the _Args pack.
+       static constexpr bool _S_values_only
+         = sizeof...(_Args) <= _S_max_packed_args;
+
+       using _Element_t
+         = __conditional_t<_S_values_only,
+                           __format::_Arg_value<_Context>,
+                           basic_format_arg<_Context>>;
+
+       _Element_t _M_args[sizeof...(_Args)];
+
+       template<typename _Tp>
+         static _Element_t
+         _S_make_elt(_Tp& __v)
+         {
+           basic_format_arg<_Context> __arg(__v);
+           if constexpr (_S_values_only)
+             return __arg._M_val;
+           else
+             return __arg;
+         }
+
+       template<typename... _Tp>
+         requires (sizeof...(_Tp) == sizeof...(_Args))
+         [[__gnu__::__always_inline__]]
+         _Store(_Tp&... __a) noexcept
+         : _M_args{_S_make_elt(__a)...}
+         { }
+      };
+
+  template<typename _Context>
+    template<typename... _Args> requires (sizeof...(_Args) == 0)
+      class basic_format_args<_Context>::_Store<_Args...>
+      { };
+
+  template<typename _Context>
+    template<typename... _Args>
+      basic_format_args<_Context>::
+      basic_format_args(const _Store<_Args...>& __store) noexcept
+      {
+       if constexpr (sizeof...(_Args) == 0)
+         {
+           _M_packed_size = 0;
+           _M_unpacked_size = 0;
+           _M_args = nullptr;
+         }
+       else if constexpr (sizeof...(_Args) <= _S_max_packed_args)
+         {
+           // The number of packed arguments:
+           _M_packed_size = sizeof...(_Args);
+           // The packed type enums:
+           _M_unpacked_size
+             = __format::__pack_arg_types<_S_packed_type_bits>(_S_types_to_pack<_Args...>());
+           // The _Arg_value objects.
+           _M_values = __store._M_args;
+         }
+       else
+         {
+           // No packed arguments:
+           _M_packed_size = 0;
+           // The number of unpacked arguments:
+           _M_unpacked_size = sizeof...(_Args);
+           // The basic_format_arg objects:
+           _M_args = __store._M_args;
+         }
+      }
+
+  /// Capture formatting arguments for use by `std::vformat`.
+  template<typename _Context = format_context, typename... _Args>
+    [[nodiscard,__gnu__::__always_inline__]]
+    inline auto
+    make_format_args(_Args&&... __fmt_args) noexcept
+    {
+      using _Fmt_args = basic_format_args<_Context>;
+      using _Fmt_arg = basic_format_arg<_Context>;
+      using _Store = typename _Fmt_args::template
+                    _Store<typename _Fmt_arg::template
+                    _Normalize<remove_reference_t<_Args>>...>;
+      return _Store(__fmt_args...);
+    }
+
+  /// Capture formatting arguments for use by `std::vformat` (for wide output).
+  template<typename... _Args>
+    [[nodiscard,__gnu__::__always_inline__]]
+    inline auto
+    make_wformat_args(_Args&&... __args) noexcept
+    { return std::make_format_args<wformat_context>(__args...); }
+
+/// @cond undocumented
+namespace __format
+{
+  template<typename _Out, typename _CharT, typename _Context>
+    _Out
+    __do_vformat_to(_Out, basic_string_view<_CharT>,
+                   const basic_format_args<_Context>&,
+                   const locale* = nullptr);
+} // namespace __format
+/// @endcond
+
+  /** Context for std::format and similar functions.
+   *
+   * A formatting context contains an output iterator and locale to use
+   * for the formatting operations. Most programs will never need to use
+   * this class template explicitly. For typical uses of `std::format` the
+   * library will use the specializations `std::format_context` (for `char`)
+   * and `std::wformat_context` (for `wchar_t`).
+   */
+  template<typename _Out, typename _CharT>
+    class basic_format_context
+    {
+      static_assert( output_iterator<_Out, const _CharT&> );
+
+      basic_format_args<basic_format_context> _M_args;
+      _Out _M_out;
+      __format::_Optional_locale _M_loc;
+
+      basic_format_context(basic_format_args<basic_format_context> __args,
+                          _Out __out)
+      : _M_args(__args), _M_out(std::move(__out))
+      { }
+
+      basic_format_context(basic_format_args<basic_format_context> __args,
+                          _Out __out, const std::locale& __loc)
+      : _M_args(__args), _M_out(std::move(__out)), _M_loc(__loc)
+      { }
+
+      template<typename _Out_, typename _CharT_, typename _Context_>
+       friend _Out_
+       __format::__do_vformat_to(_Out_, basic_string_view<_CharT_>,
+                                 const basic_format_args<_Context_>&,
+                                 const locale*);
+
+    public:
+      basic_format_context() = default;
+      ~basic_format_context() = default;
+
+      using iterator = _Out;
+      using char_type = _CharT;
+      template<typename _Tp>
+       using formatter_type = formatter<_Tp, _CharT>;
+
+      [[nodiscard]]
+      basic_format_arg<basic_format_context>
+      arg(size_t __id) const noexcept
+      { return _M_args.get(__id); }
+
+      [[nodiscard]]
+      std::locale locale() { return _M_loc.value(); }
+
+      [[nodiscard]]
+      iterator out() { return std::move(_M_out); }
+
+      void advance_to(iterator __it) { _M_out = std::move(__it); }
+    };
+
+
+/// @cond undocumented
+namespace __format
+{
+  template<typename _Ctx, typename _CharT>
+    [[__gnu__::__always_inline__]]
+    inline void
+    __write(_Ctx& __ctx, basic_string_view<_CharT> __str)
+    requires requires { { __ctx.out() } -> output_iterator<const _CharT&>; }
+    {
+      __ctx.advance_to(__format::__write(__ctx.out()));
+    }
+
+  // TODO define __process_format_string which takes an object with callbacks
+  // can use that for initial constexpr parse of format string (with callbacks
+  // that have no side effects, just non-constant on error).
+
+  template<typename _CharT>
+    struct _Scanner
+    {
+      using iterator = typename basic_format_parse_context<_CharT>::iterator;
+
+      basic_format_parse_context<_CharT> _M_pc;
+
+      constexpr explicit
+      _Scanner(basic_string_view<_CharT> __str, size_t __nargs = -1)
+      : _M_pc(__str, __nargs)
+      { }
+
+      constexpr iterator begin() const noexcept { return _M_pc.begin(); }
+      constexpr iterator end() const noexcept { return _M_pc.end(); }
+
+      constexpr void
+      _M_scan()
+      {
+       basic_string_view<_CharT> __fmt = _M_fmt_str();
+
+       if (__fmt.size() == 2 && __fmt[0] == '{' && __fmt[1] == '}')
+         {
+           _M_pc.advance_to(begin() + 1);
+           _M_format_arg(_M_pc.next_arg_id());
+           return;
+         }
+
+       size_t __lbr = __fmt.find('{');
+       size_t __rbr = __fmt.find('}');
+
+       while (__fmt.size())
+         {
+           auto __cmp = __lbr <=> __rbr;
+           if (__cmp == 0)
+             {
+               _M_on_chars(end());
+               _M_pc.advance_to(end());
+               return;
+             }
+           else if (__cmp < 0)
+             {
+               if (__lbr + 1 == __fmt.size()
+                     || (__rbr == __fmt.npos && __fmt[__lbr + 1] != '{'))
+                 __format::__unmatched_left_brace_in_format_string();
+               const bool __is_escape = __fmt[__lbr + 1] == '{';
+               iterator __last = begin() + __lbr + int(__is_escape);
+               _M_on_chars(__last);
+               _M_pc.advance_to(__last + 1);
+               __fmt = _M_fmt_str();
+               if (__is_escape)
+                 {
+                   if (__rbr != __fmt.npos)
+                     __rbr -= __lbr + 2;
+                   __lbr = __fmt.find('{');
+                 }
+               else
+                 {
+                   _M_on_replacement_field();
+                   __fmt = _M_fmt_str();
+                   __lbr = __fmt.find('{');
+                   __rbr = __fmt.find('}');
+                 }
+             }
+           else
+             {
+               if (++__rbr == __fmt.size() || __fmt[__rbr] != '}')
+                 __format::__unmatched_right_brace_in_format_string();
+               iterator __last = begin() + __rbr;
+               _M_on_chars(__last);
+               _M_pc.advance_to(__last + 1);
+               __fmt = _M_fmt_str();
+               if (__lbr != __fmt.npos)
+                 __lbr -= __rbr + 1;
+               __rbr = __fmt.find('}');
+             }
+         }
+      }
+
+      constexpr basic_string_view<_CharT>
+      _M_fmt_str() const noexcept
+      { return {begin(), end()}; }
+
+      constexpr virtual void _M_on_chars(iterator) { }
+
+      constexpr void _M_on_replacement_field()
+      {
+       auto __next = begin();
+
+       size_t __id;
+       if (*__next == '}')
+         __id = _M_pc.next_arg_id();
+       else if (*__next == ':')
+         {
+           __id = _M_pc.next_arg_id();
+           _M_pc.advance_to(++__next);
+         }
+       else
+         {
+           auto [__i, __ptr] = __format::__parse_arg_id(begin(), end());
+           if (!__ptr || !(*__ptr == '}' || *__ptr == ':'))
+             __format::__invalid_arg_id_in_format_string();
+           _M_pc.check_arg_id(__id = __i);
+           if (*__ptr == ':')
+             {
+               _M_pc.advance_to(++__ptr);
+             }
+           else
+             _M_pc.advance_to(__ptr);
+         }
+       _M_format_arg(__id);
+       _M_pc.advance_to(_M_pc.begin() + 1); // Move past '}'
+      }
+
+      constexpr virtual void _M_format_arg(size_t __id) = 0;
+    };
+
+  // Process a format string and format the arguments in the context.
+  template<typename _Out, typename _CharT>
+    class _Formatting_scanner : public _Scanner<_CharT>
+    {
+    public:
+      _Formatting_scanner(basic_format_context<_Out, _CharT>& __fc,
+                         basic_string_view<_CharT> __str)
+      : _Scanner<_CharT>(__str), _M_fc(__fc)
+      { }
+
+    private:
+      basic_format_context<_Out, _CharT>& _M_fc;
+
+      using iterator = typename _Scanner<_CharT>::iterator;
+
+      void
+      _M_on_chars(iterator __last) override
+      {
+       basic_string_view<_CharT> __str(this->begin(), __last);
+       _M_fc.advance_to(__format::__write(_M_fc.out(), __str));
+      }
+
+      void
+      _M_format_arg(size_t __id) override
+      {
+       using _Context = basic_format_context<_Out, _CharT>;
+       using handle = typename basic_format_arg<_Context>::handle;
+
+       std::visit_format_arg([this](auto& __arg) {
+         using _Type = remove_reference_t<decltype(__arg)>;
+         if constexpr (is_same_v<_Type, monostate>)
+           __format::__invalid_arg_id_in_format_string();
+         else if constexpr (is_same_v<_Type, handle>)
+           __arg.format(this->_M_pc, this->_M_fc);
+         else
+           {
+             typename _Context::template formatter_type<_Type> __f;
+             this->_M_pc.advance_to(__f.parse(this->_M_pc));
+             this->_M_fc.advance_to(__f.format(__arg, this->_M_fc));
+           }
+       }, _M_fc.arg(__id));
+      }
+    };
+
+  // Validate a format string for Args.
+  template<typename _CharT, typename... _Args>
+    class _Checking_scanner : public _Scanner<_CharT>
+    {
+    public:
+      constexpr
+      _Checking_scanner(basic_string_view<_CharT> __str)
+      : _Scanner<_CharT>(__str, sizeof...(_Args))
+      { }
+
+    private:
+      constexpr void
+      _M_format_arg(size_t __id) override
+      {
+       if constexpr (sizeof...(_Args) != 0)
+         {
+           if (__id < sizeof...(_Args))
+             {
+               _M_parse_format_spec<_Args...>(__id);
+               return;
+             }
+         }
+       __builtin_unreachable();
+      }
+
+      template<typename _Head, typename... _Tail>
+       constexpr void
+       _M_parse_format_spec(size_t __id)
+       {
+         if (__id == 0)
+           {
+             formatter<_Head, _CharT> __f;
+             this->_M_pc.advance_to(__f.parse(this->_M_pc));
+           }
+         else if constexpr (sizeof...(_Tail) != 0)
+           _M_parse_format_spec<_Tail...>(__id - 1);
+         else
+           __builtin_unreachable();
+       }
+    };
+
+  template<typename _Out, typename _CharT, typename _Context>
+    inline _Out
+    __do_vformat_to(_Out __out, basic_string_view<_CharT> __fmt,
+                   const basic_format_args<_Context>& __args,
+                   const locale* __loc)
+    {
+      _Iter_sink<_CharT, _Out> __sink(std::move(__out));
+      _Sink_iter<_CharT> __sink_out;
+
+      if constexpr (is_same_v<_Out, _Sink_iter<_CharT>>)
+       __sink_out = __out; // Already a sink iterator, safe to use post-move.
+      else
+       __sink_out = __sink.out();
+
+      auto __ctx = __loc == nullptr
+                    ? _Context(__args, __sink_out)
+                    : _Context(__args, __sink_out, *__loc);
+      _Formatting_scanner<_Sink_iter<_CharT>, _CharT> __scanner(__ctx, __fmt);
+      __scanner._M_scan();
+
+      if constexpr (is_same_v<_Out, _Sink_iter<_CharT>>)
+       return __ctx.out();
+      else
+       return std::move(__sink)._M_finish().out;
+    }
+
+} // namespace __format
+/// @endcond
+
+  template<typename _CharT, typename... _Args>
+    template<convertible_to<basic_string_view<_CharT>> _Tp>
+      consteval
+      basic_format_string<_CharT, _Args...>::
+      basic_format_string(const _Tp& __s)
+      : _M_str(__s)
+      {
+       __format::_Checking_scanner<_CharT, remove_cvref_t<_Args>...>
+         __scanner(_M_str);
+       __scanner._M_scan();
+      }
+
+  // [format.functions], formatting functions
+
+  template<typename _Out> requires output_iterator<_Out, const char&>
+    [[__gnu__::__always_inline__]]
+    inline _Out
+    vformat_to(_Out __out, string_view __fmt, format_args __args)
+    { return __format::__do_vformat_to(std::move(__out), __fmt, __args); }
+
+  template<typename _Out> requires output_iterator<_Out, const wchar_t&>
+    [[__gnu__::__always_inline__]]
+    inline _Out
+    vformat_to(_Out __out, wstring_view __fmt, wformat_args __args)
+    { return __format::__do_vformat_to(std::move(__out), __fmt, __args); }
+
+  template<typename _Out> requires output_iterator<_Out, const char&>
+    [[__gnu__::__always_inline__]]
+    inline _Out
+    vformat_to(_Out __out, const locale& __loc, string_view __fmt,
+              format_args __args)
+    { return __format::__do_vformat_to(std::move(__out), __fmt, __args, &__loc); }
+
+  template<typename _Out> requires output_iterator<_Out, const wchar_t&>
+    [[__gnu__::__always_inline__]]
+    inline _Out
+    vformat_to(_Out __out, const locale& __loc, wstring_view __fmt,
+              wformat_args __args)
+    { return __format::__do_vformat_to(std::move(__out), __fmt, __args, &__loc); }
+
+  [[nodiscard]]
+  inline string
+  vformat(string_view __fmt, format_args __args)
+  {
+    __format::_Str_sink<char> __buf;
+    std::vformat_to(__buf.out(), __fmt, __args);
+    return std::move(__buf).get();
+  }
+
+  [[nodiscard]]
+  inline wstring
+  vformat(wstring_view __fmt, wformat_args __args)
+  {
+    __format::_Str_sink<wchar_t> __buf;
+    std::vformat_to(__buf.out(), __fmt, __args);
+    return std::move(__buf).get();
+  }
+
+  [[nodiscard]]
+  inline string
+  vformat(const locale& __loc, string_view __fmt, format_args __args)
+  {
+    __format::_Str_sink<char> __buf;
+    std::vformat_to(__buf.out(), __loc, __fmt, __args);
+    return std::move(__buf).get();
+  }
+
+  [[nodiscard]]
+  inline wstring
+  vformat(const locale& __loc, wstring_view __fmt, wformat_args __args)
+  {
+    __format::_Str_sink<wchar_t> __buf;
+    std::vformat_to(__buf.out(), __loc, __fmt, __args);
+    return std::move(__buf).get();
+  }
+
+  template<typename... _Args>
+    [[nodiscard]]
+    inline string
+    format(format_string<_Args...> __fmt, _Args&&... __args)
+    { return std::vformat(__fmt.get(), std::make_format_args(__args...)); }
+
+  template<typename... _Args>
+    [[nodiscard]]
+    inline wstring
+    format(wformat_string<_Args...> __fmt, _Args&&... __args)
+    { return std::vformat(__fmt.get(), std::make_wformat_args(__args...)); }
+
+  template<typename... _Args>
+    [[nodiscard]]
+    inline string
+    format(const locale& __loc, format_string<_Args...> __fmt,
+          _Args&&... __args)
+    {
+      return std::vformat(__loc, __fmt.get(),
+                         std::make_format_args(__args...));
+    }
+
+  template<typename... _Args>
+    [[nodiscard]]
+    inline wstring
+    format(const locale& __loc, wformat_string<_Args...> __fmt,
+          _Args&&... __args)
+    {
+      return std::vformat(__loc, __fmt.get(),
+                         std::make_wformat_args(__args...));
+    }
+
+  template<typename _Out, typename... _Args>
+    requires output_iterator<_Out, const char&>
+    inline _Out
+    format_to(_Out __out, format_string<_Args...> __fmt, _Args&&... __args)
+    {
+      return std::vformat_to(std::move(__out), __fmt.get(),
+                            std::make_format_args(std::forward<_Args>(__args)...));
+    }
+
+  template<typename _Out, typename... _Args>
+    requires output_iterator<_Out, const wchar_t&>
+    inline _Out
+    format_to(_Out __out, wformat_string<_Args...> __fmt, _Args&&... __args)
+    {
+      return std::vformat_to(std::move(__out), __fmt.get(),
+                            std::make_wformat_args(std::forward<_Args>(__args)...));
+    }
+
+  template<typename _Out, typename... _Args>
+    requires output_iterator<_Out, const char&>
+    inline _Out
+    format_to(_Out __out, const locale& __loc, format_string<_Args...> __fmt,
+             _Args&&... __args)
+    {
+      return std::vformat_to(std::move(__out), __loc, __fmt.get(),
+                            std::make_format_args(std::forward<_Args>(__args)...));
+    }
+
+  template<typename _Out, typename... _Args>
+    requires output_iterator<_Out, const wchar_t&>
+    inline _Out
+    format_to(_Out __out, const locale& __loc, wformat_string<_Args...> __fmt,
+             _Args&&... __args)
+    {
+      return std::vformat_to(std::move(__out), __loc, __fmt.get(),
+                            std::make_wformat_args(std::forward<_Args>(__args)...));
+    }
+
+  template<typename _Out, typename... _Args>
+    requires output_iterator<_Out, const char&>
+    inline format_to_n_result<_Out>
+    format_to_n(_Out __out, iter_difference_t<_Out> __n,
+               format_string<_Args...> __fmt, _Args&&... __args)
+    {
+      __format::_Iter_sink<char, _Out> __sink(std::move(__out), __n);
+      std::vformat_to(__sink.out(), __fmt.get(),
+                     std::make_format_args(__args...));
+      return std::move(__sink)._M_finish();
+    }
+
+  template<typename _Out, typename... _Args>
+    requires output_iterator<_Out, const wchar_t&>
+    inline format_to_n_result<_Out>
+    format_to_n(_Out __out, iter_difference_t<_Out> __n,
+               wformat_string<_Args...> __fmt, _Args&&... __args)
+    {
+      __format::_Iter_sink<wchar_t, _Out> __sink(std::move(__out), __n);
+      std::vformat_to(__sink.out(), __fmt.get(),
+                     std::make_wformat_args(__args...));
+      return std::move(__sink)._M_finish();
+    }
+
+  template<typename _Out, typename... _Args>
+    requires output_iterator<_Out, const char&>
+    inline format_to_n_result<_Out>
+    format_to_n(_Out __out, iter_difference_t<_Out> __n, const locale& __loc,
+               format_string<_Args...> __fmt, _Args&&... __args)
+    {
+      __format::_Iter_sink<char, _Out> __sink(std::move(__out), __n);
+      std::vformat_to(__sink.out(), __loc, __fmt.get(),
+                     std::make_format_args(__args...));
+      return std::move(__sink)._M_finish();
+    }
+
+  template<typename _Out, typename... _Args>
+    requires output_iterator<_Out, const wchar_t&>
+    inline format_to_n_result<_Out>
+    format_to_n(_Out __out, iter_difference_t<_Out> __n, const locale& __loc,
+               wformat_string<_Args...> __fmt, _Args&&... __args)
+    {
+      __format::_Iter_sink<wchar_t, _Out> __sink(std::move(__out), __n);
+      std::vformat_to(__sink.out(), __loc, __fmt.get(),
+                     std::make_wformat_args(__args...));
+      return std::move(__sink)._M_finish();
+    }
+
+/// @cond undocumented
+namespace __format
+{
+#if 1
+  template<typename _CharT>
+    class _Counting_sink : public _Iter_sink<_CharT, _CharT*>
+    {
+    public:
+      _Counting_sink() : _Iter_sink<_CharT, _CharT*>(nullptr, 0) { }
+
+      [[__gnu__::__always_inline__]]
+      size_t
+      count()
+      {
+       _Counting_sink::_M_overflow();
+       return this->_M_count;
+      }
+    };
+#else
+  template<typename _CharT>
+    class _Counting_sink : public _Buf_sink<_CharT>
+    {
+      size_t _M_count = 0;
+
+      void
+      _M_overflow() override
+      {
+       if (!std::is_constant_evaluated())
+         _M_count += this->_M_used().size();
+       this->_M_rewind();
+      }
+
+    public:
+      _Counting_sink() = default;
+
+      [[__gnu__::__always_inline__]]
+      size_t
+      count() noexcept
+      {
+       _Counting_sink::_M_overflow();
+       return _M_count;
+      }
+    };
+#endif
+} // namespace __format
+/// @@endcond
+
+  template<typename... _Args>
+    [[nodiscard]]
+    inline size_t
+    formatted_size(format_string<_Args...> __fmt, _Args&&... __args)
+    {
+      __format::_Counting_sink<char> __buf;
+      std::vformat_to(__buf.out(), __fmt.get(),
+                     std::make_format_args(std::forward<_Args>(__args)...));
+      return __buf.count();
+    }
+
+  template<typename... _Args>
+    [[nodiscard]]
+    inline size_t
+    formatted_size(wformat_string<_Args...> __fmt, _Args&&... __args)
+    {
+      __format::_Counting_sink<wchar_t> __buf;
+      std::vformat_to(__buf.out(), __fmt.get(),
+                     std::make_wformat_args(std::forward<_Args>(__args)...));
+      return __buf.count();
+    }
+
+  template<typename... _Args>
+    [[nodiscard]]
+    inline size_t
+    formatted_size(const locale& __loc, format_string<_Args...> __fmt,
+                  _Args&&... __args)
+    {
+      __format::_Counting_sink<char> __buf;
+      std::vformat_to(__buf.out(), __loc, __fmt.get(),
+                     std::make_format_args(std::forward<_Args>(__args)...));
+      return __buf.count();
+    }
+
+  template<typename... _Args>
+    [[nodiscard]]
+    inline size_t
+    formatted_size(const locale& __loc, wformat_string<_Args...> __fmt,
+                  _Args&&... __args)
+    {
+      __format::_Counting_sink<wchar_t> __buf;
+      std::vformat_to(__buf.out(), __loc, __fmt.get(),
+                     std::make_wformat_args(std::forward<_Args>(__args)...));
+      return __buf.count();
+    }
+
+#if __cpp_lib_format_ranges
+  // [format.range], formatting of ranges
+  // [format.range.fmtkind], variable template format_kind
+  enum class range_format {
+    disabled,
+    map,
+    set,
+    sequence,
+    string,
+    debug_string
+  };
+
+  /// @cond undocumented
+  template<typename _Rg>
+    constexpr auto format_kind = not defined(format_kind<_Rg>);
+
+  template<typename _Tp>
+    consteval range_format
+    __fmt_kind()
+    {
+      using _Ref = ranges::range_reference_t<_Tp>;
+      if constexpr (is_same_v<remove_cvref_t<_Ref>, _Tp>)
+       return range_format::disabled;
+      else if constexpr (requires { typename _Tp::key_type; })
+       {
+         if constexpr (requires { typename _Tp::mapped_type; })
+           {
+             using _Up = remove_cvref_t<_Ref>;
+             if constexpr (__is_pair<_Up>)
+               return range_format::map;
+             else if constexpr (__is_specialization_of<_Up, tuple>)
+               if constexpr (tuple_size_v<_Up> == 2)
+                 return range_format::map;
+           }
+         return range_format::set;
+       }
+      else
+       return range_format::sequence;
+    }
+  /// @endcond
+
+  /// A constant determining how a range should be formatted.
+  template<ranges::input_range _Rg> requires same_as<_Rg, remove_cvref_t<_Rg>>
+    constexpr range_format format_kind<_Rg> = __fmt_kind<_Rg>();
+
+  // [format.range.formatter], class template range_formatter
+  template<typename _Tp, typename _CharT = char>
+    requires same_as<remove_cvref_t<_Tp>, _Tp> && formattable<_Tp, _CharT>
+    class range_formatter; // TODO
+
+/// @cond undocumented
+namespace __format
+{
+  // [format.range.fmtdef], class template range-default-formatter
+  template<range_format _Kind, ranges::input_range _Rg, typename _CharT>
+    struct __range_default_formatter; // TODO
+} // namespace __format
+/// @endcond
+
+  // [format.range.fmtmap], [format.range.fmtset], [format.range.fmtstr],
+  // specializations for maps, sets, and strings
+  template<ranges::input_range _Rg, typename _CharT>
+    requires (format_kind<_Rg> != range_format::disabled)
+      && formattable<ranges::range_reference_t<_Rg>, _CharT>
+    struct formatter<_Rg, _CharT>
+    : __format::__range_default_formatter<format_kind<_Rg>, _Rg, _CharT>
+    { };
+#endif // C++23 formatting ranges
+
+_GLIBCXX_END_NAMESPACE_VERSION
+} // namespace std
+#endif // C++20
+#endif // _GLIBCXX_FORMAT
index 0fa7805183ee01c7b314c95114bd133d69580e55..09dffad3c6f4248f40ec196ca74d864387236bd8 100644 (file)
@@ -1815,6 +1815,32 @@ class StdAtomicPrinter:
             val = self.val['_M_i']
         return '%s<%s> = { %s }' % (self.typename, str(self.value_type), val)
 
+class StdFormatArgsPrinter:
+    "Print a std::basic_format_args"
+    # TODO: add printer for basic_format_arg<C> and print out children
+    # TODO: add printer for basic_format_args<C>::_Store<Args...>
+
+    def __init__(self, typename, val):
+        self.typename = strip_versioned_namespace(typename)
+        self.val = val
+
+    def to_string(self):
+        targs = get_template_arg_list(self.val.type)
+        char_type = get_template_arg_list(targs[0])[1]
+        if char_type == gdb.lookup_type('char'):
+            typ = 'std::format_args'
+        elif char_type == gdb.lookup_type('wchar_t'):
+            typ = 'std::wformat_args'
+        else:
+            typ = 'std::basic_format_args'
+
+        size = self.val['_M_packed_size']
+        if size == 1:
+            return "%s with 1 argument" % (typ)
+        if size == 0:
+            size = self.val['_M_unpacked_size']
+        return "%s with %d arguments" % (typ, size)
+
 # A "regular expression" printer which conforms to the
 # "SubPrettyPrinter" protocol from gdb.printing.
 class RxPrinter(object):
@@ -2355,6 +2381,7 @@ def build_libstdcxx_dictionary ():
     libstdcxx_printer.add_version('std::', 'weak_ordering', StdCmpCatPrinter)
     libstdcxx_printer.add_version('std::', 'strong_ordering', StdCmpCatPrinter)
     libstdcxx_printer.add_version('std::', 'span', StdSpanPrinter)
+    libstdcxx_printer.add_version('std::', 'basic_format_args', StdFormatArgsPrinter)
 
     # Extensions.
     libstdcxx_printer.add_version('__gnu_cxx::', 'slist', StdSlistPrinter)
diff --git a/libstdc++-v3/testsuite/std/format/arguments/args.cc b/libstdc++-v3/testsuite/std/format/arguments/args.cc
new file mode 100644 (file)
index 0000000..ae2eab6
--- /dev/null
@@ -0,0 +1,96 @@
+// { dg-options "-std=gnu++20" }
+// { dg-do run { target c++20 } }
+
+#include <format>
+#include <testsuite_hooks.h>
+
+template<typename Ctx, typename T>
+bool equals(std::basic_format_arg<Ctx> fmt_arg, T expected) {
+  return std::visit_format_arg([=](auto arg_val) {
+    if constexpr (std::is_same_v<decltype(arg_val), T>)
+      return arg_val == expected;
+    else
+      return false;
+  }, fmt_arg);
+}
+
+void
+test_empty()
+{
+  std::format_args args = std::make_format_args();
+  VERIFY(!args.get(0));
+  VERIFY(!args.get(1));
+  VERIFY(!args.get((std::size_t)-1));
+  VERIFY(equals(args.get(0), std::monostate{}));
+
+  std::format_args cargs =  std::make_format_args<std::format_context>();
+  VERIFY(!cargs.get(0));
+  VERIFY(equals(cargs.get(0), std::monostate{}));
+
+  std::wformat_args wargs = std::make_wformat_args();
+  VERIFY(!wargs.get(0));
+  VERIFY(equals(wargs.get(0), std::monostate{}));
+}
+
+enum E { ByGum };
+
+template<>
+struct std::formatter<E> : std::formatter<int>
+{
+  using std::formatter<int>::parse;
+
+  std::format_context::iterator
+  format(E e, std::format_context& fc) const
+  { return std::formatter<int>::format((int)e, fc); }
+};
+
+void
+test_args()
+{
+  auto store = std::make_format_args(false, 1, '2', 3.4);
+  std::format_args args = store;
+  VERIFY(equals(args.get(0), false));
+  VERIFY(equals(args.get(1), 1));
+  VERIFY(equals(args.get(2), '2'));
+  VERIFY(equals(args.get(3), 3.4));
+  VERIFY(!args.get(4));
+
+  auto cstore = std::make_format_args<std::format_context>(5L, 6ULL, 7.8f);
+  std::format_args cargs = cstore;
+  if constexpr (sizeof(long) == sizeof(int))
+    VERIFY(equals(cargs.get(0), 5));
+  else
+    VERIFY(equals(cargs.get(0), 5LL));
+  VERIFY(equals(cargs.get(1), 6ULL));
+  VERIFY(equals(cargs.get(2), 7.8f));
+  VERIFY(!cargs.get(3));
+
+  VERIFY(equals(std::format_args(std::make_format_args(std::string("tenfour"))).get(0), std::string_view("tenfour")));
+
+  // This needs to be on the stack so that testing pointer equality works.
+  wchar_t eleven[] = L"eleven";
+  // This needs to be on the stack so that the wstring_view doesn't dangle.
+  std::wstring tenfour = L"tenfour";
+
+  auto wstore = std::make_wformat_args('9', L'X', eleven, 12.13L, tenfour);
+  std::wformat_args wargs = wstore;
+  VERIFY(equals(wargs.get(0), static_cast<wchar_t>('9')));
+  VERIFY(equals(wargs.get(1), L'X'));
+  VERIFY(equals(wargs.get(2), static_cast<const wchar_t*>(eleven)));
+  VERIFY(equals(wargs.get(3), 12.13L));
+  VERIFY(equals(wargs.get(4), std::wstring_view(tenfour)));
+  VERIFY(!wargs.get(5));
+
+  auto another_store = std::make_format_args(nullptr, E::ByGum);
+  args = another_store;
+  VERIFY(equals(args.get(0), static_cast<const void*>(nullptr)));
+  using handle = std::basic_format_arg<std::format_context>::handle;
+  auto is_handle = []<typename T>(T) { return std::is_same_v<T, handle>; };
+  VERIFY(std::visit_format_arg(is_handle, args.get(1)));
+}
+
+int main()
+{
+  test_empty();
+  test_args();
+}
diff --git a/libstdc++-v3/testsuite/std/format/error.cc b/libstdc++-v3/testsuite/std/format/error.cc
new file mode 100644 (file)
index 0000000..a6918f5
--- /dev/null
@@ -0,0 +1,26 @@
+// { dg-options "-std=gnu++20" }
+// { dg-do run { target c++20 } }
+
+#include <format>
+#include <string>
+#include <testsuite_hooks.h>
+
+static_assert( std::is_base_of_v<std::runtime_error, std::format_error> );
+static_assert( std::is_convertible_v<std::format_error*, std::runtime_error*> );
+
+void
+test_what()
+{
+  const char* cstr = "test string";
+  std::format_error e(cstr);
+  VERIFY( std::string(e.what()).find(cstr) != std::string::npos );
+
+  std::string str = "test std::string";
+  std::format_error ee(str);
+  VERIFY( std::string(ee.what()).find(str) != std::string::npos );
+}
+
+int main()
+{
+  test_what();
+}
diff --git a/libstdc++-v3/testsuite/std/format/formatter.cc b/libstdc++-v3/testsuite/std/format/formatter.cc
new file mode 100644 (file)
index 0000000..64ff2db
--- /dev/null
@@ -0,0 +1,89 @@
+// { dg-options "-std=gnu++20" }
+// { dg-do run { target c++20 } }
+
+#include <format>
+#include <testsuite_hooks.h>
+
+struct S { };
+
+template<> struct std::formatter<S> : std::formatter<const char*> {
+  template<class Out>
+  auto format(S, std::basic_format_context<Out, char>& ctx) const {
+    return formatter<const char*>::format("ess", ctx);
+  }
+};
+
+struct T { };
+
+template<> struct std::formatter<T> : std::formatter<const char*> {
+  // This function only accepts std::format_context, not other contexts.
+  auto format(T, std::format_context& ctx) const {
+    return formatter<const char*>::format("tee", ctx);
+  }
+};
+
+struct U { };
+
+void
+test_concept() // [format.formattable]
+{
+  static_assert( std::formattable<int, char> );
+  static_assert( std::formattable<const int, char> );
+  static_assert( std::formattable<int, wchar_t> );
+  static_assert( std::formattable<const int, wchar_t> );
+  static_assert( std::formattable<char, char> );
+  static_assert( std::formattable<char*, char> );
+  static_assert( std::formattable<wchar_t, wchar_t> );
+  static_assert( std::formattable<wchar_t*, wchar_t> );
+  static_assert( std::formattable<char, wchar_t> );
+  static_assert( ! std::formattable<char*, wchar_t> );
+  static_assert( ! std::formattable<wchar_t, char> );
+  static_assert( ! std::formattable<wchar_t*, char> );
+  static_assert( std::formattable<S, char> );
+  static_assert( std::formattable<const S, char> );
+  static_assert( ! std::formattable<S, wchar_t> ); // only formats as char
+  static_assert( ! std::formattable<T, char> ); // formatter not generic
+  static_assert( ! std::formattable<U, char> ); // no formatter
+}
+
+enum color { red, green, blue };
+const char* color_names[] = { "red", "green", "blue" };
+
+template<> struct std::formatter<color> : std::formatter<const char*> {
+  auto format(color c, format_context& ctx) const {
+    return formatter<const char*>::format(color_names[c], ctx);
+  }
+};
+
+struct err {};
+
+void
+test_specializations() // [format.formatter.spec]
+{
+  std::string s0 = std::format("{}", 42); // OK, library-provided formatter
+  VERIFY( s0 == "42" );
+
+  // std::string s1 = std::format("{}", L"foo"); // error: disabled formatter
+  using Fw = std::format_context::formatter_type<wchar_t>;
+  static_assert( ! std::is_default_constructible_v<Fw> );
+  static_assert( ! std::is_copy_constructible_v<Fw> );
+  static_assert( ! std::is_move_constructible_v<Fw> );
+  static_assert( ! std::is_copy_assignable_v<Fw> );
+  static_assert( ! std::is_move_assignable_v<Fw> );
+
+  std::string s2 = std::format("{}", red);  // OK, user-provided formatter
+  VERIFY( s2 == "red" );
+
+  // std::string s3 = std::format("{}", err{}); // error: disabled formatter
+  using Ferr = std::format_context::formatter_type<err>;
+  static_assert( ! std::is_default_constructible_v<Ferr> );
+  static_assert( ! std::is_copy_constructible_v<Ferr> );
+  static_assert( ! std::is_move_constructible_v<Ferr> );
+  static_assert( ! std::is_copy_assignable_v<Ferr> );
+  static_assert( ! std::is_move_assignable_v<Ferr> );
+}
+
+int main()
+{
+  test_specializations();
+}
diff --git a/libstdc++-v3/testsuite/std/format/functions/format.cc b/libstdc++-v3/testsuite/std/format/functions/format.cc
new file mode 100644 (file)
index 0000000..e9e6169
--- /dev/null
@@ -0,0 +1,313 @@
+// { dg-options "-std=gnu++20" }
+// { dg-do run { target c++20 } }
+
+#include <format>
+#include <string>
+#include <limits>
+#include <cstdint>
+#include <testsuite_hooks.h>
+
+void
+test_no_args()
+{
+  std::string s;
+  s = std::format("disco");
+  VERIFY( s == "disco" );
+
+  s = std::format("}} machine {{ funk }} specialists {{");
+  VERIFY( s == "} machine { funk } specialists {" );
+
+  s = std::format("128bpm }}");
+  VERIFY( s == "128bpm }" );
+}
+
+void
+test_unescaped()
+{
+#ifdef __cpp_exceptions
+  for (auto f : { "{", "}", "{{{", "{{}", "}{", "{{{{{" })
+    try {
+      (void) std::vformat(f, std::make_format_args());
+      VERIFY( false );
+    } catch (const std::format_error& e) {
+      std::string what = e.what();
+      VERIFY( what.find("unmatched") != what.npos );
+    }
+#endif
+}
+
+struct brit_punc : std::numpunct<char>
+{
+  std::string do_grouping() const override { return "\3\3"; }
+  char do_thousands_sep() const override { return ','; }
+  std::string do_truename() const override { return "yes mate"; }
+  std::string do_falsename() const override { return "nah bruv"; }
+};
+
+void
+test_std_examples()
+{
+  using namespace std;
+
+  string s = format("{0}-{{", 8); // value of s is "8-{"
+  VERIFY( s == "8-{" );
+
+  // align
+  {
+    char c = 120;
+    string s0 = format("{:6}", 42);
+    VERIFY(s0 == "    42");
+    string s1 = format("{:6}", 'x');
+    VERIFY(s1 == "x     ");
+    string s2 = format("{:*<6}", 'x');
+    VERIFY(s2 == "x*****");
+    string s3 = format("{:*>6}", 'x');
+    VERIFY(s3 == "*****x");
+    string s4 = format("{:*^6}", 'x');
+    VERIFY(s4 == "**x***");
+    string s5 = format("{:6d}", c);
+    VERIFY(s5 == "   120");
+    string s6 = format("{:6}", true);
+    VERIFY(s6 == "true  ");
+  }
+
+  // sign
+  {
+    double inf = numeric_limits<double>::infinity();
+    double nan = numeric_limits<double>::quiet_NaN();
+    string s0 = format("{0:},{0:+},{0:-},{0: }", 1);
+    VERIFY(s0 == "1,+1,1, 1");
+    string s1 = format("{0:},{0:+},{0:-},{0: }", -1);
+    VERIFY(s1 == "-1,-1,-1,-1");
+    string s2 = format("{0:},{0:+},{0:-},{0: }", inf);
+    VERIFY(s2 == "inf,+inf,inf, inf");
+    string s3 = format("{0:},{0:+},{0:-},{0: }", nan);
+    VERIFY(s3 == "nan,+nan,nan, nan");
+  }
+
+  // alternate form and zero fill
+  {
+    char c = 120;
+    string s1 = format("{:+06d}", c);
+    VERIFY(s1 == "+00120");
+    string s2 = format("{:#06x}", 0xa);
+    VERIFY(s2 == "0x000a");
+    string s3 = format("{:<06}", -42);
+    VERIFY(s3 == "-42   "); // 0 is ignored because of < alignment
+  }
+
+  // integer presentation types
+  {
+    // Change global locale so "{:L}" adds digit separators.
+    std::locale::global(std::locale({}, new brit_punc));
+
+    string s0 = format("{}", 42);
+    VERIFY(s0 == "42");
+    string s1 = format("{0:b} {0:d} {0:o} {0:x}", 42);
+    VERIFY(s1 == "101010 42 52 2a");
+    string s2 = format("{0:#x} {0:#X}", 42);
+    VERIFY(s2 == "0x2a 0X2A");
+    string s3 = format("{:L}", 1234);
+    VERIFY(s3 == "1,234");
+
+    // Restore
+    std::locale::global(std::locale::classic());
+  }
+}
+
+void
+test_alternate_forms()
+{
+  std::string s;
+
+  s = std::format("{0:#b} {0:+#B} {0:#o} {0:#x} {0:+#X} {0: #d}", 42);
+  VERIFY( s == "0b101010 +0B101010 052 0x2a +0X2A  42" );
+  s = std::format("{0:#b} {0:+#B} {0:#o} {0:#x} {0:+#X} {0: #d}", 0);
+  VERIFY( s == "0b0 +0B0 0 0x0 +0X0  0" );
+
+  s = std::format("{0:+#012g} {0:+#014g} {0:+#014g}", 1234.0);
+  VERIFY( s == "+00001234.00 +0000001234.00 +0000001234.00" );
+  s = std::format("{0:+#0{1}g} {0:+#0{2}g} {0:+#0{2}g}", 1234.5, 12, 14);
+  VERIFY( s == "+00001234.50 +0000001234.50 +0000001234.50" );
+
+  s = std::format("{:#.2g}", -0.0);
+  VERIFY( s == "-0.0" );
+}
+
+struct euro_punc : std::numpunct<char>
+{
+  std::string do_grouping() const override { return "\3\3"; }
+  char do_thousands_sep() const override { return '.'; }
+  char do_decimal_point() const override { return ','; }
+};
+
+void
+test_locale()
+{
+  // The default C locale.
+  std::locale cloc = std::locale::classic();
+  // A custom locale using comma digit separators.
+  std::locale bloc(cloc, new brit_punc);
+  // A custom locale using period digit separators.
+  std::locale eloc(cloc, new euro_punc);
+
+  std::string s;
+
+  // Change the global locale:
+  std::locale::global(bloc);
+  // Format using the global locale:
+  s = std::format("{0:L} {0:Lx} {0:Lb}", 12345);
+  VERIFY( s == "12,345 3,039 11,000,000,111,001" );
+  s = std::format("{0:L} {0:.7Lg} {0:La}", 12345.6789);
+  VERIFY( s == "12,345.6789 12,345.68 1.81cd6e631f8a1p+13" );
+
+  s = std::format("{0:s} {0:L} {1:Ls} {0:Ld}", true, false);
+  VERIFY( s == "true yes mate nah bruv 1" );
+
+  // Format using a specific locale:
+  s = std::format(eloc, "{0:L} {0:Lx} {0:Lb}", 12345);
+  VERIFY( s == "12.345 3.039 11.000.000.111.001" );
+  s = std::format(eloc, "{0:L} {0:.7LG} {0:La}", 12345.6789);
+  VERIFY( s == "12.345,6789 12.345,68 1,81cd6e631f8a1p+13" );
+
+  s = std::format(eloc, "{0:#Lg} {0:+#.3Lg} {0:#08.4Lg}", -1234.);
+  VERIFY( s == "-1.234,00 -1,23e+03 -01.234," );
+
+  // Restore
+  std::locale::global(cloc);
+}
+
+void
+test_width()
+{
+  std::string s;
+
+  s = std::format("{:4}", "");
+  VERIFY( s == "    " );
+  s = std::format("{:{}}", "", 3);
+  VERIFY( s == "   " );
+  s = std::format("{1:{0}}", 2, "");
+  VERIFY( s == "  " );
+  s = std::format("{:03}", 9);
+  VERIFY( s == "009" );
+
+  s = std::format("DR {0:{1}}: allow width {1} from arg-id", 3721, 0);
+  VERIFY( s == "DR 3721: allow width 0 from arg-id" );
+
+  try {
+    s = std::format("Negative width is an error: {0:{1}}", 123, -1);
+    VERIFY(false);
+  } catch (const std::format_error&) {
+  }
+
+  try {
+    auto args = std::make_format_args(false, true);
+    s = std::vformat("DR 3720: restrict type of width arg-id {0:{1}}", args);
+    VERIFY(false);
+  } catch (const std::format_error&) {
+  }
+
+  try {
+    auto args = std::make_format_args('?', '!');
+    s = std::vformat("DR 3720: restrict type of width arg-id {0:{1}}", args);
+    VERIFY(false);
+  } catch (const std::format_error&) {
+  }
+}
+
+void
+test_wchar()
+{
+  using namespace std::literals;
+  std::wstring s;
+
+  s = std::format(L"{} {} {} {} {} {}", L'0', 1, 2LL, 3.4, L"five", L"six"s);
+  VERIFY( s == L"0 1 2 3.4 five six" );
+
+  std::locale loc;
+  s = std::format(loc, L"{:L} {:.3s}{:Lc}", true, L"data"sv, '.');
+  VERIFY( s == L"true dat." );
+}
+
+void
+test_minmax()
+{
+  auto check = []<typename T>(T) {
+    const int digits = std::numeric_limits<T>::digits;
+    const std::string zeros(digits, '0');
+    const std::string ones(digits, '1');
+    auto s = std::format("{:b}" , std::numeric_limits<T>::min());
+    VERIFY( s == "-1" + zeros );
+    s = std::format("{:b}" , std::numeric_limits<T>::max());
+    VERIFY( s == ones );
+    using U = std::make_unsigned_t<T>;
+    s = std::format("{:0{}b}" , std::numeric_limits<U>::min(), digits + 1);
+    VERIFY( s == '0' + zeros );
+    s = std::format("{:b}" , std::numeric_limits<U>::max());
+    VERIFY( s == '1' + ones );
+  };
+  check(std::int8_t(0));
+  check(std::int16_t(0));
+  check(std::int32_t(0));
+  check(std::int64_t(0));
+#ifdef __SIZEOF_INT128__
+  check(__int128(0));
+#endif
+}
+
+void
+test_p1652r1() // printf corner cases in std::format
+{
+  std::string s;
+
+  // Problem 1: "#o" specification should not print 0 as "00"
+  s = std::format("{:#o}", 0);
+  VERIFY( s == "0" );
+
+  // Problem 2: 'c' should be able to print 65 as "A" (ASCII)
+  int c = 'A';
+  s = std::format("{:c}", c);
+  VERIFY( s == "A" );
+
+  // Problem 3: "-000nan" is not a floating point value
+  double nan = std::numeric_limits<double>::quiet_NaN();
+  try {
+    s = std::vformat("{:0=6}", std::make_format_args(nan));
+    VERIFY( false );
+  } catch (const std::format_error&) {
+  }
+
+  s = std::format("{:06}", nan);
+  VERIFY( s == "   nan" );
+
+  // Problem 4: bool needs a type format specifier
+  s = std::format("{:s}", true);
+  VERIFY( s == "true" );
+
+  // Problem 5: double does not roundtrip float
+  s = std::format("{}", 3.31f);
+  VERIFY( s == "3.31" );
+}
+
+void
+test_float128()
+{
+#ifdef __SIZEOF_FLOAT128__
+  auto s = std::format("{:#} != {:<+7.3f}", (__float128)-0.0, (__float128)0.5);
+  VERIFY( s == "-0. != +0.500 " );
+#endif
+}
+
+int main()
+{
+  test_no_args();
+  test_unescaped();
+  test_std_examples();
+  test_alternate_forms();
+  test_locale();
+  test_width();
+  test_wchar();
+  test_minmax();
+  test_p1652r1();
+  test_float128();
+}
diff --git a/libstdc++-v3/testsuite/std/format/functions/format_to_n.cc b/libstdc++-v3/testsuite/std/format/functions/format_to_n.cc
new file mode 100644 (file)
index 0000000..846bda3
--- /dev/null
@@ -0,0 +1,96 @@
+// { dg-options "-std=gnu++20" }
+// { dg-do run { target c++20 } }
+
+#include <format>
+#include <vector>
+#include <testsuite_hooks.h>
+
+struct punct : std::numpunct<char>
+{
+  std::string do_grouping() const override { return "\2"; }
+  std::string do_truename() const override { return "troo"; }
+  std::string do_falsename() const override { return "falz"; }
+};
+
+void
+test()
+{
+  char buf[4] = { };
+  auto [out, len] = std::format_to_n(buf, 3, "123 + 456 = {}", 579);
+  VERIFY( out == buf+3 );
+  VERIFY( len == 15 );
+
+  std::locale loc({}, new punct);
+  auto [out2, len2] = std::format_to_n(buf, 4, loc, "{:Ld}", 12345);
+  VERIFY( out2 == buf+4 );
+  VERIFY( len2 == 7 );
+  VERIFY( std::string_view(buf, 4) == "1,23" );
+}
+
+struct wpunct : std::numpunct<wchar_t>
+{
+  std::string do_grouping() const override { return "\2"; }
+  std::wstring do_truename() const override { return L"troo"; }
+  std::wstring do_falsename() const override { return L"falz"; }
+};
+
+void
+test_wchar()
+{
+  wchar_t buf[4] = { };
+  auto [out, len] = std::format_to_n(buf, 3, L"123 + 456 = {}", 579);
+  VERIFY( out == buf+3 );
+  VERIFY( len == 15 );
+
+  std::locale loc({}, new wpunct);
+  auto [out2, len2] = std::format_to_n(buf, 4, loc, L"{:Ld}", 12345);
+  VERIFY( out2 == buf+4 );
+  VERIFY( len2 == 7 );
+  VERIFY( std::wstring_view(buf, 4) == L"1,23" );
+}
+
+template<typename I>
+struct move_only_iterator
+{
+  using iterator = I;
+  using value_type = iterator::value_type;
+  using difference_type = iterator::difference_type;
+  using iterator_category = std::output_iterator_tag;
+
+  move_only_iterator(iterator b) : base_(b) { }
+  move_only_iterator(move_only_iterator&&) = default;
+  move_only_iterator& operator=(move_only_iterator&&) = default;
+
+  move_only_iterator& operator++() { ++base_; return *this; }
+  move_only_iterator operator++(int) { auto tmp = *this; ++base_; return tmp; }
+
+  decltype(auto) operator*() { return *base_; }
+
+private:
+  iterator base_;
+};
+
+void
+test_move_only()
+{
+  std::string str;
+  move_only_iterator mo(std::back_inserter(str));
+  auto [res, len] = std::format_to_n(std::move(mo), 4, "for{:.3} that{:c}",
+                                    "matte", (int)'!');
+  VERIFY( str == "form" );
+  VERIFY( len == 12 );
+
+  std::vector<wchar_t> vec;
+  move_only_iterator wmo(std::back_inserter(vec));
+  auto [wres, wlen] = std::format_to_n(std::move(wmo), 9, L"for{:.3} hat{:c}",
+                                      L"matte", (long)L'!');
+  VERIFY( std::wstring_view(vec.data(), vec.size()) == L"format ha" );
+  VERIFY( wlen == 11 );
+}
+
+int main()
+{
+  test();
+  test_wchar();
+  test_move_only();
+}
diff --git a/libstdc++-v3/testsuite/std/format/functions/size.cc b/libstdc++-v3/testsuite/std/format/functions/size.cc
new file mode 100644 (file)
index 0000000..4509a7c
--- /dev/null
@@ -0,0 +1,52 @@
+// { dg-options "-std=gnu++20" }
+// { dg-do run { target c++20 } }
+
+#include <format>
+#include <string>
+#include <testsuite_hooks.h>
+
+void
+test()
+{
+  auto n = std::formatted_size("");
+  static_assert( std::is_same_v<std::size_t, decltype(n)> );
+  VERIFY( n == 0 );
+
+  n = std::formatted_size("abc");
+  VERIFY( n == 3 );
+
+  n = std::formatted_size("{{abc}}");
+  VERIFY( n == 5 );
+
+  n = std::formatted_size("{{{}}}", 1);
+  VERIFY( n == 3 );
+
+  n = std::formatted_size("{{{}}}", "abc");
+  VERIFY( n == 5 );
+}
+
+void
+test_wchar()
+{
+  auto n = std::formatted_size(L"");
+  static_assert( std::is_same_v<std::size_t, decltype(n)> );
+  VERIFY( n == 0 );
+
+  n = std::formatted_size(L"abc");
+  VERIFY( n == 3 );
+
+  n = std::formatted_size(L"{{abc}}");
+  VERIFY( n == 5 );
+
+  n = std::formatted_size(L"{{{}}}", 1);
+  VERIFY( n == 3 );
+
+  n = std::formatted_size(L"{{{}}}", L"abc");
+  VERIFY( n == 5 );
+}
+
+int main()
+{
+  test();
+  test_wchar();
+}
diff --git a/libstdc++-v3/testsuite/std/format/functions/vformat_to.cc b/libstdc++-v3/testsuite/std/format/functions/vformat_to.cc
new file mode 100644 (file)
index 0000000..6fa33b9
--- /dev/null
@@ -0,0 +1,51 @@
+// { dg-options "-std=gnu++20" }
+// { dg-do run { target c++20 } }
+
+#include <format>
+#include <string>
+#include <iterator>
+#include <testsuite_hooks.h>
+
+template<typename C>
+struct move_only_iterator
+{
+  using iterator = std::back_insert_iterator<std::basic_string<C>>;
+  using value_type = iterator::value_type;
+  using difference_type = iterator::difference_type;
+  using iterator_category = std::output_iterator_tag;
+
+  move_only_iterator(iterator b) : base_(b) { }
+  move_only_iterator(move_only_iterator&&) = default;
+  move_only_iterator& operator=(move_only_iterator&&) = default;
+
+  move_only_iterator& operator++() { ++base_; return *this; }
+  move_only_iterator operator++(int) { auto tmp = *this; ++base_; return tmp; }
+
+  decltype(auto) operator*() { return *base_; }
+
+private:
+  iterator base_;
+};
+
+void
+test_move_only()
+{
+  std::string str;
+  move_only_iterator<char> mo(std::back_inserter(str));
+  auto res = std::vformat_to(std::move(mo), "for{:.3} that{:c}",
+                            std::make_format_args("matte", (int)'!'));
+  static_assert(std::is_same_v<decltype(res), decltype(mo)>);
+  VERIFY( str == "format that!" );
+
+  std::wstring wstr;
+  move_only_iterator<wchar_t> wmo(std::back_inserter(wstr));
+  auto wres = std::vformat_to(std::move(wmo), L"for{:.3} that{:c}",
+                             std::make_wformat_args(L"matte", (long)L'!'));
+  static_assert(std::is_same_v<decltype(wres), decltype(wmo)>);
+  VERIFY( wstr == L"format that!" );
+}
+
+int main()
+{
+  test_move_only();
+}
diff --git a/libstdc++-v3/testsuite/std/format/parse_ctx.cc b/libstdc++-v3/testsuite/std/format/parse_ctx.cc
new file mode 100644 (file)
index 0000000..dd6ca68
--- /dev/null
@@ -0,0 +1,374 @@
+// { dg-options "-std=gnu++20" }
+// { dg-do run { target c++20 } }
+
+#include <format>
+#include <testsuite_hooks.h>
+
+template<typename T, size_t N = 1, bool auto_indexing = true>
+bool
+is_std_format_spec_for(std::string_view spec)
+{
+  std::format_parse_context pc(spec, N);
+  if (auto_indexing)
+    (void) pc.next_arg_id();
+  else
+    pc.check_arg_id(0);
+
+  std::formatter<T> f;
+  try {
+    auto end = f.parse(pc);
+    VERIFY( end == spec.end() || *end == '}' );
+    return true;
+  } catch (const std::format_error&) {
+    return false;
+  }
+}
+
+#if __cpp_lib_format_ranges
+constexpr bool escaped_strings_supported = true;
+#else
+constexpr bool escaped_strings_supported = false;
+#endif
+
+void
+test_char()
+{
+  VERIFY( is_std_format_spec_for<char>("") );
+  VERIFY( is_std_format_spec_for<char>("<") );
+  VERIFY( is_std_format_spec_for<char>(">") );
+  VERIFY( is_std_format_spec_for<char>("^") );
+  VERIFY( is_std_format_spec_for<char>("0<") );
+  VERIFY( is_std_format_spec_for<char>("0>") );
+  VERIFY( is_std_format_spec_for<char>("0^") );
+  VERIFY( ! is_std_format_spec_for<char>("{^") );
+  VERIFY( ! is_std_format_spec_for<char>("+") );
+  VERIFY( ! is_std_format_spec_for<char>("-") );
+  VERIFY( ! is_std_format_spec_for<char>(" ") );
+  VERIFY( ! is_std_format_spec_for<char>("#") );
+  VERIFY( is_std_format_spec_for<char>("0d") );
+  VERIFY( ! is_std_format_spec_for<char>("0") );
+  VERIFY( ! is_std_format_spec_for<char>("00d") );
+  VERIFY( is_std_format_spec_for<char>("01d") );
+  VERIFY( ! is_std_format_spec_for<char>("0{}d") );
+  VERIFY( ! is_std_format_spec_for<char>("0{1}d") );
+  VERIFY(( is_std_format_spec_for<char, 2>("0{}d") ));
+  VERIFY(( ! is_std_format_spec_for<char, 2>("0{1}d") ));
+  VERIFY(( is_std_format_spec_for<char, 2, false>("0{1}d") ));
+  VERIFY( is_std_format_spec_for<char>("1") );
+  VERIFY( ! is_std_format_spec_for<char>("-1") );
+  VERIFY( is_std_format_spec_for<char>("-1d") ); // sign and width
+  VERIFY( ! is_std_format_spec_for<char>(".") );
+  VERIFY( ! is_std_format_spec_for<char>(".1") );
+  VERIFY( is_std_format_spec_for<char>("c") );
+  VERIFY( is_std_format_spec_for<char>("b") );
+  VERIFY( is_std_format_spec_for<char>("B") );
+  VERIFY( is_std_format_spec_for<char>("d") );
+  VERIFY( is_std_format_spec_for<char>("o") );
+  VERIFY( is_std_format_spec_for<char>("x") );
+  VERIFY( is_std_format_spec_for<char>("X") );
+  VERIFY( ! is_std_format_spec_for<char>("s") );
+  VERIFY( is_std_format_spec_for<char>("?") == escaped_strings_supported );
+  VERIFY( ! is_std_format_spec_for<char>("a") );
+  VERIFY( ! is_std_format_spec_for<char>("A") );
+  VERIFY( ! is_std_format_spec_for<char>("f") );
+  VERIFY( ! is_std_format_spec_for<char>("F") );
+  VERIFY( ! is_std_format_spec_for<char>("g") );
+  VERIFY( ! is_std_format_spec_for<char>("G") );
+  VERIFY( ! is_std_format_spec_for<char>("+c") );
+  VERIFY( ! is_std_format_spec_for<char>("+?") );
+  VERIFY( is_std_format_spec_for<char>("+d") );
+}
+
+void
+test_int()
+{
+  VERIFY( is_std_format_spec_for<int>("") );
+  VERIFY( is_std_format_spec_for<int>("<") );
+  VERIFY( is_std_format_spec_for<int>(">") );
+  VERIFY( is_std_format_spec_for<int>("^") );
+  VERIFY( is_std_format_spec_for<int>("0<") );
+  VERIFY( is_std_format_spec_for<int>("0>") );
+  VERIFY( is_std_format_spec_for<int>("0^") );
+  VERIFY( ! is_std_format_spec_for<int>("{^") );
+  VERIFY( is_std_format_spec_for<int>("+") );
+  VERIFY( is_std_format_spec_for<int>("-") );
+  VERIFY( is_std_format_spec_for<int>(" ") );
+  VERIFY( is_std_format_spec_for<int>("#") );
+  VERIFY( is_std_format_spec_for<int>("0d") );
+  VERIFY( is_std_format_spec_for<int>("0") );
+  VERIFY( ! is_std_format_spec_for<int>("00d") );
+  VERIFY( is_std_format_spec_for<int>("01d") );
+  VERIFY( ! is_std_format_spec_for<int>("0{}d") );
+  VERIFY( ! is_std_format_spec_for<int>("0{1}d") );
+  VERIFY(( is_std_format_spec_for<int, 2>("0{}d") ));
+  VERIFY(( ! is_std_format_spec_for<int, 2>("0{1}d") ));
+  VERIFY(( is_std_format_spec_for<int, 2, false>("0{1}d") ));
+  VERIFY( is_std_format_spec_for<int>("1") );
+  VERIFY( is_std_format_spec_for<int>("-1") ); // sign and width
+  VERIFY( ! is_std_format_spec_for<int>(".") );
+  VERIFY( ! is_std_format_spec_for<int>(".1") );
+  VERIFY( is_std_format_spec_for<int>("c") );
+  VERIFY( is_std_format_spec_for<int>("b") );
+  VERIFY( is_std_format_spec_for<int>("B") );
+  VERIFY( is_std_format_spec_for<int>("d") );
+  VERIFY( is_std_format_spec_for<int>("o") );
+  VERIFY( is_std_format_spec_for<int>("x") );
+  VERIFY( is_std_format_spec_for<int>("X") );
+  VERIFY( ! is_std_format_spec_for<int>("s") );
+  VERIFY( ! is_std_format_spec_for<int>("?") );
+  VERIFY( ! is_std_format_spec_for<int>("a") );
+  VERIFY( ! is_std_format_spec_for<int>("A") );
+  VERIFY( ! is_std_format_spec_for<int>("f") );
+  VERIFY( ! is_std_format_spec_for<int>("F") );
+  VERIFY( ! is_std_format_spec_for<int>("g") );
+  VERIFY( ! is_std_format_spec_for<int>("G") );
+  VERIFY( ! is_std_format_spec_for<int>("p") );
+  VERIFY( ! is_std_format_spec_for<int>("P") );
+  VERIFY( is_std_format_spec_for<int>("+c") ); // But LWG 3644 would change it.
+  VERIFY( ! is_std_format_spec_for<int>("+?") );
+  VERIFY( is_std_format_spec_for<int>("+d") );
+}
+
+void
+test_bool()
+{
+  VERIFY( is_std_format_spec_for<bool>("") );
+  VERIFY( is_std_format_spec_for<bool>("<") );
+  VERIFY( is_std_format_spec_for<bool>(">") );
+  VERIFY( is_std_format_spec_for<bool>("^") );
+  VERIFY( is_std_format_spec_for<bool>("0<") );
+  VERIFY( is_std_format_spec_for<bool>("0>") );
+  VERIFY( is_std_format_spec_for<bool>("0^") );
+  VERIFY( ! is_std_format_spec_for<bool>("{^") );
+  VERIFY( ! is_std_format_spec_for<bool>("+") );
+  VERIFY( ! is_std_format_spec_for<bool>("-") );
+  VERIFY( ! is_std_format_spec_for<bool>(" ") );
+  VERIFY( ! is_std_format_spec_for<bool>("#") );
+  VERIFY( is_std_format_spec_for<bool>("0d") );
+  VERIFY( ! is_std_format_spec_for<bool>("0") );
+  VERIFY( ! is_std_format_spec_for<bool>("00d") );
+  VERIFY( is_std_format_spec_for<bool>("01d") );
+  VERIFY( is_std_format_spec_for<bool>("1") );
+  VERIFY( ! is_std_format_spec_for<bool>("-1") );
+  VERIFY( is_std_format_spec_for<bool>("-1d") ); // sign and width
+  VERIFY( ! is_std_format_spec_for<bool>(".") );
+  VERIFY( ! is_std_format_spec_for<bool>(".1") );
+  VERIFY( ! is_std_format_spec_for<bool>("c") );
+  VERIFY( is_std_format_spec_for<bool>("b") );
+  VERIFY( is_std_format_spec_for<bool>("B") );
+  VERIFY( is_std_format_spec_for<bool>("d") );
+  VERIFY( is_std_format_spec_for<bool>("o") );
+  VERIFY( is_std_format_spec_for<bool>("x") );
+  VERIFY( is_std_format_spec_for<bool>("X") );
+  VERIFY( is_std_format_spec_for<bool>("s") );
+  VERIFY( ! is_std_format_spec_for<bool>("?") );
+  VERIFY( ! is_std_format_spec_for<bool>("a") );
+  VERIFY( ! is_std_format_spec_for<bool>("A") );
+  VERIFY( ! is_std_format_spec_for<bool>("f") );
+  VERIFY( ! is_std_format_spec_for<bool>("F") );
+  VERIFY( ! is_std_format_spec_for<bool>("g") );
+  VERIFY( ! is_std_format_spec_for<bool>("G") );
+  VERIFY( ! is_std_format_spec_for<bool>("p") );
+  VERIFY( ! is_std_format_spec_for<bool>("P") );
+  VERIFY( ! is_std_format_spec_for<bool>("+s") );
+  VERIFY( is_std_format_spec_for<bool>("+d") );
+}
+
+void
+test_float()
+{
+  VERIFY( is_std_format_spec_for<float>("") );
+  VERIFY( is_std_format_spec_for<float>("<") );
+  VERIFY( is_std_format_spec_for<float>(">") );
+  VERIFY( is_std_format_spec_for<float>("^") );
+  VERIFY( is_std_format_spec_for<float>("0<") );
+  VERIFY( is_std_format_spec_for<float>("0>") );
+  VERIFY( is_std_format_spec_for<float>("0^") );
+  VERIFY( ! is_std_format_spec_for<float>("{^") );
+  VERIFY( is_std_format_spec_for<float>("+") );
+  VERIFY( is_std_format_spec_for<float>("-") );
+  VERIFY( is_std_format_spec_for<float>(" ") );
+  VERIFY( is_std_format_spec_for<float>("#") );
+  VERIFY( is_std_format_spec_for<float>("0f") );
+  VERIFY( is_std_format_spec_for<float>("0") );
+  VERIFY( ! is_std_format_spec_for<float>("00f") );
+  VERIFY( is_std_format_spec_for<float>("01f") );
+  VERIFY( ! is_std_format_spec_for<float>("0{}f") );
+  VERIFY( ! is_std_format_spec_for<float>("0{1}f") );
+  VERIFY(( is_std_format_spec_for<float, 2>("0{}f") ));
+  VERIFY(( ! is_std_format_spec_for<float, 2>("0{1}f") ));
+  VERIFY(( is_std_format_spec_for<float, 2, false>("0{1}f") ));
+  VERIFY( is_std_format_spec_for<float>("1") );
+  VERIFY( is_std_format_spec_for<float>("-1") ); // sign and width
+  VERIFY( ! is_std_format_spec_for<float>(".") );
+  VERIFY( is_std_format_spec_for<float>(".1") );
+  VERIFY( ! is_std_format_spec_for<float>(".{}") );
+  VERIFY( ! is_std_format_spec_for<float>(".{1}") );
+  VERIFY(( is_std_format_spec_for<float, 2>(".{}") ));
+  VERIFY(( ! is_std_format_spec_for<float, 2>(".{1}") ));
+  VERIFY(( is_std_format_spec_for<float, 2, false>(".{1}") ));
+  VERIFY(( ! is_std_format_spec_for<float, 2>("{}.{}") ));
+  VERIFY(( is_std_format_spec_for<float, 3>("{}.{}") ));
+  VERIFY(( is_std_format_spec_for<float, 2, false>("{1}.{1}") ));
+  VERIFY(( is_std_format_spec_for<float, 3, false>("{2}.{1}") ));
+  VERIFY( ! is_std_format_spec_for<float>("c") );
+  VERIFY( ! is_std_format_spec_for<float>("b") );
+  VERIFY( ! is_std_format_spec_for<float>("B") );
+  VERIFY( ! is_std_format_spec_for<float>("d") );
+  VERIFY( ! is_std_format_spec_for<float>("o") );
+  VERIFY( ! is_std_format_spec_for<float>("x") );
+  VERIFY( ! is_std_format_spec_for<float>("X") );
+  VERIFY( ! is_std_format_spec_for<float>("s") );
+  VERIFY( ! is_std_format_spec_for<float>("?") );
+  VERIFY( is_std_format_spec_for<float>("a") );
+  VERIFY( is_std_format_spec_for<float>("A") );
+  VERIFY( is_std_format_spec_for<float>("f") );
+  VERIFY( is_std_format_spec_for<float>("F") );
+  VERIFY( is_std_format_spec_for<float>("g") );
+  VERIFY( is_std_format_spec_for<float>("G") );
+  VERIFY( ! is_std_format_spec_for<float>("p") );
+  VERIFY( ! is_std_format_spec_for<float>("P") );
+  VERIFY( is_std_format_spec_for<float>("+f") );
+
+  VERIFY( is_std_format_spec_for<float>("_<+#09.6Lf") );
+  VERIFY( is_std_format_spec_for<float>("<+#09.6Lf") );
+  VERIFY( is_std_format_spec_for<float>("<+#9.6Lf") );
+  VERIFY( is_std_format_spec_for<float>(".0006f") );
+}
+
+void
+test_pointer()
+{
+  VERIFY( is_std_format_spec_for<void*>("") );
+  VERIFY( is_std_format_spec_for<void*>("<") );
+  VERIFY( is_std_format_spec_for<void*>(">") );
+  VERIFY( is_std_format_spec_for<void*>("^") );
+  VERIFY( is_std_format_spec_for<void*>("0<") );
+  VERIFY( is_std_format_spec_for<void*>("0>") );
+  VERIFY( is_std_format_spec_for<void*>("0^") );
+  VERIFY( ! is_std_format_spec_for<void*>("{^") );
+  VERIFY( ! is_std_format_spec_for<void*>("+") );
+  VERIFY( ! is_std_format_spec_for<void*>("-") );
+  VERIFY( ! is_std_format_spec_for<void*>(" ") );
+  VERIFY( ! is_std_format_spec_for<void*>("#") );
+  VERIFY( is_std_format_spec_for<void*>("0p") ); // P2510
+  VERIFY( is_std_format_spec_for<void*>("0") );
+  VERIFY( ! is_std_format_spec_for<void*>("00p") );
+  VERIFY( is_std_format_spec_for<void*>("01p") );
+  VERIFY( is_std_format_spec_for<void*>("1") );
+  VERIFY( ! is_std_format_spec_for<void*>("-1") );
+  VERIFY( ! is_std_format_spec_for<void*>("-1p") );
+  VERIFY( ! is_std_format_spec_for<void*>(".") );
+  VERIFY( ! is_std_format_spec_for<void*>(".1") );
+  VERIFY( ! is_std_format_spec_for<void*>("c") );
+  VERIFY( ! is_std_format_spec_for<void*>("b") );
+  VERIFY( ! is_std_format_spec_for<void*>("B") );
+  VERIFY( ! is_std_format_spec_for<void*>("d") );
+  VERIFY( ! is_std_format_spec_for<void*>("o") );
+  VERIFY( ! is_std_format_spec_for<void*>("x") );
+  VERIFY( ! is_std_format_spec_for<void*>("X") );
+  VERIFY( ! is_std_format_spec_for<void*>("s") );
+  VERIFY( ! is_std_format_spec_for<void*>("?") );
+  VERIFY( is_std_format_spec_for<void*>("p") );
+  VERIFY( is_std_format_spec_for<void*>("P") );
+  VERIFY( ! is_std_format_spec_for<void*>("a") );
+  VERIFY( ! is_std_format_spec_for<void*>("A") );
+  VERIFY( ! is_std_format_spec_for<void*>("f") );
+  VERIFY( ! is_std_format_spec_for<void*>("F") );
+  VERIFY( ! is_std_format_spec_for<void*>("g") );
+  VERIFY( ! is_std_format_spec_for<void*>("G") );
+  VERIFY( ! is_std_format_spec_for<void*>("+p") );
+}
+
+void
+test_string()
+{
+  VERIFY( is_std_format_spec_for<const char*>("") );
+  VERIFY( is_std_format_spec_for<const char*>("<") );
+  VERIFY( is_std_format_spec_for<const char*>(">") );
+  VERIFY( is_std_format_spec_for<const char*>("^") );
+  VERIFY( is_std_format_spec_for<const char*>("0<") );
+  VERIFY( is_std_format_spec_for<const char*>("0>") );
+  VERIFY( is_std_format_spec_for<const char*>("0^") );
+  VERIFY( ! is_std_format_spec_for<const char*>("{^") );
+  VERIFY( ! is_std_format_spec_for<const char*>("+") );
+  VERIFY( ! is_std_format_spec_for<const char*>("-") );
+  VERIFY( ! is_std_format_spec_for<const char*>(" ") );
+  VERIFY( ! is_std_format_spec_for<const char*>("#") );
+  VERIFY( ! is_std_format_spec_for<const char*>("0") );
+  VERIFY( ! is_std_format_spec_for<const char*>("01s") );
+  VERIFY( is_std_format_spec_for<const char*>("1") );
+  VERIFY( ! is_std_format_spec_for<const char*>("-1") );
+  VERIFY( ! is_std_format_spec_for<const char*>("-1s") );
+  VERIFY( ! is_std_format_spec_for<const char*>(".") );
+  VERIFY( is_std_format_spec_for<const char*>(".1") );
+  VERIFY( ! is_std_format_spec_for<const char*>(".{}") );
+  VERIFY(( ! is_std_format_spec_for<const char*, 1, false>(".{1}") ));
+  VERIFY(( is_std_format_spec_for<const char*, 1, false>(".{0}") ));
+  VERIFY(( is_std_format_spec_for<const char*, 2>(".{}") ));
+  VERIFY(( is_std_format_spec_for<const char*, 2, false>(".{1}") ));
+  VERIFY( ! is_std_format_spec_for<const char*>("c") );
+  VERIFY( ! is_std_format_spec_for<const char*>("b") );
+  VERIFY( ! is_std_format_spec_for<const char*>("B") );
+  VERIFY( ! is_std_format_spec_for<const char*>("d") );
+  VERIFY( ! is_std_format_spec_for<const char*>("o") );
+  VERIFY( ! is_std_format_spec_for<const char*>("x") );
+  VERIFY( ! is_std_format_spec_for<const char*>("X") );
+  VERIFY( is_std_format_spec_for<const char*>("s") );
+  VERIFY( is_std_format_spec_for<const char*>("?") == escaped_strings_supported );
+  VERIFY( ! is_std_format_spec_for<const char*>("p") );
+  VERIFY( ! is_std_format_spec_for<const char*>("P") );
+  VERIFY( ! is_std_format_spec_for<const char*>("a") );
+  VERIFY( ! is_std_format_spec_for<const char*>("A") );
+  VERIFY( ! is_std_format_spec_for<const char*>("f") );
+  VERIFY( ! is_std_format_spec_for<const char*>("F") );
+  VERIFY( ! is_std_format_spec_for<const char*>("g") );
+  VERIFY( ! is_std_format_spec_for<const char*>("G") );
+
+  VERIFY( is_std_format_spec_for<const char*>("*^6s") );
+  VERIFY( is_std_format_spec_for<const char*>(">6s") );
+  VERIFY( is_std_format_spec_for<const char*>("_<6.4?") == escaped_strings_supported );
+}
+
+struct S { };
+
+template<>
+struct std::formatter<S, char>
+{
+  constexpr std::format_parse_context::iterator
+  parse(std::format_parse_context& pc)
+  {
+    std::string_view spec(pc.begin(), pc.end());
+    auto p = spec.find('}');
+    if (p == std::string_view::npos)
+      p = spec.size();
+    if (p == 0)
+      throw std::format_error("empty format-spec");
+    if (spec != "custom")
+      throw std::format_error("invalid format-spec");
+    return pc.begin() + p;
+  }
+
+  std::format_context::iterator
+  format(const S&, std::format_context&) const;
+};
+
+void
+test_custom()
+{
+  VERIFY( is_std_format_spec_for<S>("custom") );
+  VERIFY( ! is_std_format_spec_for<S>("customer") );
+  VERIFY( ! is_std_format_spec_for<S>("custard") );
+  VERIFY( ! is_std_format_spec_for<S>("") );
+}
+
+int main()
+{
+  test_char();
+  test_int();
+  test_bool();
+  test_float();
+  test_string();
+  test_pointer();
+  test_custom();
+}
diff --git a/libstdc++-v3/testsuite/std/format/string.cc b/libstdc++-v3/testsuite/std/format/string.cc
new file mode 100644 (file)
index 0000000..e421028
--- /dev/null
@@ -0,0 +1,131 @@
+// { dg-options "-std=gnu++20" }
+// { dg-do run { target c++20 } }
+
+#include <format>
+#include <testsuite_hooks.h>
+
+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;
+  }
+}
+
+void
+test_no_args()
+{
+  VERIFY( is_format_string_for("") );
+  VERIFY( is_format_string_for("chars") );
+  VERIFY( is_format_string_for(" The Great Escape {{}} ") );
+
+  VERIFY( ! is_format_string_for("{") );
+  VERIFY( ! is_format_string_for("}") );
+  VERIFY( ! is_format_string_for("}{") );
+  VERIFY( ! is_format_string_for("{{}") );
+  VERIFY( ! is_format_string_for("{{{") );
+  VERIFY( ! is_format_string_for("{{{{{") );
+}
+
+void
+test_indexing()
+{
+  VERIFY( is_format_string_for("{} to {}", "a", "b") );   // automatic indexing
+  VERIFY( is_format_string_for("{1} to {0}", "a", "b") ); // manual indexing
+  VERIFY( ! is_format_string_for("{0} to {}", "a", "b") );  // mixed indexing
+  VERIFY( ! is_format_string_for("{} to {1}", "a", "b") );  // mixed indexing
+
+  VERIFY( is_format_string_for("{} {} {}", 1, 2, 3) );
+  VERIFY( is_format_string_for("{} {} {}", 1, 2, 3, 4) );
+  VERIFY( is_format_string_for("{0} {1} {2}", 1, 2, 3, 4) );
+  VERIFY( is_format_string_for("{1} {2} {3}", 1, 2, 3, 4) );
+  VERIFY( is_format_string_for("{3} {3} {3}", 1, 2, 3, 4) );
+
+  VERIFY( ! is_format_string_for("{2}", 1, 2) );
+
+  VERIFY( ! is_format_string_for("{0} {}", 1) );
+  VERIFY( ! is_format_string_for("{} {0}", 1) );
+}
+
+#if __cpp_lib_format_ranges
+constexpr bool escaped_strings_supported = true;
+#else
+constexpr bool escaped_strings_supported = false;
+#endif
+
+void
+test_format_spec()
+{
+  VERIFY( is_format_string_for("{:}", 1) );
+  VERIFY( is_format_string_for("{0:}", 1) );
+  VERIFY( is_format_string_for("{2:}", 1, 2, 3) );
+  VERIFY( is_format_string_for("{0:s} {0:}", "str") );
+  VERIFY( is_format_string_for("{0:} {0:c}", 'c') );
+  VERIFY( is_format_string_for("{0:p} {0:}", nullptr) );
+  VERIFY( is_format_string_for("{:d} {:+d}", true, true) );
+  VERIFY( is_format_string_for("{:0<-#03Ld}", 1) );
+  VERIFY( is_format_string_for("{1:0<-#03.4Lf}", 1, 2.3) );
+  VERIFY( is_format_string_for("{1:3.3f}", 1, 2.3) );
+  VERIFY( is_format_string_for("{:#d}", 'c') );
+  VERIFY( is_format_string_for("{:#d}", true) );
+  VERIFY( is_format_string_for("{0:s} {0:?}", "str") == escaped_strings_supported );
+  VERIFY( is_format_string_for("{0:} {0:?}", 'c') == escaped_strings_supported );
+
+  // Invalid sign options.
+  VERIFY( ! is_format_string_for("{:+}", "str") );
+  VERIFY( ! is_format_string_for("{:+s}", "str") );
+  VERIFY( ! is_format_string_for("{:+}", 'c') );
+  VERIFY( ! is_format_string_for("{:+c}", 'c') );
+  VERIFY( ! is_format_string_for("{:+p}", nullptr) );
+  VERIFY( ! is_format_string_for("{:+}", true) );
+  VERIFY( ! is_format_string_for("{:+s}", true) );
+  VERIFY( ! is_format_string_for("{:+?}", "str") );
+  VERIFY( ! is_format_string_for("{:+?}", 'c') );
+
+  // Invalid alternate forms.
+  VERIFY( ! is_format_string_for("{:#}", "str") );
+  VERIFY( ! is_format_string_for("{:#s}", "str") );
+  VERIFY( ! is_format_string_for("{:#}", 'c') );
+  VERIFY( ! is_format_string_for("{:#c}", 'c') );
+  VERIFY( ! is_format_string_for("{:#}", true) );
+  VERIFY( ! is_format_string_for("{:#s}", true) );
+  VERIFY( ! is_format_string_for("{:#}", nullptr) );
+  VERIFY( ! is_format_string_for("{:#p}", nullptr) );
+  VERIFY( ! is_format_string_for("{:#?}", "str") );
+  VERIFY( ! is_format_string_for("{:#?}", 'c') );
+
+  // Precision only valid for string and floating-point types.
+  VERIFY( ! is_format_string_for("{:.3d}", 1) );
+  VERIFY( ! is_format_string_for("{:3.3d}", 1) );
+  VERIFY( is_format_string_for("{:3.3s}", "str") );
+  VERIFY( ! is_format_string_for("{:3.3s}", 'c') );
+  VERIFY( ! is_format_string_for("{:3.3p}", nullptr) );
+
+  // Invalid presentation types for integers.
+  VERIFY( ! is_format_string_for("{:f}", 1) );
+  VERIFY( ! is_format_string_for("{:s}", 1) );
+  VERIFY( ! is_format_string_for("{:g}", 1) );
+  VERIFY( ! is_format_string_for("{:E}", 1) );
+  VERIFY( ! is_format_string_for("{:D}", 1) );
+
+  // Invalid presentation types for floating-point types.
+  VERIFY( ! is_format_string_for("{:d}", 1.2) );
+  VERIFY( ! is_format_string_for("{:b}", 1.2) );
+  VERIFY( ! is_format_string_for("{:x}", 1.2) );
+  VERIFY( ! is_format_string_for("{:s}", 1.2) );
+
+  // Invalid presentation types for strings.
+  VERIFY( ! is_format_string_for("{:S}", "str") );
+  VERIFY( ! is_format_string_for("{:d}", "str") );
+}
+
+int main()
+{
+  test_no_args();
+  test_indexing();
+  test_format_spec();
+}
diff --git a/libstdc++-v3/testsuite/std/format/string_neg.cc b/libstdc++-v3/testsuite/std/format/string_neg.cc
new file mode 100644 (file)
index 0000000..8ec7096
--- /dev/null
@@ -0,0 +1,7 @@
+// { dg-options "-std=gnu++20" }
+// { dg-do compile { target c++20 } }
+
+#include <format>
+
+auto s = std::format(" {9} ");
+// { dg-error "invalid.arg.id" "" { target *-*-* } 0 }