[committed] libstdc++: Implement C++20 <format> [PR104166]

Message ID 20221113011454.920766-1-jwakely@redhat.com
State Committed
Headers
Series [committed] libstdc++: Implement C++20 <format> [PR104166] |

Commit Message

Jonathan Wakely Nov. 13, 2022, 1:14 a.m. UTC
  Tested x86_64-linux and powerpc64le-linux. Pushed to trunk.

-- >8 --

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.
---
 libstdc++-v3/include/Makefile.am              |    1 +
 libstdc++-v3/include/Makefile.in              |    1 +
 libstdc++-v3/include/precompiled/stdc++.h     |    1 +
 libstdc++-v3/include/std/format               | 3926 +++++++++++++++++
 libstdc++-v3/python/libstdcxx/v6/printers.py  |   27 +
 .../testsuite/std/format/arguments/args.cc    |   96 +
 libstdc++-v3/testsuite/std/format/error.cc    |   26 +
 .../testsuite/std/format/formatter.cc         |   89 +
 .../testsuite/std/format/functions/format.cc  |  313 ++
 .../std/format/functions/format_to_n.cc       |   96 +
 .../testsuite/std/format/functions/size.cc    |   52 +
 .../std/format/functions/vformat_to.cc        |   51 +
 .../testsuite/std/format/parse_ctx.cc         |  374 ++
 libstdc++-v3/testsuite/std/format/string.cc   |  131 +
 .../testsuite/std/format/string_neg.cc        |    7 +
 15 files changed, 5191 insertions(+)
 create mode 100644 libstdc++-v3/include/std/format
 create mode 100644 libstdc++-v3/testsuite/std/format/arguments/args.cc
 create mode 100644 libstdc++-v3/testsuite/std/format/error.cc
 create mode 100644 libstdc++-v3/testsuite/std/format/formatter.cc
 create mode 100644 libstdc++-v3/testsuite/std/format/functions/format.cc
 create mode 100644 libstdc++-v3/testsuite/std/format/functions/format_to_n.cc
 create mode 100644 libstdc++-v3/testsuite/std/format/functions/size.cc
 create mode 100644 libstdc++-v3/testsuite/std/format/functions/vformat_to.cc
 create mode 100644 libstdc++-v3/testsuite/std/format/parse_ctx.cc
 create mode 100644 libstdc++-v3/testsuite/std/format/string.cc
 create mode 100644 libstdc++-v3/testsuite/std/format/string_neg.cc
  

Patch

diff --git a/libstdc++-v3/include/Makefile.am b/libstdc++-v3/include/Makefile.am
index 96137a6621a..27dfa2be2f3 100644
--- a/libstdc++-v3/include/Makefile.am
+++ b/libstdc++-v3/include/Makefile.am
@@ -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 \
diff --git a/libstdc++-v3/include/Makefile.in b/libstdc++-v3/include/Makefile.in
index be3e12a6f1b..64621922f77 100644
--- a/libstdc++-v3/include/Makefile.in
+++ b/libstdc++-v3/include/Makefile.in
@@ -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 \
diff --git a/libstdc++-v3/include/precompiled/stdc++.h b/libstdc++-v3/include/precompiled/stdc++.h
index 81576899473..b447feb844f 100644
--- a/libstdc++-v3/include/precompiled/stdc++.h
+++ b/libstdc++-v3/include/precompiled/stdc++.h
@@ -210,6 +210,7 @@ 
 #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
index 00000000000..1796362ceef
--- /dev/null
+++ b/libstdc++-v3/include/std/format
@@ -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
diff --git a/libstdc++-v3/python/libstdcxx/v6/printers.py b/libstdc++-v3/python/libstdcxx/v6/printers.py
index 0fa7805183e..09dffad3c6f 100644
--- a/libstdc++-v3/python/libstdcxx/v6/printers.py
+++ b/libstdc++-v3/python/libstdcxx/v6/printers.py
@@ -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
index 00000000000..ae2eab6d560
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/format/arguments/args.cc
@@ -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
index 00000000000..a6918f5ab2e
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/format/error.cc
@@ -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
index 00000000000..64ff2dbfbfd
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/format/formatter.cc
@@ -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
index 00000000000..e9e61694f7d
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/format/functions/format.cc
@@ -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
index 00000000000..846bda30fdf
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/format/functions/format_to_n.cc
@@ -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
index 00000000000..4509a7ccd73
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/format/functions/size.cc
@@ -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
index 00000000000..6fa33b9fbac
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/format/functions/vformat_to.cc
@@ -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
index 00000000000..dd6ca68290b
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/format/parse_ctx.cc
@@ -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
index 00000000000..e421028a873
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/format/string.cc
@@ -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
index 00000000000..8ec7096ffd3
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/format/string_neg.cc
@@ -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 }